diff options
author | Iliyan Malchev <malchev@google.com> | 2010-09-08 18:23:45 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2012-03-21 22:11:24 -0700 |
commit | 09db0a5b30cf3a8bb128e68c8459982f3e736a72 (patch) | |
tree | ac224f2d15bd267200061fd28cbda77b6beb725c /arch/arm/mach-tegra/tegra_i2s_audio.c | |
parent | c48563db430b4addaf62dd3ef3a8d9bbccb6b64b (diff) |
enable tegra_i2s_audio
Change-Id: I0b6bfba1f2084d5d05929c2066a49a6c7413c54a
Signed-off-by: Iliyan Malchev <malchev@google.com>
Rebase-Id: Rd162366495556cc538c735ebbd23870b7e824890
Diffstat (limited to 'arch/arm/mach-tegra/tegra_i2s_audio.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra_i2s_audio.c | 218 |
1 files changed, 117 insertions, 101 deletions
diff --git a/arch/arm/mach-tegra/tegra_i2s_audio.c b/arch/arm/mach-tegra/tegra_i2s_audio.c index 395b33e4b894..0df7ef033fe5 100644 --- a/arch/arm/mach-tegra/tegra_i2s_audio.c +++ b/arch/arm/mach-tegra/tegra_i2s_audio.c @@ -51,6 +51,12 @@ #include "clock.h" +#define PCM_BUFFER_MAX_SIZE_ORDER (PAGE_SHIFT + 2) +#define PCM_BUFFER_DMA_CHUNK_SIZE_ORDER PAGE_SHIFT +#define PCM_BUFFER_THRESHOLD_ORDER (PCM_BUFFER_MAX_SIZE_ORDER - 1) +#define PCM_DMA_CHUNK_MIN_SIZE_ORDER 3 + +#define PCM_IN_BUFFER_PADDING (1<<6) /* bytes */ /* per stream (input/output) */ struct audio_stream { @@ -63,6 +69,7 @@ struct audio_stream { dma_addr_t buf_phys; struct kfifo fifo; struct completion fifo_completion; + struct scatterlist sg; unsigned errors; @@ -465,12 +472,6 @@ static inline u32 i2s_get_fifo_full_empty_count(unsigned long base, int fifo) return val & I2S_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK; } -#define PCM_IN_BUFFER_PADDING (1<<6) /* bytes */ -#define PCM_BUFFER_MAX_SIZE_ORDER (PAGE_SHIFT + 2) -#define PCM_BUFFER_DMA_CHUNK_SIZE_ORDER (PCM_BUFFER_MAX_SIZE_ORDER - 1) -#define PCM_BUFFER_THRESHOLD_ORDER PCM_BUFFER_DMA_CHUNK_SIZE_ORDER -#define PCM_DMA_CHUNK_MIN_SIZE_ORDER 3 - static int init_stream_buffer(struct audio_stream *, struct tegra_audio_buf_config *cfg, unsigned); @@ -690,7 +691,8 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req) aos->errors++; } - kfifo_skip(&aos->fifo, count); + kfifo_dma_out_finish(&aos->fifo, count); + dma_unmap_sg(NULL, &aos->sg, 1, DMA_TO_DEVICE); if (kfifo_avail(&aos->fifo) >= threshold_size(aos) && !completion_done(&aos->fifo_completion)) { @@ -727,7 +729,8 @@ static void dma_rx_complete_callback(struct tegra_dma_req *req) count, kfifo_avail(&ais->fifo)); BUG_ON(kfifo_avail(&ais->fifo) < count); - __kfifo_add_in(&ais->fifo, count); + kfifo_dma_in_finish(&ais->fifo, count); + dma_unmap_sg(NULL, &ais->sg, 1, DMA_FROM_DEVICE); if (kfifo_avail(&ais->fifo) <= threshold_size(ais) && !completion_done(&ais->fifo_completion)) { @@ -800,19 +803,21 @@ static int resume_dma_playback(struct audio_stream *aos) struct audio_driver_state *ads = ads_from_out(aos); struct tegra_dma_req *req = &aos->dma_req; - unsigned out, in; - - out = __kfifo_off(&aos->fifo, aos->fifo.out); - in = __kfifo_off(&aos->fifo, aos->fifo.in); + if (aos->dma_has_it) { + pr_debug("%s: playback already in progress\n", __func__); + return -EALREADY; + } + rc = kfifo_dma_out_prepare(&aos->fifo, &aos->sg, + 1, kfifo_len(&aos->fifo)); /* stop_playback_if_necessary() already checks to see if the fifo is * empty. */ - BUG_ON(!kfifo_len(&aos->fifo)); - - if (aos->dma_has_it) { - pr_debug("%s: playback already in progress\n", __func__); - return 0; + BUG_ON(!rc); + rc = dma_map_sg(NULL, &aos->sg, 1, DMA_TO_DEVICE); + if (rc < 0) { + pr_err("%s: could not map dma memory: %d\n", __func__, rc); + return rc; } #if 0 @@ -821,20 +826,17 @@ static int resume_dma_playback(struct audio_stream *aos) i2s_fifo_set_attention_level(ads->i2s_base, I2S_FIFO_TX, aos->i2s_fifo_atn_level); - req->source_addr = aos->buf_phys + out; - if (out < in) - req->size = in - out; - else - req->size = kfifo_size(&aos->fifo) - out; + req->source_addr = sg_dma_address(&aos->sg); + req->size = sg_dma_len(&aos->sg); dma_sync_single_for_device(NULL, - aos->buf_phys + out, req->size, DMA_TO_DEVICE); + req->source_addr, req->size, DMA_TO_DEVICE); /* Don't send all the data yet. */ if (req->size > chunk_size(aos)) req->size = chunk_size(aos); - pr_debug("%s resume playback (%d in fifo, writing %d, in %d out %d)\n", - __func__, kfifo_len(&aos->fifo), req->size, in, out); + pr_debug("%s resume playback (%d in fifo, writing %d)\n", + __func__, kfifo_len(&aos->fifo), req->size); i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1); @@ -871,16 +873,10 @@ static void stop_dma_playback(struct audio_stream *aos) /* Called with ais->dma_req_lock taken. */ static int resume_dma_recording(struct audio_stream *ais) { + int rc; struct audio_driver_state *ads = ads_from_in(ais); struct tegra_dma_req *req = &ais->dma_req; - unsigned out, in; - - out = __kfifo_off(&ais->fifo, ais->fifo.out); - in = __kfifo_off(&ais->fifo, ais->fifo.in); - - pr_debug("%s in %d out %d\n", __func__, in, out); - BUG_ON(kfifo_is_full(&ais->fifo)); if (ais->dma_has_it) { @@ -888,21 +884,24 @@ static int resume_dma_recording(struct audio_stream *ais) return 0; } - req->dest_addr = ais->buf_phys + in; - if (out <= in) - req->size = kfifo_size(&ais->fifo) - in; - else - req->size = out - in; - /* Don't send all the data yet. */ if (req->size > chunk_size(ais)) req->size = chunk_size(ais); + rc = kfifo_dma_in_prepare(&ais->fifo, &ais->sg, 1, + kfifo_avail(&ais->fifo)); + BUG_ON(!rc); + rc = dma_map_sg(NULL, &ais->sg, 1, DMA_FROM_DEVICE); + if (rc < 0) { + pr_err("%s: coult not map dma for recording: %d\n", + __func__, rc); + return rc; + } - req->size = round_down(req->size, 4); + req->dest_addr = sg_dma_address(&ais->sg); + req->size = round_down(sg_dma_len(&ais->sg), 4); if (!req->size) { - pr_err("%s: invalid request size %d (in %d out %d)\n", __func__, - req->size, in, out); + pr_err("%s: invalid request size %d\n", __func__, req->size); return -EIO; } @@ -1468,22 +1467,21 @@ static int downsample(const s16 *in, int in_len, } static ssize_t __downsample_to_user(struct audio_driver_state *ads, - void __user *buf, unsigned int off, - int src_size, - int dst_size, + void __user *dst, int dst_size, + void *src, int src_size, int *num_consumed) { int bytes_ds; pr_debug("%s\n", __func__); - bytes_ds = downsample(ads->in.buffer + off, src_size / sizeof(s16), - ads->in.buffer + off, dst_size / sizeof(s16), + bytes_ds = downsample(src, src_size / sizeof(s16), + src, dst_size / sizeof(s16), num_consumed, ads->in_divs, ads->in_divs_len, ads->in_config.stereo) * sizeof(s16); - if (copy_to_user(buf, ads->in.buffer + off, bytes_ds)) { + if (copy_to_user(dst, src, bytes_ds)) { pr_err("%s: error copying %d bytes to user\n", __func__, bytes_ds); return -EFAULT; @@ -1492,8 +1490,6 @@ static ssize_t __downsample_to_user(struct audio_driver_state *ads, *num_consumed *= sizeof(s16); BUG_ON(*num_consumed > src_size); - kfifo_skip(&ads->in.fifo, *num_consumed); - pr_debug("%s: generated %d, skipped %d, original in fifo %d\n", __func__, bytes_ds, *num_consumed, src_size); @@ -1504,75 +1500,94 @@ static ssize_t downsample_to_user(struct audio_driver_state *ads, void __user *buf, size_t size) /* bytes to write to user buffer */ { - unsigned out, in; - int bytes_consumed_from_fifo; - int bytes_ds; - int bytes_till_end; + int i, nr_sg; + int bytes_consumed_from_fifo, bc_now; + int bytes_ds, ds_now; bool take_two = false; - out = __kfifo_off(&ads->in.fifo, ads->in.fifo.out); - in = __kfifo_off(&ads->in.fifo, ads->in.fifo.in); + struct scatterlist sgl[PCM_BUFFER_MAX_SIZE_ORDER - PAGE_SHIFT]; + sg_init_table(sgl, ARRAY_SIZE(sgl)); - pr_debug("%s (size %d out %d in %d)\n", __func__, size, out, in); + if (size == 0) { + pr_debug("%s: user buffer is full\n", __func__); + return 0; + } if (kfifo_is_empty(&ads->in.fifo)) { pr_debug("%s: input fifo is empty\n", __func__); return 0; } - if (size == 0) { - pr_debug("%s: user buffer is full\n", __func__); - return 0; - } + nr_sg = kfifo_dma_out_prepare(&ads->in.fifo, + sgl, ARRAY_SIZE(sgl), + kfifo_len(&ads->in.fifo)); + BUG_ON(!nr_sg); - /* Does the fifo have enough bytes? We need a contiguous stretch of - * data in the fifo (not wrapping around). - */ - if (out < in) { - bytes_ds = __downsample_to_user(ads, buf, out, - in - out, - size, - &bytes_consumed_from_fifo); - pr_debug("%s: (out < in) downsampled (%d req, %d actual)"\ - " -> %d (size %d)\n", __func__, - in - out, bytes_consumed_from_fifo, - bytes_ds, size); - BUG_ON(bytes_ds > size); - return bytes_ds; - } + pr_debug("%s (fifo size %d)\n", __func__, size); - bytes_till_end = kfifo_size(&ads->in.fifo) - out; + bytes_ds = 0; + bytes_consumed_from_fifo = 0; + for (bytes_ds = 0, i = 0; i < nr_sg; i++) { + BUG_ON(!sgl[i].length); again: - bytes_ds = __downsample_to_user(ads, buf, out, - bytes_till_end, - size, - &bytes_consumed_from_fifo); - pr_debug("%s: (out > in) downsampled (req %d act %d size %d) -> %d\n", - __func__, bytes_till_end, bytes_consumed_from_fifo, - bytes_ds, size); - BUG_ON(bytes_ds > size); - - if (!bytes_ds) { - BUG_ON(take_two); - take_two = true; - - if (in < PCM_IN_BUFFER_PADDING) { - pr_debug("%s: not enough data till end of fifo\n", - __func__); - return 0; - } + ds_now = __downsample_to_user(ads, + buf, size, + sg_virt(&sgl[i]), sgl[i].length, + &bc_now); + + if (!ds_now && !sg_is_last(sgl + i)) { + + BUG_ON(bc_now); + BUG_ON(take_two); + take_two = true; + + /* The assumption is that this sgl entry is at the end + * of the fifo, and there isn't enough space till the + * end of the fifo for at least one target sample to be + * generated. When this happens, we copy enough bytes + * from the next sgl entry to the end of the buffer of + * the current one, knowing that the copied bytes will + * cause the fifo to wrap around. We adjust the next + * entry, and continue with the loop. + */ + + BUG_ON(sg_virt(&sgl[i]) + sgl[i].length != + ads->in.buffer + kfifo_size(&ads->in.fifo)); + + if (sgl[i + 1].length < PCM_IN_BUFFER_PADDING) { + pr_debug("%s: not enough data till end of fifo\n", + __func__); + return 0; + } - pr_debug("%s: adding padding to fifo\n", __func__); + memcpy(sg_virt(&sgl[i]) + sgl[i].length, + sg_virt(&sgl[i + 1]), + PCM_IN_BUFFER_PADDING); + sgl[i].length += PCM_IN_BUFFER_PADDING; - memcpy(ads->in.buffer + buf_size(&ads->in), - ads->in.buffer, - PCM_IN_BUFFER_PADDING); - bytes_till_end += PCM_IN_BUFFER_PADDING; - pr_debug("%s: take two\n", __func__); - goto again; + sg_set_buf(&sgl[i + 1], + sg_virt(&sgl[i + 1]) + PCM_IN_BUFFER_PADDING, + sgl[i + 1].length - PCM_IN_BUFFER_PADDING); + + goto again; + } + + bytes_ds += ds_now; + buf += ds_now; + BUG_ON(ds_now > size); + size -= ds_now; + bytes_consumed_from_fifo += bc_now; + pr_debug("%s: downsampled (%d req, %d actual)" \ + " -> total ds %d (size %d)\n", __func__, + sgl[i].length, bytes_consumed_from_fifo, + bytes_ds, size); + if (sg_is_last(sgl + i)) + break; } + kfifo_dma_out_finish(&ads->in.fifo, bytes_consumed_from_fifo); + return bytes_ds; } @@ -1820,6 +1835,7 @@ static int init_stream_buffer(struct audio_stream *s, } kfifo_init(&s->fifo, s->buffer, 1 << cfg->size); + sg_init_table(&s->sg, 1); return 0; } |