diff options
author | Iliyan Malchev <malchev@google.com> | 2010-08-06 15:41:26 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2012-03-21 22:10:56 -0700 |
commit | 6ded470d6a9d70f08a1a8eadfd7887875a2711a4 (patch) | |
tree | fbe7c8d0f2d7b657a278420d8b7a10c80e10a5dc /arch/arm/mach-tegra/tegra_i2s_audio.c | |
parent | b8321ca49e63a89350a059f87de2c22bbda470bd (diff) |
[ARM] tegra: audio_i2s_audio: clean up & support for recording audio
-- add audio_in_stream (identical to audio_out_stream, may merge them later)
-- add support for DMA and PIO recording
-- add ioctls for /dev/audio<n>_in to start and stop recording
Signed-off-by: Iliyan Malchev <malchev@google.com>
Rebase-Id: R1bed311d1278b267646c4e732d7f076df3ded5f1
Diffstat (limited to 'arch/arm/mach-tegra/tegra_i2s_audio.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra_i2s_audio.c | 611 |
1 files changed, 539 insertions, 72 deletions
diff --git a/arch/arm/mach-tegra/tegra_i2s_audio.c b/arch/arm/mach-tegra/tegra_i2s_audio.c index 84928e667884..cb3a33e69dd7 100644 --- a/arch/arm/mach-tegra/tegra_i2s_audio.c +++ b/arch/arm/mach-tegra/tegra_i2s_audio.c @@ -38,6 +38,8 @@ #include <linux/platform_device.h> #include <linux/io.h> +#include <linux/tegra_audio.h> + #include <mach/dma.h> #include <mach/iomap.h> #include <mach/i2s.h> @@ -51,15 +53,30 @@ struct audio_out_stream { int opened; struct mutex lock; - bool playback; + bool active; spinlock_t pcm_out_lock; void *buffer; dma_addr_t buf_phys; struct kfifo fifo; struct completion fifo_completion; - struct tegra_dma_channel *tx_dma; - struct tegra_dma_req tx_dma_req; + struct tegra_dma_channel *dma_chan; + struct tegra_dma_req dma_req; +}; + +struct audio_in_stream { + int opened; + struct mutex lock; + + bool active; + spinlock_t pcm_in_lock; + void *buffer; + dma_addr_t buf_phys; + struct kfifo fifo; + struct completion fifo_completion; + + struct tegra_dma_channel *dma_chan; + struct tegra_dma_req dma_req; }; struct i2s_pio_stats { @@ -84,18 +101,31 @@ struct audio_driver_state { int irq; /* for pio mode */ struct i2s_pio_stats pio_stats; + bool recording_cancelled; struct miscdevice misc_out; struct audio_out_stream out; struct miscdevice misc_in; -/* struct audio_in_stream in; */ + struct audio_in_stream in; }; -static inline struct audio_driver_state *ads_from_misc(struct file *file) +static inline struct audio_driver_state *ads_from_misc_out(struct file *file) +{ + struct miscdevice *m = file->private_data; + struct audio_driver_state *ads = + container_of(m, struct audio_driver_state, misc_out); + BUG_ON(!ads); + return ads; +} + +static inline struct audio_driver_state *ads_from_misc_in(struct file *file) { struct miscdevice *m = file->private_data; - return container_of(m, struct audio_driver_state, misc_out); + struct audio_driver_state *ads = + container_of(m, struct audio_driver_state, misc_in); + BUG_ON(!ads); + return ads; } static inline struct audio_driver_state *ads_from_out( @@ -104,6 +134,12 @@ static inline struct audio_driver_state *ads_from_out( return container_of(aos, struct audio_driver_state, out); } +static inline struct audio_driver_state *ads_from_in( + struct audio_in_stream *ais) +{ + return container_of(ais, struct audio_driver_state, in); +} + #define I2S_I2S_FIFO_TX_BUSY I2S_I2S_STATUS_FIFO1_BSY #define I2S_I2S_FIFO_TX_QS I2S_I2S_STATUS_QS_FIFO1 #define I2S_I2S_FIFO_TX_ERR I2S_I2S_STATUS_FIFO1_ERR @@ -209,6 +245,14 @@ static void i2s_fifo_enable(unsigned long base, int fifo, int on) i2s_writel(base, val, I2S_I2S_CTRL_0); } +static bool i2s_is_fifo_enabled(unsigned long base, int fifo) +{ + u32 val = i2s_readl(base, I2S_I2S_CTRL_0); + if (!fifo) + return !!(val & I2S_I2S_CTRL_FIFO1_ENABLE); + return !!(val & I2S_I2S_CTRL_FIFO2_ENABLE); +} + static void i2s_fifo_clear(unsigned long base, int fifo) { u32 val = i2s_readl(base, I2S_I2S_FIFO_SCR_0); @@ -382,53 +426,64 @@ static inline u32 i2s_get_fifo_full_empty_count(unsigned long base, int fifo) #define PCM_OUT_DMA_CHUNK (PAGE_SIZE) #define PCM_OUT_THRESHOLD (PAGE_SIZE*2) -#define PLAYBACK_STARTED true -#define PLAYBACK_STOPPED false +#define PCM_IN_BUFFER_SIZE (PAGE_SIZE*4) +#define PCM_IN_DMA_CHUNK (PAGE_SIZE) +#define PCM_IN_THRESHOLD (PAGE_SIZE*2) static int setup_dma(struct audio_driver_state *); static void tear_down_dma(struct audio_driver_state *); -static void start_dma_playback(struct audio_out_stream *); +static int start_dma_playback(struct audio_out_stream *); static void stop_dma_playback(struct audio_out_stream *); +static int start_dma_recording(struct audio_in_stream *); +static int resume_dma_recording(struct audio_in_stream *); +static void stop_dma_recording(struct audio_in_stream *); static int setup_pio(struct audio_driver_state *); static void tear_down_pio(struct audio_driver_state *); -static void start_pio_playback(struct audio_out_stream *); +static int start_pio_playback(struct audio_out_stream *); static void stop_pio_playback(struct audio_out_stream *); +static int start_pio_recording(struct audio_in_stream *); +static void stop_pio_recording(struct audio_in_stream *); struct sound_ops { int (*setup)(struct audio_driver_state *); void (*tear_down)(struct audio_driver_state *); - void (*start_playback)(struct audio_out_stream *); + int (*start_playback)(struct audio_out_stream *); void (*stop_playback)(struct audio_out_stream *); + int (*start_recording)(struct audio_in_stream *); + void (*stop_recording)(struct audio_in_stream *); }; static const struct sound_ops dma_sound_ops = { .setup = setup_dma, .tear_down = tear_down_dma, .start_playback = start_dma_playback, - .stop_playback = stop_dma_playback + .stop_playback = stop_dma_playback, + .start_recording = start_dma_recording, + .stop_recording = stop_dma_recording, }; static const struct sound_ops pio_sound_ops = { .setup = setup_pio, .tear_down = tear_down_pio, .start_playback = start_pio_playback, - .stop_playback = stop_pio_playback + .stop_playback = stop_pio_playback, + .start_recording = start_pio_recording, + .stop_recording = stop_pio_recording, }; static const struct sound_ops *sound_ops = &dma_sound_ops; static void start_playback(struct audio_out_stream *aos) { - sound_ops->start_playback(aos); - aos->playback = PLAYBACK_STARTED; + aos->active = !sound_ops->start_playback(aos); } static void start_playback_if_necessary(struct audio_out_stream *aos) { unsigned long flags; spin_lock_irqsave(&aos->pcm_out_lock, flags); - if (aos->playback == PLAYBACK_STOPPED) { + if (!aos->active) { pr_debug("%s: starting playback\n", __func__); start_playback(aos); } else @@ -436,10 +491,33 @@ static void start_playback_if_necessary(struct audio_out_stream *aos) spin_unlock_irqrestore(&aos->pcm_out_lock, flags); } +static void start_recording(struct audio_in_stream *ais) +{ + ais->active = !sound_ops->start_recording(ais); +} + +static void start_recording_if_necessary(struct audio_in_stream *ais) +{ + unsigned long flags; + spin_lock_irqsave(&ais->pcm_in_lock, flags); + if (!ais->active) { + pr_info("%s: starting recording\n", __func__); + start_recording(ais); + } else + pr_debug("%s: recording already started\n", __func__); + spin_unlock_irqrestore(&ais->pcm_in_lock, flags); +} + static void stop_playback(struct audio_out_stream *aos) { - aos->playback = PLAYBACK_STOPPED; sound_ops->stop_playback(aos); + aos->active = false; +} + +static void stop_recording(struct audio_in_stream *ais) +{ + sound_ops->stop_recording(ais); + ais->active = false; } static bool stop_playback_if_necessary(struct audio_out_stream *aos) @@ -456,6 +534,20 @@ static bool stop_playback_if_necessary(struct audio_out_stream *aos) return false; } +static bool stop_recording_if_necessary(struct audio_in_stream *ais) +{ + unsigned long flags; + spin_lock_irqsave(&ais->pcm_in_lock, flags); + if (kfifo_is_full(&ais->fifo)) { + stop_recording(ais); + spin_unlock_irqrestore(&ais->pcm_in_lock, flags); + return true; + } + spin_unlock_irqrestore(&ais->pcm_in_lock, flags); + + return false; +} + static void toggle_dma(struct audio_driver_state *ads) { pr_info("%s: %s\n", __func__, ads->using_dma ? "pio" : "dma"); @@ -467,42 +559,79 @@ static void toggle_dma(struct audio_driver_state *ads) /* DMA */ -static void resume_dma_playback(struct audio_out_stream *aos); +static int resume_dma_playback(struct audio_out_stream *aos); static void setup_dma_tx_request(struct tegra_dma_req *req, struct audio_out_stream *aos); +static void setup_dma_rx_request(struct tegra_dma_req *req, + struct audio_in_stream *ais); + static int setup_dma(struct audio_driver_state *ads) { + int rc; pr_info("%s\n", __func__); - ads->out.buf_phys = dma_map_single(NULL, ads->out.buffer, + /* setup audio playback */ + ads->out.buf_phys = dma_map_single(&ads->pdev->dev, ads->out.buffer, PCM_OUT_BUFFER_SIZE, DMA_TO_DEVICE); BUG_ON(!ads->out.buf_phys); - - /* setup audio playback */ - setup_dma_tx_request(&ads->out.tx_dma_req, &ads->out); - ads->out.tx_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); - if (IS_ERR(ads->out.tx_dma)) { - pr_err("%s: could not allocate DMA channel for I2S: %ld\n", - __func__, PTR_ERR(ads->out.tx_dma)); - return PTR_ERR(ads->out.tx_dma); + setup_dma_tx_request(&ads->out.dma_req, &ads->out); + ads->out.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); + if (IS_ERR(ads->out.dma_chan)) { + pr_err("%s: could not allocate output I2S DMA channel: %ld\n", + __func__, PTR_ERR(ads->out.dma_chan)); + rc = PTR_ERR(ads->out.dma_chan); + goto fail_tx; } - /* TODO: setup recording */ + /* setup audio recording */ + ads->in.buf_phys = dma_map_single(&ads->pdev->dev, ads->in.buffer, + PCM_IN_BUFFER_SIZE, DMA_FROM_DEVICE); + BUG_ON(!ads->in.buf_phys); + setup_dma_rx_request(&ads->in.dma_req, &ads->in); + ads->in.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); + if (IS_ERR(ads->in.dma_chan)) { + pr_err("%s: could not allocate input I2S DMA channel: %ld\n", + __func__, PTR_ERR(ads->in.dma_chan)); + rc = PTR_ERR(ads->in.dma_chan); + goto fail_rx; + } return 0; + +fail_rx: + dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys, + PCM_IN_BUFFER_SIZE, DMA_FROM_DEVICE); + tegra_dma_free_channel(ads->in.dma_chan); + ads->in.dma_chan = 0; + +fail_tx: + dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys, + PCM_OUT_BUFFER_SIZE, DMA_TO_DEVICE); + tegra_dma_free_channel(ads->out.dma_chan); + ads->out.dma_chan = 0; + + return rc; } static void tear_down_dma(struct audio_driver_state *ads) { pr_info("%s\n", __func__); - tegra_dma_free_channel(ads->out.tx_dma); - ads->out.tx_dma = NULL; + + tegra_dma_free_channel(ads->out.dma_chan); + ads->out.dma_chan = NULL; dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys, PCM_OUT_BUFFER_SIZE, DMA_TO_DEVICE); ads->out.buf_phys = 0; + + tegra_dma_free_channel(ads->in.dma_chan); + ads->in.dma_chan = NULL; + dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys, + PCM_IN_BUFFER_SIZE, + DMA_FROM_DEVICE); + ads->in.buf_phys = 0; } static void dma_tx_complete_callback(struct tegra_dma_req *req) @@ -526,6 +655,40 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req) resume_dma_playback(aos); } +static void dma_rx_complete_threshold(struct tegra_dma_req *req) +{ + pr_info("%s\n", __func__); +} + +static void dma_rx_complete_callback(struct tegra_dma_req *req) +{ + struct audio_in_stream *ais = req->dev; + int count = req->bytes_transferred; + + pr_debug("%s bytes transferred %d\n", __func__, count); + + __kfifo_add_in(&ais->fifo, count); + + if (kfifo_avail(&ais->fifo) < PCM_IN_THRESHOLD && + !completion_done(&ais->fifo_completion)) { + pr_debug("%s: complete\n", __func__); + complete(&ais->fifo_completion); + } + + if (!ais->active) { + pr_warn("%s: recording has been stopped\n", __func__); + return; + } + + if (stop_recording_if_necessary(ais)) { + pr_warn("%s: paused recording (input fifo full)\n", + __func__); + return; + } + + resume_dma_recording(ais); +} + static void setup_dma_tx_request(struct tegra_dma_req *req, struct audio_out_stream *aos) { @@ -533,7 +696,6 @@ static void setup_dma_tx_request(struct tegra_dma_req *req, memset(req, 0, sizeof(*req)); - /* FIXME: need to set instance? */ req->complete = dma_tx_complete_callback; req->dev = aos; req->to_memory = false; @@ -545,10 +707,29 @@ static void setup_dma_tx_request(struct tegra_dma_req *req, req->req_sel = ads->dma_req_sel; } -static void resume_dma_playback(struct audio_out_stream *aos) +static void setup_dma_rx_request(struct tegra_dma_req *req, + struct audio_in_stream *ais) +{ + struct audio_driver_state *ads = ads_from_in(ais); + + memset(req, 0, sizeof(*req)); + + req->complete = dma_rx_complete_callback; + req->threshold = dma_rx_complete_threshold; + req->dev = ais; + req->to_memory = true; + req->source_addr = i2s_get_fifo_phy_base(ads->i2s_phys, I2S_FIFO_RX); + req->source_wrap = 4; + req->source_bus_width = 16; + req->dest_bus_width = 32; + req->dest_wrap = 0; + req->req_sel = ads->dma_req_sel; +} + +static int resume_dma_playback(struct audio_out_stream *aos) { struct audio_driver_state *ads = ads_from_out(aos); - struct tegra_dma_req *req = &aos->tx_dma_req; + struct tegra_dma_req *req = &aos->dma_req; unsigned out = __kfifo_off(&aos->fifo, aos->fifo.out); unsigned in = __kfifo_off(&aos->fifo, aos->fifo.in); @@ -573,26 +754,77 @@ static void resume_dma_playback(struct audio_out_stream *aos) i2s_fifo_set_attention_level(ads->i2s_base, I2S_FIFO_TX, I2S_FIFO_ATN_LVL_FOUR_SLOTS); i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1); - tegra_dma_enqueue_req(aos->tx_dma, req); + + return tegra_dma_enqueue_req(aos->dma_chan, req); } -static void start_dma_playback(struct audio_out_stream *aos) +static int start_dma_playback(struct audio_out_stream *aos) { - resume_dma_playback(aos); + return resume_dma_playback(aos); } static void stop_dma_playback(struct audio_out_stream *aos) { struct audio_driver_state *ads = ads_from_out(aos); pr_debug("%s\n", __func__); + tegra_dma_dequeue_req(ads->out.dma_chan, &ads->out.dma_req); i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 0); while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY) - /* spin */; + pr_info("%s: spin\n", __func__); } -/* PIO (non-DMA) */ +static int resume_dma_recording(struct audio_in_stream *ais) +{ + struct audio_driver_state *ads = ads_from_in(ais); + struct tegra_dma_req *req = &ais->dma_req; + + unsigned out = __kfifo_off(&ais->fifo, ais->fifo.out); + unsigned in = __kfifo_off(&ais->fifo, ais->fifo.in); -static irqreturn_t i2s_interrupt(int irq, void *data); + pr_debug("%s\n", __func__); + + 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 > PCM_OUT_DMA_CHUNK) + req->size = PCM_OUT_DMA_CHUNK; + + dma_sync_single_for_device(NULL, + req->dest_addr, req->size, DMA_FROM_DEVICE); + + if (tegra_dma_enqueue_req(ais->dma_chan, &ais->dma_req)) { + pr_err("%s: could not enqueue RX DMA req\n", __func__); + return -EINVAL; + } + i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX); + i2s_fifo_set_attention_level(ads->i2s_base, + I2S_FIFO_RX, I2S_FIFO_ATN_LVL_TWELVE_SLOTS); + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1); + return 0; +} + +static int start_dma_recording(struct audio_in_stream *ais) +{ + pr_info("%s\n", __func__); + return resume_dma_recording(ais); +} + +static void stop_dma_recording(struct audio_in_stream *ais) +{ + struct audio_driver_state *ads = ads_from_in(ais); + pr_info("%s\n", __func__); + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 0); + i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX); + while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_RX_BUSY) + pr_debug("%s: spin\n", __func__); + tegra_dma_dequeue_req(ais->dma_chan, &ais->dma_req); +} + +/* PIO (non-DMA) */ static int setup_pio(struct audio_driver_state *ads) { @@ -607,11 +839,11 @@ static void tear_down_pio(struct audio_driver_state *ads) disable_irq(ads->irq); } -static void start_pio_playback(struct audio_out_stream *aos) +static int start_pio_playback(struct audio_out_stream *aos) { struct audio_driver_state *ads = ads_from_out(aos); - pr_debug("%s\n", __func__); + pr_info("%s\n", __func__); i2s_fifo_set_attention_level(ads->i2s_base, I2S_FIFO_TX, I2S_FIFO_ATN_LVL_ONE_SLOT); @@ -619,6 +851,9 @@ static void start_pio_playback(struct audio_out_stream *aos) i2s_set_fifo_irq_on_err(ads->i2s_base, I2S_FIFO_TX, 1); i2s_set_fifo_irq_on_qe(ads->i2s_base, I2S_FIFO_TX, 1); + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1); + + return 0; } static void stop_pio_playback(struct audio_out_stream *aos) @@ -635,10 +870,45 @@ static void stop_pio_playback(struct audio_out_stream *aos) ads->pio_stats.i2s_interrupt_count); pr_info("%s: sent %d\n", __func__, ads->pio_stats.tx_fifo_written); - pr_info("%s: received %d\n", __func__, - ads->pio_stats.rx_fifo_read); pr_info("%s: tx errors %d\n", __func__, ads->pio_stats.tx_fifo_errors); + + memset(&ads->pio_stats, 0, sizeof(ads->pio_stats)); +} + +static int start_pio_recording(struct audio_in_stream *ais) +{ + struct audio_driver_state *ads = ads_from_in(ais); + + pr_info("%s\n", __func__); + + i2s_fifo_set_attention_level(ads->i2s_base, + I2S_FIFO_RX, I2S_FIFO_ATN_LVL_TWELVE_SLOTS); + i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX); + + i2s_set_fifo_irq_on_err(ads->i2s_base, I2S_FIFO_RX, 1); + i2s_set_fifo_irq_on_qe(ads->i2s_base, I2S_FIFO_RX, 1); + + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1); + + return 0; +} + +static void stop_pio_recording(struct audio_in_stream *ais) +{ + struct audio_driver_state *ads = ads_from_in(ais); + + pr_info("%s\n", __func__); + + i2s_set_fifo_irq_on_err(ads->i2s_base, I2S_FIFO_RX, 0); + i2s_set_fifo_irq_on_qe(ads->i2s_base, I2S_FIFO_RX, 0); + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 0); + i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX); + + pr_info("%s: interrupts %d\n", __func__, + ads->pio_stats.i2s_interrupt_count); + pr_info("%s: received %d\n", __func__, + ads->pio_stats.rx_fifo_read); pr_info("%s: rx errors %d\n", __func__, ads->pio_stats.rx_fifo_errors); @@ -671,17 +941,23 @@ static irqreturn_t i2s_interrupt(int irq, void *data) struct audio_out_stream *out = &ads->out; - pr_debug("%s fifo is ready\n", __func__); + if (!i2s_is_fifo_enabled(ads->i2s_base, I2S_FIFO_TX)) { + pr_debug("%s: tx fifo not enabled, skipping\n", + __func__); + goto check_rx; + } + + pr_debug("%s tx fifo is ready\n", __func__); if (kfifo_avail(&out->fifo) > PCM_OUT_THRESHOLD && !completion_done(&out->fifo_completion)) { - pr_debug("%s: complete (%d avail)\n", __func__, + pr_debug("%s: tx complete (%d avail)\n", __func__, kfifo_avail(&out->fifo)); complete(&out->fifo_completion); } if (stop_playback_if_necessary(out)) - goto done; + goto check_rx; empty = i2s_get_fifo_full_empty_count(ads->i2s_base, I2S_FIFO_TX); @@ -709,15 +985,47 @@ static irqreturn_t i2s_interrupt(int irq, void *data) } } +check_rx: if (status & I2S_I2S_FIFO_RX_QS) { - int read = 0; - while (i2s_get_fifo_full_empty_count(ads->i2s_base, - I2S_FIFO_RX)) { - i2s_fifo_read(ads->i2s_base, I2S_FIFO_RX); - read++; + int nr; + int full; + + struct audio_in_stream *in = &ads->in; + + if (!i2s_is_fifo_enabled(ads->i2s_base, I2S_FIFO_RX)) { + pr_debug("%s: rx fifo not enabled, skipping\n", + __func__); + goto done; } - ads->pio_stats.rx_fifo_read += read; - pr_debug("%s: rx fifo empty (%d read)\n", __func__, read); + + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 0); + + full = i2s_get_fifo_full_empty_count(ads->i2s_base, + I2S_FIFO_RX); + + pr_debug("%s rx fifo is ready (%d samples)\n", __func__, full); + + nr = full; + while (nr--) { + u16 sample = i2s_fifo_read(ads->i2s_base, I2S_FIFO_RX); + kfifo_in(&in->fifo, &sample, sizeof(sample)); + } + + ads->pio_stats.rx_fifo_read += full * sizeof(u16); + + if (kfifo_avail(&in->fifo) < PCM_IN_THRESHOLD && + !completion_done(&in->fifo_completion)) { + pr_debug("%s: rx complete (%d avail)\n", __func__, + kfifo_avail(&in->fifo)); + complete(&in->fifo_completion); + } + + if (!in->active) { + pr_info("%s: stopping recording\n", __func__); + goto done; + } + + i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1); } done: @@ -731,7 +1039,7 @@ static ssize_t tegra_audio_write(struct file *file, ssize_t rc, total = 0; unsigned nw = 0; - struct audio_driver_state *ads = ads_from_misc(file); + struct audio_driver_state *ads = ads_from_misc_out(file); mutex_lock(&ads->out.lock); @@ -739,6 +1047,7 @@ static ssize_t tegra_audio_write(struct file *file, pr_err("%s: user size request %d not aligned to 4\n", __func__, size); rc = -EINVAL; + goto done; } pr_debug("%s: write %d bytes, %d available\n", __func__, @@ -776,9 +1085,112 @@ done: return rc; } +static long tegra_audio_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc = 0; + struct audio_driver_state *ads = ads_from_misc_in(file); + struct audio_in_stream *ais = &ads->in; + + mutex_lock(&ais->lock); + + switch (cmd) { + case TEGRA_AUDIO_IN_START: + pr_info("%s: start recording\n", __func__); + ads->recording_cancelled = false; + start_recording_if_necessary(ais); + break; + case TEGRA_AUDIO_IN_STOP: + pr_info("%s: stop recording\n", __func__); + stop_recording(ais); + ads->recording_cancelled = true; + if (!completion_done(&ais->fifo_completion)) { + pr_info("%s: complete\n", __func__); + complete(&ais->fifo_completion); + } + break; + default: + rc = -EINVAL; + } + + mutex_unlock(&ais->lock); + return rc; +} + +ssize_t tegra_audio_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + ssize_t rc, total = 0; + unsigned nr; + + struct audio_driver_state *ads = ads_from_misc_in(file); + + mutex_lock(&ads->in.lock); + + if (!IS_ALIGNED(size, 4)) { + pr_err("%s: user size request %d not aligned to 4\n", + __func__, size); + rc = -EINVAL; + goto done; + } + + pr_debug("%s: read %d bytes, %d available\n", __func__, + size, kfifo_avail(&ads->in.fifo)); + + if (!ads->recording_cancelled) + start_recording_if_necessary(&ads->in); + +again: + if (!ads->in.active && kfifo_is_empty(&ads->in.fifo)) { + pr_info("%s: recording has stopped (read %d bytes)\n", + __func__, total); + rc = total; + *off += total; + goto done; + } + + rc = kfifo_to_user(&ads->in.fifo, buf + total, size - total, &nr); + if (rc < 0) { + pr_err("%s: error copying to user\n", __func__); + goto done; + } + + total += nr; + + pr_debug("%s: copied %d bytes to user, total %d/%d\n", + __func__, nr, total, size); + + if (total < size && ads->in.active) { + pr_debug("%s: sleep (user %d total %d nr %d)\n", __func__, + size, total, nr); + mutex_unlock(&ads->in.lock); + rc = wait_for_completion_interruptible( + &ads->in.fifo_completion); + mutex_lock(&ads->in.lock); + if (rc == -ERESTARTSYS) { + pr_warn("%s: interrupted\n", __func__); + goto done; + } + pr_debug("%s: awake\n", __func__); + goto again; + } + + pr_debug("%s: done reading %d bytes, %d available\n", __func__, + total, kfifo_avail(&ads->in.fifo)); + + rc = total; + *off += total; + +done: + mutex_unlock(&ads->in.lock); + return rc; +} + static int tegra_audio_out_open(struct inode *inode, struct file *file) { - struct audio_driver_state *ads = ads_from_misc(file); + struct audio_driver_state *ads = ads_from_misc_out(file); + + pr_info("%s\n", __func__); mutex_lock(&ads->out.lock); if (!ads->out.opened++) @@ -790,11 +1202,15 @@ static int tegra_audio_out_open(struct inode *inode, struct file *file) static int tegra_audio_out_release(struct inode *inode, struct file *file) { - struct audio_driver_state *ads = ads_from_misc(file); + struct audio_driver_state *ads = ads_from_misc_out(file); + + pr_info("%s\n", __func__); mutex_lock(&ads->out.lock); if (ads->out.opened) ads->out.opened--; + if (!ads->out.opened) + stop_playback(&ads->out); mutex_unlock(&ads->out.lock); return 0; @@ -802,11 +1218,40 @@ static int tegra_audio_out_release(struct inode *inode, struct file *file) static int tegra_audio_in_open(struct inode *inode, struct file *file) { + struct audio_driver_state *ads = ads_from_misc_in(file); + + pr_info("%s\n", __func__); + + mutex_lock(&ads->in.lock); + if (!ads->in.opened++) { + pr_info("%s: resetting fifo\n", __func__); + /* By default, do not start recording when someone reads from + * input device. + */ + ads->recording_cancelled = false; + kfifo_reset(&ads->in.fifo); + } + mutex_unlock(&ads->in.lock); + + pr_info("%s: done\n", __func__); return 0; } static int tegra_audio_in_release(struct inode *inode, struct file *file) { + struct audio_driver_state *ads = ads_from_misc_in(file); + + pr_info("%s\n", __func__); + + mutex_lock(&ads->in.lock); + if (ads->in.opened) + ads->in.opened--; + if (!ads->in.opened) { + pr_info("%s: stop recording\n", __func__); + stop_recording(&ads->in); + } + mutex_unlock(&ads->in.lock); + pr_info("%s: done\n", __func__); return 0; } @@ -877,10 +1322,11 @@ ssize_t debugfs_write(struct file *file, return -EINVAL; } - /* FIXME: lock the input stream here as well. */ mutex_lock(&ads->out.lock); - if (ads->out.playback == PLAYBACK_STARTED) { - pr_err("%s: playback in progress, try again.\n", __func__); + mutex_lock(&ads->in.lock); + if (ads->out.active || ads->in.active) { + pr_err("%s: playback or recording in progress.\n", __func__); + mutex_unlock(&ads->in.lock); mutex_unlock(&ads->out.lock); return -EBUSY; } @@ -888,6 +1334,7 @@ ssize_t debugfs_write(struct file *file, toggle_dma(ads); else pr_info("%s: no change\n", __func__); + mutex_unlock(&ads->in.lock); mutex_unlock(&ads->out.lock); return 5; @@ -925,6 +1372,8 @@ static const struct file_operations tegra_audio_out_fops = { static const struct file_operations tegra_audio_in_fops = { .owner = THIS_MODULE, .open = tegra_audio_in_open, + .read = tegra_audio_read, + .unlocked_ioctl = tegra_audio_in_ioctl, .release = tegra_audio_in_release, }; @@ -1016,28 +1465,47 @@ static int tegra_audio_probe(struct platform_device *pdev) clk_get_rate(i2s_clk)); i2s_set_master(state->i2s_base, state->pdata->master); - i2s_set_fifo_mode(state->i2s_base, FIFO1, 1); /* FIFO1 is TX */ - i2s_set_fifo_mode(state->i2s_base, FIFO2, 0); /* FIFO2 is RX */ + i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_TX, 1); + i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_RX, 0); i2s_set_bit_format(state->i2s_base, state->pdata->mode); i2s_set_bit_size(state->i2s_base, state->pdata->bit_size); i2s_set_fifo_format(state->i2s_base, state->pdata->fifo_fmt); state->out.opened = 0; - state->out.playback = PLAYBACK_STOPPED; + state->out.active = false; mutex_init(&state->out.lock); init_completion(&state->out.fifo_completion); spin_lock_init(&state->out.pcm_out_lock); state->out.buf_phys = 0; - state->out.tx_dma = NULL; + state->out.dma_chan = NULL; + + state->in.opened = 0; + state->in.active = false; + mutex_init(&state->in.lock); + init_completion(&state->in.fifo_completion); + spin_lock_init(&state->in.pcm_in_lock); + state->in.buf_phys = 0; + state->in.dma_chan = NULL; - state->out.buffer = kmalloc(PCM_OUT_BUFFER_SIZE, - GFP_KERNEL | GFP_DMA); + state->out.buffer = kmalloc(PCM_OUT_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); if (!state->out.buffer) { - pr_err("%s: could not allocate buffer\n", __func__); + pr_err("%s: could not allocate output buffer\n", __func__); + return -ENOMEM; + } + + state->in.buffer = kmalloc(PCM_IN_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (!state->in.buffer) { + pr_err("%s: could not allocate input buffer\n", __func__); return -ENOMEM; } + kfifo_init(&state->out.fifo, state->out.buffer, + PCM_OUT_BUFFER_SIZE); + + kfifo_init(&state->in.fifo, state->in.buffer, + PCM_IN_BUFFER_SIZE); + if (request_irq(state->irq, i2s_interrupt, IRQF_DISABLED, state->pdev->name, state) < 0) { pr_err("%s: could not register handler for irq %d\n", @@ -1045,12 +1513,6 @@ static int tegra_audio_probe(struct platform_device *pdev) return -EIO; } - kfifo_init(&state->out.fifo, state->out.buffer, - PCM_OUT_BUFFER_SIZE); - - state->using_dma = state->pdata->dma_on; - sound_ops->setup(state); - memset(&state->misc_out, 0, sizeof(state->misc_out)); state->misc_out.minor = MISC_DYNAMIC_MINOR; state->misc_out.name = kmalloc(sizeof("audio_out") + 1, GFP_KERNEL); @@ -1077,6 +1539,11 @@ static int tegra_audio_probe(struct platform_device *pdev) return -EIO; } + state->using_dma = state->pdata->dma_on; + if (!state->using_dma) + sound_ops = &pio_sound_ops; + sound_ops->setup(state); + setup_tegra_audio_debugfs(state); return 0; |