diff options
Diffstat (limited to 'sound/x86/intel_hdmi_audio.c')
-rw-r--r-- | sound/x86/intel_hdmi_audio.c | 536 |
1 files changed, 212 insertions, 324 deletions
diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 57042ef3a480..8978dc9bf579 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -622,82 +622,6 @@ static void had_prog_dip(struct snd_pcm_substream *substream, had_write_register(intelhaddata, AUD_CNTL_ST, ctrl_state.regval); } -/* - * Programs buffer address and length registers - * This function programs ring buffer address and length into registers. - */ -static int snd_intelhad_prog_buffer(struct snd_pcm_substream *substream, - struct snd_intelhad *intelhaddata, - int start, int end) -{ - u32 ring_buf_addr, ring_buf_size, period_bytes; - u8 i, num_periods; - - ring_buf_addr = substream->runtime->dma_addr; - ring_buf_size = snd_pcm_lib_buffer_bytes(substream); - intelhaddata->stream_info.ring_buf_size = ring_buf_size; - period_bytes = frames_to_bytes(substream->runtime, - substream->runtime->period_size); - num_periods = substream->runtime->periods; - - /* - * buffer addr should be 64 byte aligned, period bytes - * will be used to calculate addr offset - */ - period_bytes &= ~0x3F; - - /* Hardware supports MAX_PERIODS buffers */ - if (end >= HAD_MAX_PERIODS) - return -EINVAL; - - for (i = start; i <= end; i++) { - /* Program the buf registers with addr and len */ - intelhaddata->buf_info[i].buf_addr = ring_buf_addr + - (i * period_bytes); - if (i < num_periods-1) - intelhaddata->buf_info[i].buf_size = period_bytes; - else - intelhaddata->buf_info[i].buf_size = ring_buf_size - - (i * period_bytes); - - had_write_register(intelhaddata, - AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH), - intelhaddata->buf_info[i].buf_addr | - BIT(0) | BIT(1)); - had_write_register(intelhaddata, - AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH), - period_bytes); - intelhaddata->buf_info[i].is_valid = true; - } - dev_dbg(intelhaddata->dev, "%s:buf[%d-%d] addr=%#x and size=%d\n", - __func__, start, end, - intelhaddata->buf_info[start].buf_addr, - intelhaddata->buf_info[start].buf_size); - intelhaddata->valid_buf_cnt = num_periods; - return 0; -} - -static int snd_intelhad_read_len(struct snd_intelhad *intelhaddata) -{ - int i, retval = 0; - u32 len[4]; - - for (i = 0; i < 4 ; i++) { - had_read_register(intelhaddata, - AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH), - &len[i]); - if (!len[i]) - retval++; - } - if (retval != 1) { - for (i = 0; i < 4 ; i++) - dev_dbg(intelhaddata->dev, "buf[%d] size=%d\n", - i, len[i]); - } - - return retval; -} - static int had_calculate_maud_value(u32 aud_samp_freq, u32 link_rate) { u32 maud_val; @@ -885,33 +809,217 @@ static int had_prog_n(u32 aud_samp_freq, u32 *n_param, return 0; } +/* + * PCM ring buffer handling + * + * The hardware provides a ring buffer with the fixed 4 buffer descriptors + * (BDs). The driver maps these 4 BDs onto the PCM ring buffer. The mapping + * moves at each period elapsed. The below illustrates how it works: + * + * At time=0 + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 0 | 1 | 2 | 3 | + * + * At time=1 (period elapsed) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 1 | 2 | 3 | 0 | + * + * At time=2 (second period elapsed) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 2 | 3 | 0 | 1 | + * + * The bd_head field points to the index of the BD to be read. It's also the + * position to be filled at next. The pcm_head and the pcm_filled fields + * point to the indices of the current position and of the next position to + * be filled, respectively. For PCM buffer there are both _head and _filled + * because they may be difference when nperiods > 4. For example, in the + * example above at t=1, bd_head=1 and pcm_head=1 while pcm_filled=5: + * + * pcm_head (=1) --v v-- pcm_filled (=5) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 1 | 2 | 3 | 0 | + * bd_head (=1) --^ ^-- next to fill (= bd_head) + * + * For nperiods < 4, the remaining BDs out of 4 are marked as invalid, so that + * the hardware skips those BDs in the loop. + */ + +#define AUD_BUF_ADDR(x) (AUD_BUF_A_ADDR + (x) * HAD_REG_WIDTH) +#define AUD_BUF_LEN(x) (AUD_BUF_A_LENGTH + (x) * HAD_REG_WIDTH) + +/* Set up a buffer descriptor at the "filled" position */ +static void had_prog_bd(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int idx = intelhaddata->bd_head; + int ofs = intelhaddata->pcmbuf_filled * intelhaddata->period_bytes; + u32 addr = substream->runtime->dma_addr + ofs; + + addr |= AUD_BUF_VALID | AUD_BUF_INTR_EN; + had_write_register(intelhaddata, AUD_BUF_ADDR(idx), addr); + had_write_register(intelhaddata, AUD_BUF_LEN(idx), + intelhaddata->period_bytes); + + /* advance the indices to the next */ + intelhaddata->bd_head++; + intelhaddata->bd_head %= intelhaddata->num_bds; + intelhaddata->pcmbuf_filled++; + intelhaddata->pcmbuf_filled %= substream->runtime->periods; +} + +/* invalidate a buffer descriptor with the given index */ +static void had_invalidate_bd(struct snd_intelhad *intelhaddata, + int idx) +{ + had_write_register(intelhaddata, AUD_BUF_ADDR(idx), 0); + had_write_register(intelhaddata, AUD_BUF_LEN(idx), 0); +} + +/* Initial programming of ring buffer */ +static void had_init_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int i, num_periods; + + num_periods = runtime->periods; + intelhaddata->num_bds = min(num_periods, HAD_NUM_OF_RING_BUFS); + intelhaddata->period_bytes = + frames_to_bytes(runtime, runtime->period_size); + WARN_ON(intelhaddata->period_bytes & 0x3f); + + intelhaddata->bd_head = 0; + intelhaddata->pcmbuf_head = 0; + intelhaddata->pcmbuf_filled = 0; + + for (i = 0; i < HAD_NUM_OF_RING_BUFS; i++) { + if (i < num_periods) + had_prog_bd(substream, intelhaddata); + else /* invalidate the rest */ + had_invalidate_bd(intelhaddata, i); + } + + intelhaddata->bd_head = 0; /* reset at head again before starting */ +} + +/* process a bd, advance to the next */ +static void had_advance_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int num_periods = substream->runtime->periods; + + /* reprogram the next buffer */ + had_prog_bd(substream, intelhaddata); + + /* proceed to next */ + intelhaddata->pcmbuf_head++; + intelhaddata->pcmbuf_head %= num_periods; +} + +/* process the current BD(s); + * returns the current PCM buffer byte position, or -EPIPE for underrun. + */ +static int had_process_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int len, processed; + unsigned long flags; + + processed = 0; + spin_lock_irqsave(&intelhaddata->had_spinlock, flags); + for (;;) { + /* get the remaining bytes on the buffer */ + had_read_register(intelhaddata, + AUD_BUF_LEN(intelhaddata->bd_head), + &len); + if (len < 0 || len > intelhaddata->period_bytes) { + dev_dbg(intelhaddata->dev, "Invalid buf length %d\n", + len); + len = -EPIPE; + goto out; + } + + if (len > 0) /* OK, this is the current buffer */ + break; + + /* len=0 => already empty, check the next buffer */ + if (++processed >= intelhaddata->num_bds) { + len = -EPIPE; /* all empty? - report underrun */ + goto out; + } + had_advance_ringbuf(substream, intelhaddata); + } + + len = intelhaddata->period_bytes - len; + len += intelhaddata->period_bytes * intelhaddata->pcmbuf_head; + out: + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); + return len; +} + +/* called from irq handler */ +static void had_process_buffer_done(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + + if (!intelhaddata->connected) + return; /* disconnected? - bail out */ + + substream = had_substream_get(intelhaddata); + if (!substream) + return; /* no stream? - bail out */ + + /* process or stop the stream */ + if (had_process_ringbuf(substream, intelhaddata) < 0) + snd_pcm_stop_xrun(substream); + else + snd_pcm_period_elapsed(substream); + + had_substream_put(intelhaddata); +} + #define MAX_CNT 0xFF -static void snd_intelhad_handle_underrun(struct snd_intelhad *intelhaddata) +/* + * The interrupt status 'sticky' bits might not be cleared by + * setting '1' to that bit once... + */ +static void wait_clear_underrun_bit(struct snd_intelhad *intelhaddata) +{ + int i; + u32 val; + + for (i = 0; i < MAX_CNT; i++) { + /* clear bit30, 31 AUD_HDMI_STATUS */ + had_read_register(intelhaddata, AUD_HDMI_STATUS, &val); + if (!(val & AUD_CONFIG_MASK_UNDERRUN)) + return; + had_write_register(intelhaddata, AUD_HDMI_STATUS, val); + } + dev_err(intelhaddata->dev, "Unable to clear UNDERRUN bits\n"); +} + +/* called from irq handler */ +static void had_process_buffer_underrun(struct snd_intelhad *intelhaddata) { - u32 hdmi_status = 0, i = 0; + struct snd_pcm_substream *substream; /* Handle Underrun interrupt within Audio Unit */ had_write_register(intelhaddata, AUD_CONFIG, 0); /* Reset buffer pointers */ had_reset_audio(intelhaddata); - /* - * The interrupt status 'sticky' bits might not be cleared by - * setting '1' to that bit once... - */ - do { /* clear bit30, 31 AUD_HDMI_STATUS */ - had_read_register(intelhaddata, AUD_HDMI_STATUS, - &hdmi_status); - dev_dbg(intelhaddata->dev, "HDMI status =0x%x\n", hdmi_status); - if (hdmi_status & AUD_CONFIG_MASK_UNDERRUN) { - i++; - had_write_register(intelhaddata, - AUD_HDMI_STATUS, hdmi_status); - } else - break; - } while (i < MAX_CNT); - if (i >= MAX_CNT) - dev_err(intelhaddata->dev, "Unable to clear UNDERRUN bits\n"); + + wait_clear_underrun_bit(intelhaddata); + + if (!intelhaddata->connected) + return; /* disconnected? - bail out */ + + /* Report UNDERRUN error to above layers */ + substream = had_substream_get(intelhaddata); + if (substream) { + snd_pcm_stop_xrun(substream); + had_substream_put(intelhaddata); + } } /* @@ -957,11 +1065,6 @@ static int had_pcm_open(struct snd_pcm_substream *substream) intelhaddata->stream_info.substream_refcount++; spin_unlock_irq(&intelhaddata->had_spinlock); - /* these are cleared in prepare callback, but just to be sure */ - intelhaddata->curr_buf = 0; - intelhaddata->underrun_count = 0; - intelhaddata->stream_info.buffer_rendered = 0; - return retval; error: pm_runtime_put(intelhaddata->dev); @@ -1123,10 +1226,6 @@ static int had_pcm_prepare(struct snd_pcm_substream *substream) dev_dbg(intelhaddata->dev, "rate=%d\n", runtime->rate); dev_dbg(intelhaddata->dev, "channels=%d\n", runtime->channels); - intelhaddata->curr_buf = 0; - intelhaddata->underrun_count = 0; - intelhaddata->stream_info.buffer_rendered = 0; - /* Get N value in KHz */ disp_samp_freq = intelhaddata->tmds_clock_speed; @@ -1148,8 +1247,7 @@ static int had_pcm_prepare(struct snd_pcm_substream *substream) retval = had_init_audio_ctrl(substream, intelhaddata); /* Prog buffer address */ - retval = snd_intelhad_prog_buffer(substream, intelhaddata, - HAD_BUF_TYPE_A, HAD_BUF_TYPE_D); + had_init_ringbuf(substream, intelhaddata); /* * Program channel mapping in following order: @@ -1168,48 +1266,17 @@ prep_end: static snd_pcm_uframes_t had_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_intelhad *intelhaddata; - u32 bytes_rendered = 0; - u32 t; - int buf_id; + int len; intelhaddata = snd_pcm_substream_chip(substream); if (!intelhaddata->connected) return SNDRV_PCM_POS_XRUN; - /* Use a hw register to calculate sub-period position reports. - * This makes PulseAudio happier. - */ - - buf_id = intelhaddata->curr_buf % 4; - had_read_register(intelhaddata, - AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), &t); - - if ((t == 0) || (t == ((u32)-1L))) { - intelhaddata->underrun_count++; - dev_dbg(intelhaddata->dev, - "discovered buffer done for buf %d, count = %d\n", - buf_id, intelhaddata->underrun_count); - - if (intelhaddata->underrun_count > (HAD_MIN_PERIODS/2)) { - dev_dbg(intelhaddata->dev, - "assume audio_codec_reset, underrun = %d - do xrun\n", - intelhaddata->underrun_count); - return SNDRV_PCM_POS_XRUN; - } - } else { - /* Reset Counter */ - intelhaddata->underrun_count = 0; - } - - t = intelhaddata->buf_info[buf_id].buf_size - t; - - if (intelhaddata->stream_info.buffer_rendered) - div_u64_rem(intelhaddata->stream_info.buffer_rendered, - intelhaddata->stream_info.ring_buf_size, - &(bytes_rendered)); - - return bytes_to_frames(substream->runtime, bytes_rendered + t); + len = had_process_ringbuf(substream, intelhaddata); + if (len < 0) + return SNDRV_PCM_POS_XRUN; + return bytes_to_frames(substream->runtime, len); } /* @@ -1278,179 +1345,9 @@ out: return retval; } -static inline int had_chk_intrmiss(struct snd_intelhad *intelhaddata, - enum intel_had_aud_buf_type buf_id) -{ - int i, intr_count = 0; - enum intel_had_aud_buf_type buff_done; - u32 buf_size, buf_addr; - - buff_done = buf_id; - - intr_count = snd_intelhad_read_len(intelhaddata); - if (intr_count > 1) { - /* In case of active playback */ - dev_err(intelhaddata->dev, - "Driver detected %d missed buffer done interrupt(s)\n", - (intr_count - 1)); - if (intr_count > 3) - return intr_count; - - buf_id += (intr_count - 1); - /* Reprogram registers*/ - for (i = buff_done; i < buf_id; i++) { - int j = i % 4; - - buf_size = intelhaddata->buf_info[j].buf_size; - buf_addr = intelhaddata->buf_info[j].buf_addr; - had_write_register(intelhaddata, - AUD_BUF_A_LENGTH + - (j * HAD_REG_WIDTH), buf_size); - had_write_register(intelhaddata, - AUD_BUF_A_ADDR+(j * HAD_REG_WIDTH), - (buf_addr | BIT(0) | BIT(1))); - } - buf_id = buf_id % 4; - intelhaddata->buff_done = buf_id; - } - - return intr_count; -} - -/* called from irq handler */ -static int had_process_buffer_done(struct snd_intelhad *intelhaddata) -{ - u32 len = 1; - enum intel_had_aud_buf_type buf_id; - enum intel_had_aud_buf_type buff_done; - struct pcm_stream_info *stream; - struct snd_pcm_substream *substream; - u32 buf_size; - int intr_count; - unsigned long flags; - - stream = &intelhaddata->stream_info; - intr_count = 1; - - spin_lock_irqsave(&intelhaddata->had_spinlock, flags); - if (!intelhaddata->connected) { - spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); - dev_dbg(intelhaddata->dev, - "%s:Device already disconnected\n", __func__); - return 0; - } - buf_id = intelhaddata->curr_buf; - intelhaddata->buff_done = buf_id; - buff_done = intelhaddata->buff_done; - buf_size = intelhaddata->buf_info[buf_id].buf_size; - - /* Every debug statement has an implication - * of ~5msec. Thus, avoid having >3 debug statements - * for each buffer_done handling. - */ - - /* Check for any intr_miss in case of active playback */ - if (stream->running) { - intr_count = had_chk_intrmiss(intelhaddata, buf_id); - if (!intr_count || (intr_count > 3)) { - spin_unlock_irqrestore(&intelhaddata->had_spinlock, - flags); - dev_err(intelhaddata->dev, - "HAD SW state in non-recoverable mode\n"); - return 0; - } - buf_id += (intr_count - 1); - buf_id = buf_id % 4; - } - - intelhaddata->buf_info[buf_id].is_valid = true; - if (intelhaddata->valid_buf_cnt-1 == buf_id) { - if (stream->running) - intelhaddata->curr_buf = HAD_BUF_TYPE_A; - } else - intelhaddata->curr_buf = buf_id + 1; - - spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); - - if (!intelhaddata->connected) { - dev_dbg(intelhaddata->dev, "HDMI cable plugged-out\n"); - return 0; - } - - /* Reprogram the registers with addr and length */ - had_write_register(intelhaddata, - AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), - buf_size); - had_write_register(intelhaddata, - AUD_BUF_A_ADDR + (buf_id * HAD_REG_WIDTH), - intelhaddata->buf_info[buf_id].buf_addr | - BIT(0) | BIT(1)); - - had_read_register(intelhaddata, - AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), - &len); - dev_dbg(intelhaddata->dev, "%s:Enabled buf[%d]\n", __func__, buf_id); - - /* In case of actual data, - * report buffer_done to above ALSA layer - */ - substream = had_substream_get(intelhaddata); - if (substream) { - buf_size = intelhaddata->buf_info[buf_id].buf_size; - intelhaddata->stream_info.buffer_rendered += - (intr_count * buf_size); - snd_pcm_period_elapsed(substream); - had_substream_put(intelhaddata); - } - - return 0; -} - -/* called from irq handler */ -static int had_process_buffer_underrun(struct snd_intelhad *intelhaddata) -{ - enum intel_had_aud_buf_type buf_id; - struct pcm_stream_info *stream; - struct snd_pcm_substream *substream; - unsigned long flags; - int connected; - - stream = &intelhaddata->stream_info; - - spin_lock_irqsave(&intelhaddata->had_spinlock, flags); - buf_id = intelhaddata->curr_buf; - intelhaddata->buff_done = buf_id; - connected = intelhaddata->connected; - if (stream->running) - intelhaddata->curr_buf = HAD_BUF_TYPE_A; - - spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); - - dev_dbg(intelhaddata->dev, "Enter:%s buf_id=%d, stream_running=%d\n", - __func__, buf_id, stream->running); - - snd_intelhad_handle_underrun(intelhaddata); - - if (!connected) { - dev_dbg(intelhaddata->dev, - "%s:Device already disconnected\n", __func__); - return 0; - } - - /* Report UNDERRUN error to above layers */ - substream = had_substream_get(intelhaddata); - if (substream) { - snd_pcm_stop_xrun(substream); - had_substream_put(intelhaddata); - } - - return 0; -} - /* process hot plug, called from wq with mutex locked */ static void had_process_hot_plug(struct snd_intelhad *intelhaddata) { - enum intel_had_aud_buf_type buf_id; struct snd_pcm_substream *substream; spin_lock_irq(&intelhaddata->had_spinlock); @@ -1460,17 +1357,12 @@ static void had_process_hot_plug(struct snd_intelhad *intelhaddata) return; } - buf_id = intelhaddata->curr_buf; - intelhaddata->buff_done = buf_id; intelhaddata->connected = true; dev_dbg(intelhaddata->dev, "%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_CONNECTED\n", __func__, __LINE__); spin_unlock_irq(&intelhaddata->had_spinlock); - dev_dbg(intelhaddata->dev, "Processing HOT_PLUG, buf_id = %d\n", - buf_id); - /* Safety check */ substream = had_substream_get(intelhaddata); if (substream) { @@ -1487,11 +1379,8 @@ static void had_process_hot_plug(struct snd_intelhad *intelhaddata) /* process hot unplug, called from wq with mutex locked */ static void had_process_hot_unplug(struct snd_intelhad *intelhaddata) { - enum intel_had_aud_buf_type buf_id; struct snd_pcm_substream *substream; - buf_id = intelhaddata->curr_buf; - substream = had_substream_get(intelhaddata); spin_lock_irq(&intelhaddata->had_spinlock); @@ -1862,13 +1751,12 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev) dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); - /* allocate dma pages for ALSA stream operations - * memory allocated is based on size, not max value - * thus using same argument for max & size + /* allocate dma pages; + * try to allocate 600k buffer as default which is large enough */ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL, - HAD_MAX_BUFFER, HAD_MAX_BUFFER); + HAD_DEFAULT_BUFFER, HAD_MAX_BUFFER); /* create controls */ for (i = 0; i < ARRAY_SIZE(had_controls); i++) { |