diff options
Diffstat (limited to 'sound/soc/imx/imx-hdmi-dma.c')
-rw-r--r-- | sound/soc/imx/imx-hdmi-dma.c | 209 |
1 files changed, 148 insertions, 61 deletions
diff --git a/sound/soc/imx/imx-hdmi-dma.c b/sound/soc/imx/imx-hdmi-dma.c index 34a64d6072d6..9ce99efccc1b 100644 --- a/sound/soc/imx/imx-hdmi-dma.c +++ b/sound/soc/imx/imx-hdmi-dma.c @@ -39,7 +39,8 @@ struct imx_hdmi_dma_runtime_data { struct snd_pcm_substream *tx_substream; unsigned long buffer_bytes; - void *buf; + struct snd_dma_buffer hw_buffer; + unsigned long appl_bytes; int period_time; int periods; @@ -147,7 +148,7 @@ static void dumprtd(struct imx_hdmi_dma_runtime_data *rtd) pr_debug("period_bytes = %d\n", rtd->period_bytes); pr_debug("dma period_bytes = %d\n", rtd->dma_period_bytes); pr_debug("buffer_ratio = %d\n", rtd->buffer_ratio); - pr_debug("dma buf addr = 0x%08x\n", (int)rtd->buf); + pr_debug("hw dma buffer = 0x%08x\n", (int)rtd->hw_buffer.addr); pr_debug("dma buf size = %d\n", (int)rtd->buffer_bytes); pr_debug("sample_rate = %d\n", (int)rtd->rate); } @@ -225,13 +226,86 @@ static void hdmi_dma_irq_mute(int mute) hdmi_writeb(0x00, HDMI_IH_MUTE_AHBDMAAUD_STAT0); } +/* Add frame information for one pcm subframe */ +static u32 hdmi_dma_add_frame_info(struct imx_hdmi_dma_runtime_data *rtd, + u32 pcm_data, int subframe_idx) +{ + hdmi_audio_dma_data_t subframe; + int i; + + subframe.U = 0; + iec_header.B.channel = subframe_idx; + + /* fill b (start-of-block) */ + subframe.B.b = (rtd->frame_idx == 0) ? 1 : 0; + + /* fill c (channel status) */ + if (rtd->frame_idx < 42) + subframe.B.c = (iec_header.U >> rtd->frame_idx) & 0x1; + else + subframe.B.c = 0; + + /* fill p (parity) */ + for (i = 0 ; i < rtd->sample_bits ; i++) + subframe.B.p ^= (pcm_data >> i) & 0x01; + subframe.B.p ^= subframe.B.c; + subframe.B.p ^= subframe.B.u; + subframe.B.p ^= subframe.B.v; + + /* fill data */ + if (rtd->sample_bits == 16) + subframe.B.data = pcm_data << 8; + else + subframe.B.data = pcm_data; + + return subframe.U; +} + +/* Increment the frame index. We save frame_idx in case a frame + * spans more than one dma period. */ +static void hdmi_dma_incr_frame_idx(struct imx_hdmi_dma_runtime_data *rtd) +{ + rtd->frame_idx++; + if (rtd->frame_idx == 192) + rtd->frame_idx = 0; +} + +static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream, + int offset, int count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data; + u32 *hw_buf, *dma_buf_u32; + u16 *dma_buf_u16; + int subframe_idx; + + /* dma_buffer is the mmapped buffer we are copying pcm from. */ + dma_buf_u16 = (u16 *)(runtime->dma_area + offset); + dma_buf_u32 = (u32 *)(runtime->dma_area + offset); + + /* hw_buffer is the destination for pcm data plus frame info. */ + hw_buf = (u32 *)(rtd->hw_buffer.area + (offset * rtd->buffer_ratio)); + + while (count > 0) { + for (subframe_idx = 1 ; subframe_idx <= rtd->channels ; subframe_idx++) { + if (rtd->sample_align == 2) + *hw_buf++ = hdmi_dma_add_frame_info(rtd, *dma_buf_u16++, subframe_idx); + else + *hw_buf++ = hdmi_dma_add_frame_info(rtd, *dma_buf_u32++, subframe_idx); + + count -= rtd->sample_align; + } + hdmi_dma_incr_frame_idx(rtd); + } +} + static irqreturn_t hdmi_dma_isr(int irq, void *dev_id) { struct imx_hdmi_dma_runtime_data *rtd = dev_id; struct snd_pcm_substream *substream = rtd->tx_substream; - struct snd_dma_buffer *dma_buffer; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset, count, space_to_end, appl_bytes; unsigned int status; - unsigned int dma_offset; unsigned long flags; spin_lock_irqsave(&rtd->irq_lock, flags); @@ -249,17 +323,28 @@ static irqreturn_t hdmi_dma_isr(int irq, void *dev_id) rtd->offset += rtd->period_bytes; rtd->offset %= rtd->period_bytes * rtd->periods; - dma_offset = rtd->offset * rtd->buffer_ratio; + /* For mmap access, need to copy data from dma_buffer to hw_buffer + * and add the frame info. */ + if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) { + appl_bytes = frames_to_bytes(runtime, + runtime->control->appl_ptr); + count = appl_bytes - rtd->appl_bytes; + offset = rtd->appl_bytes % rtd->buffer_bytes; + space_to_end = rtd->buffer_bytes - offset; + + if (count <= space_to_end) + hdmi_dma_mmap_copy(substream, offset, count); + else { + hdmi_dma_mmap_copy(substream, offset, space_to_end); + hdmi_dma_mmap_copy(substream, 0, count - space_to_end); + } + rtd->appl_bytes = appl_bytes; + } snd_pcm_period_elapsed(substream); - pr_debug("HDMI DMA Done interrupt: period_bytes=%d offset=%d\n", - rtd->period_bytes, (int)rtd->offset); - - dma_buffer = &substream->dma_buffer; - - hdmi_dma_set_addr(dma_buffer->addr + dma_offset, - rtd->dma_period_bytes); + hdmi_dma_set_addr(rtd->hw_buffer.addr + (rtd->offset * rtd->buffer_ratio), + rtd->dma_period_bytes); hdmi_dma_start(); } @@ -381,10 +466,18 @@ static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream) iec_header.B.sample_freq = 0x02; iec_header.B.org_sample_freq = 0x0D; break; + case 88200: + iec_header.B.sample_freq = 0x08; + iec_header.B.org_sample_freq = 0x07; + break; case 96000: iec_header.B.sample_freq = 0x0A; iec_header.B.org_sample_freq = 0x05; break; + case 176400: + iec_header.B.sample_freq = 0x0C; + iec_header.B.org_sample_freq = 0x03; + break; case 192000: iec_header.B.sample_freq = 0x0E; iec_header.B.org_sample_freq = 0x01; @@ -421,59 +514,26 @@ static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel, struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data; unsigned int count = frames_to_bytes(runtime, frames); unsigned int pos_bytes = frames_to_bytes(runtime, pos); - - u8 *addr_start; - u8 *addr_end; - u32 *addr_cur; - int subframe_idx, i; - hdmi_audio_dma_data_t hdmi_audio_dma_data; + u32 *hw_buf; + int subframe_idx; u32 pcm_data; - addr_start = rtd->buf + (pos_bytes * rtd->buffer_ratio); - addr_end = addr_start + (frames * rtd->channels * 4); - addr_cur = (u32 *)addr_start; + /* Copy pcm data from userspace and add frame info. Destination is hw_buffer. */ + hw_buf = (u32 *)(rtd->hw_buffer.area + (pos_bytes * rtd->buffer_ratio)); - while ((count > 0) && ((u32)addr_cur < (u32)addr_end)) { + while (count > 0) { for (subframe_idx = 1 ; subframe_idx <= rtd->channels ; subframe_idx++) { - pcm_data = 0; + if (copy_from_user(&pcm_data, buf, rtd->sample_align)) return -EFAULT; + buf += rtd->sample_align; count -= rtd->sample_align; - hdmi_audio_dma_data.U = 0; - iec_header.B.channel = subframe_idx; - - /* fill b (start-of-block) */ - hdmi_audio_dma_data.B.b = (rtd->frame_idx == 0) ? 1 : 0; - - /* fill c (channel status) */ - if (rtd->frame_idx < 42) - hdmi_audio_dma_data.B.c = - (iec_header.U >> rtd->frame_idx) & 0x1; - else - hdmi_audio_dma_data.B.c = 0; - - /* fill p (parity) */ - for (i = 0 ; i < rtd->sample_bits ; i++) - hdmi_audio_dma_data.B.p ^= (pcm_data >> i) & 0x01; - hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.c; - hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.u; - hdmi_audio_dma_data.B.p ^= hdmi_audio_dma_data.B.v; - - /* fill data */ - if (rtd->sample_bits == 16) - hdmi_audio_dma_data.B.data = pcm_data << 8; - else - hdmi_audio_dma_data.B.data = pcm_data; - - *addr_cur = hdmi_audio_dma_data.U; - addr_cur++; + /* Save the header info to the audio dma buffer */ + *hw_buf++ = hdmi_dma_add_frame_info(rtd, pcm_data, subframe_idx); } - - rtd->frame_idx++; - if (rtd->frame_idx == 192) - rtd->frame_idx = 0; + hdmi_dma_incr_frame_idx(rtd); } return 0; @@ -484,7 +544,6 @@ static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, { struct snd_pcm_runtime *runtime = substream->runtime; struct imx_hdmi_dma_runtime_data *rtd = runtime->private_data; - struct snd_dma_buffer *dma_buffer = &substream->dma_buffer; rtd->buffer_bytes = params_buffer_bytes(params); rtd->periods = params_periods(params); @@ -495,7 +554,6 @@ static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, rtd->offset = 0; rtd->period_time = HZ / (params_rate(params) / params_period_size(params)); - rtd->buf = (unsigned int *)dma_buffer->area; switch (rtd->format) { case SNDRV_PCM_FORMAT_S16_LE: @@ -520,7 +578,7 @@ static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); hdmi_dma_configure_dma(rtd->channels); - hdmi_dma_set_addr(dma_buffer->addr, rtd->dma_period_bytes); + hdmi_dma_set_addr(rtd->hw_buffer.addr, rtd->dma_period_bytes); dumprtd(rtd); @@ -539,6 +597,12 @@ static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: rtd->frame_idx = 0; + if (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) { + rtd->appl_bytes = frames_to_bytes(runtime, + runtime->control->appl_ptr); + + hdmi_dma_mmap_copy(substream, 0, rtd->appl_bytes); + } dumpregs(); hdmi_dma_irq_mask(0); hdmi_dma_start(); @@ -569,6 +633,8 @@ static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream) static struct snd_pcm_hardware snd_imx_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = MXC_HDMI_FORMATS_PLAYBACK, @@ -686,9 +752,12 @@ static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; - size_t size = HDMI_DMA_BUF_SIZE; + struct snd_dma_buffer *hw_buffer = &hdmi_dma_priv->hw_buffer; - buf->area = dma_alloc_writecombine(pcm->card->dev, size, + /* The 'dma_buffer' is the buffer alsa knows about. + * It contains only raw audio. */ + buf->area = dma_alloc_writecombine(pcm->card->dev, + HDMI_DMA_BUF_SIZE / 2, &buf->addr, GFP_KERNEL); if (!buf->area) return -ENOMEM; @@ -696,10 +765,19 @@ static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; - buf->bytes = size; + buf->bytes = HDMI_DMA_BUF_SIZE / 2; hdmi_dma_priv->tx_substream = substream; + /* For mmap access, isr will copy from the dma_buffer to the hw_buffer */ + hw_buffer->area = dma_alloc_writecombine(pcm->card->dev, + HDMI_DMA_BUF_SIZE, + &hw_buffer->addr, GFP_KERNEL); + if (!hw_buffer->area) + return -ENOMEM; + + hw_buffer->bytes = HDMI_DMA_BUF_SIZE; + return 0; } @@ -730,6 +808,7 @@ static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm) struct snd_dma_buffer *buf; int stream; + /* free each dma_buffer */ for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; if (!substream) @@ -743,6 +822,14 @@ static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm) buf->area, buf->addr); buf->area = NULL; } + + /* free the hw_buffer */ + buf = &hdmi_dma_priv->hw_buffer; + if (buf->area) { + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } } static struct snd_soc_platform_driver imx_soc_platform_mx2 = { |