#include <linux/io.h>
#include <linux/ktime.h>
#include <linux/sysfs.h>
-
+#include <linux/pm_qos_params.h>
+#include <linux/delay.h>
#include <linux/tegra_audio.h>
-
+#include <linux/pm.h>
#include <mach/dma.h>
#include <mach/iomap.h>
#include <mach/i2s.h>
#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 */
+
+#define TEGRA_AUDIO_DSP_NONE 0
+#define TEGRA_AUDIO_DSP_PCM 1
+#define TEGRA_AUDIO_DSP_NETWORK 2
+#define TEGRA_AUDIO_DSP_TDM 3
/* per stream (input/output) */
struct audio_stream {
dma_addr_t buf_phys;
struct kfifo fifo;
struct completion fifo_completion;
+ struct scatterlist sg;
- unsigned errors;
+ struct tegra_audio_error_counts errors;
int i2s_fifo_atn_level;
ktime_t last_dma_ts;
struct tegra_dma_channel *dma_chan;
+ bool stop;
struct completion stop_completion;
spinlock_t dma_req_lock; /* guards dma_has_it */
int dma_has_it;
struct tegra_dma_req dma_req;
+
+ struct pm_qos_request_list pm_qos;
};
struct i2s_pio_stats {
int irq; /* for pio mode */
struct i2s_pio_stats pio_stats;
- bool recording_cancelled;
struct tegra_audio_in_config in_config;
const int *in_divs;
int in_divs_len;
struct miscdevice misc_in;
struct miscdevice misc_in_ctl;
struct audio_stream in;
+
+ /* Control for whole I2S (Data format, etc.) */
+ struct miscdevice misc_ctl;
+ unsigned int bit_format;
};
static inline int buf_size(struct audio_stream *s)
return ads;
}
+static inline struct audio_driver_state *ads_from_misc_ctl(
+ struct file *file)
+{
+ struct miscdevice *m = file->private_data;
+ struct audio_driver_state *ads =
+ container_of(m, struct audio_driver_state,
+ misc_ctl);
+ BUG_ON(!ads);
+ return ads;
+}
+
static inline struct audio_driver_state *ads_from_out(
struct audio_stream *aos)
{
return container_of(ais, struct audio_driver_state, in);
}
+static inline void prevent_suspend(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ pm_qos_update_request(&as->pm_qos, 0);
+}
+
+static inline void allow_suspend(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ pm_qos_update_request(&as->pm_qos, PM_QOS_DEFAULT_VALUE);
+}
+
#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
i2s_writel(base, val, I2S_I2S_CTRL_0);
}
+static int i2s_set_dsp_mode(unsigned long base, unsigned int mode)
+{
+ u32 val;
+ if (mode > TEGRA_AUDIO_DSP_TDM) {
+ pr_err("%s: invalid mode %d.\n", __func__, mode);
+ return -EINVAL;
+ }
+ if (mode == TEGRA_AUDIO_DSP_TDM) {
+ pr_err("TEGRA_AUDIO_DSP_TDM not implemented.\n");
+ return -EINVAL;
+ }
+
+ /* Disable unused modes */
+ if (mode != TEGRA_AUDIO_DSP_PCM) {
+ /* Disable PCM mode */
+ val = i2s_readl(base, I2S_I2S_PCM_CTRL_0);
+ val &= ~(I2S_I2S_PCM_CTRL_TRM_MODE |
+ I2S_I2S_PCM_CTRL_RCV_MODE);
+ i2s_writel(base, val, I2S_I2S_PCM_CTRL_0);
+ }
+ if (mode != TEGRA_AUDIO_DSP_NETWORK) {
+ /* Disable Network mode */
+ val = i2s_readl(base, I2S_I2S_NW_CTRL_0);
+ val &= ~(I2S_I2S_NW_CTRL_TRM_TLPHY_MODE |
+ I2S_I2S_NW_CTRL_RCV_TLPHY_MODE);
+ i2s_writel(base, val, I2S_I2S_NW_CTRL_0);
+ }
+
+ /* Enable the selected mode. */
+ switch (mode) {
+ case TEGRA_AUDIO_DSP_NETWORK:
+ /* Set DSP Network (Telephony) Mode */
+ val = i2s_readl(base, I2S_I2S_NW_CTRL_0);
+ val |= I2S_I2S_NW_CTRL_TRM_TLPHY_MODE |
+ I2S_I2S_NW_CTRL_RCV_TLPHY_MODE;
+ i2s_writel(base, val, I2S_I2S_NW_CTRL_0);
+ break;
+ case TEGRA_AUDIO_DSP_PCM:
+ /* Set DSP PCM Mode */
+ val = i2s_readl(base, I2S_I2S_PCM_CTRL_0);
+ val |= I2S_I2S_PCM_CTRL_TRM_MODE |
+ I2S_I2S_PCM_CTRL_RCV_MODE;
+ i2s_writel(base, val, I2S_I2S_PCM_CTRL_0);
+ break;
+ }
+
+ return 0;
+}
+
static int i2s_set_bit_format(unsigned long base, unsigned fmt)
{
u32 val;
val = i2s_readl(base, I2S_I2S_CTRL_0);
val &= ~I2S_I2S_CTRL_BIT_FORMAT_MASK;
val |= fmt << I2S_BIT_FORMAT_SHIFT;
-
i2s_writel(base, val, I2S_I2S_CTRL_0);
+ /* For DSP format, select DSP PCM mode. */
+ /* PCM mode and Network Mode slot 0 are effectively identical. */
+ if (fmt == I2S_BIT_FORMAT_DSP)
+ i2s_set_dsp_mode(base, TEGRA_AUDIO_DSP_PCM);
+ else
+ i2s_set_dsp_mode(base, TEGRA_AUDIO_DSP_NONE);
+
return 0;
}
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 i2s_configure(struct platform_device *pdev)
+{
+ struct tegra_audio_platform_data *pdata = pdev->dev.platform_data;
+ struct audio_driver_state *state = pdata->driver_data;
+ bool master;
+ struct clk *i2s_clk;
+ int master_clk;
+
+ /* dev_info(&pdev->dev, "%s\n", __func__); */
+
+ if (!state)
+ return -ENOMEM;
+
+ /* disable interrupts from I2S */
+ i2s_enable_fifos(state->i2s_base, 0);
+ i2s_fifo_clear(state->i2s_base, I2S_FIFO_TX);
+ i2s_fifo_clear(state->i2s_base, I2S_FIFO_RX);
+ i2s_set_left_right_control_polarity(state->i2s_base, 0); /* default */
+
+ i2s_clk = clk_get(&pdev->dev, NULL);
+ if (!i2s_clk) {
+ dev_err(&pdev->dev, "%s: could not get i2s clock\n",
+ __func__);
+ return -EIO;
+ }
+
+ master = state->bit_format == TEGRA_AUDIO_BIT_FORMAT_DSP ?
+ state->pdata->dsp_master : state->pdata->i2s_master;
+
+
+ master_clk = state->bit_format == TEGRA_AUDIO_BIT_FORMAT_DSP ?
+ state->pdata->dsp_master_clk :
+ state->pdata->i2s_master_clk;
+#define I2S_CLK_FUDGE_FACTOR 2 /* Todo, fix this! */
+ if (master)
+ i2s_set_channel_bit_count(state->i2s_base, master_clk,
+ clk_get_rate(i2s_clk)*I2S_CLK_FUDGE_FACTOR);
+ i2s_set_master(state->i2s_base, master);
+
+ i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_TX, 1);
+ i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_RX, 0);
+
+ if (state->bit_format == TEGRA_AUDIO_BIT_FORMAT_DSP)
+ i2s_set_bit_format(state->i2s_base, I2S_BIT_FORMAT_DSP);
+ else
+ 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);
+
+ return 0;
+}
static int init_stream_buffer(struct audio_stream *,
struct tegra_audio_buf_config *cfg, unsigned);
pr_debug("%s: starting playback\n", __func__);
rc = sound_ops->start_playback(aos);
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+ if (!rc)
+ prevent_suspend(aos);
return rc;
}
static int start_recording_if_necessary(struct audio_stream *ais)
{
int rc = 0;
+ bool started = false;
unsigned long flags;
- struct audio_driver_state *ads = ads_from_in(ais);
spin_lock_irqsave(&ais->dma_req_lock, flags);
- if (!ads->recording_cancelled && !kfifo_is_full(&ais->fifo)) {
+ if (!ais->stop && !kfifo_is_full(&ais->fifo)) {
pr_debug("%s: starting recording\n", __func__);
rc = sound_ops->start_recording(ais);
+ started = !rc;
}
spin_unlock_irqrestore(&ais->dma_req_lock, flags);
+ if (started)
+ prevent_suspend(ais);
return rc;
}
spin_lock_irqsave(&aos->dma_req_lock, flags);
if (kfifo_is_empty(&aos->fifo)) {
sound_ops->stop_playback(aos);
- if (aos->active)
- aos->errors++;
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+ allow_suspend(aos);
return true;
}
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
static bool stop_recording_if_necessary_nosync(struct audio_stream *ais)
{
- struct audio_driver_state *ads = ads_from_in(ais);
-
- if (ads->recording_cancelled || kfifo_is_full(&ais->fifo)) {
+ if (ais->stop || kfifo_is_full(&ais->fifo)) {
if (kfifo_is_full(&ais->fifo))
- ais->errors++;
+ ais->errors.full_empty++; /* overflow */
sound_ops->stop_recording(ais);
return true;
}
return false;
}
-static bool stop_recording(struct audio_stream *ais)
+/* playback and recording */
+static bool wait_till_stopped(struct audio_stream *as)
{
int rc;
pr_debug("%s: wait for completion\n", __func__);
- rc = wait_for_completion_interruptible(
- &ais->stop_completion);
+ rc = wait_for_completion_interruptible_timeout(
+ &as->stop_completion, HZ);
+ if (!rc)
+ pr_err("%s: wait timed out", __func__);
+ allow_suspend(as);
pr_debug("%s: done: %d\n", __func__, rc);
return true;
}
+/* Ask for playback and recording to stop. The _nosync means that
+ * as->lock has to be locked by the caller.
+ */
+static void request_stop_nosync(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ if (!as->stop) {
+ as->stop = true;
+ wait_till_stopped(as);
+ if (!completion_done(&as->fifo_completion)) {
+ pr_debug("%s: complete\n", __func__);
+ complete(&as->fifo_completion);
+ }
+ }
+ kfifo_reset(&as->fifo);
+ as->active = false; /* applies to recording only */
+ pr_debug("%s: done\n", __func__);
+}
+
static void toggle_dma(struct audio_driver_state *ads)
{
pr_info("%s: %s\n", __func__, ads->using_dma ? "pio" : "dma");
int rc;
pr_info("%s\n", __func__);
- /* setup audio playback */
- ads->out.buf_phys = dma_map_single(&ads->pdev->dev, ads->out.buffer,
- 1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
- BUG_ON(!ads->out.buf_phys);
- setup_dma_tx_request(&ads->out.dma_req, &ads->out);
- ads->out.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
- if (!ads->out.dma_chan) {
- pr_err("%s: could not allocate output I2S DMA channel: %ld\n",
- __func__, PTR_ERR(ads->out.dma_chan));
- rc = -ENODEV;
- goto fail_tx;
- }
-
- /* setup audio recording */
- ads->in.buf_phys = dma_map_single(&ads->pdev->dev, ads->in.buffer,
- 1 << PCM_BUFFER_MAX_SIZE_ORDER,
- 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 (!ads->in.dma_chan) {
- pr_err("%s: could not allocate input I2S DMA channel: %ld\n",
- __func__, PTR_ERR(ads->in.dma_chan));
- rc = -ENODEV;
- goto fail_rx;
+ if ((ads->pdata->mask & TEGRA_AUDIO_ENABLE_TX)) {
+ /* setup audio playback */
+ ads->out.buf_phys = dma_map_single(&ads->pdev->dev,
+ ads->out.buffer,
+ 1 << PCM_BUFFER_MAX_SIZE_ORDER,
+ DMA_TO_DEVICE);
+ BUG_ON(!ads->out.buf_phys);
+ setup_dma_tx_request(&ads->out.dma_req, &ads->out);
+ ads->out.dma_chan =
+ tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
+ if (!ads->out.dma_chan) {
+ pr_err("%s: error allocating output DMA channel: %ld\n",
+ __func__, PTR_ERR(ads->out.dma_chan));
+ rc = -ENODEV;
+ goto fail_tx;
+ }
+ }
+
+ if ((ads->pdata->mask & TEGRA_AUDIO_ENABLE_RX)) {
+ /* setup audio recording */
+ ads->in.buf_phys = dma_map_single(&ads->pdev->dev,
+ ads->in.buffer,
+ 1 << PCM_BUFFER_MAX_SIZE_ORDER,
+ 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 (!ads->in.dma_chan) {
+ pr_err("%s: error allocating input DMA channel: %ld\n",
+ __func__, PTR_ERR(ads->in.dma_chan));
+ rc = -ENODEV;
+ goto fail_rx;
+ }
}
return 0;
fail_rx:
- dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys,
+ if ((ads->pdata->mask & TEGRA_AUDIO_ENABLE_RX)) {
+ dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys,
1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_FROM_DEVICE);
- tegra_dma_free_channel(ads->in.dma_chan);
- ads->in.dma_chan = 0;
-
+ 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,
+ if ((ads->pdata->mask & TEGRA_AUDIO_ENABLE_TX)) {
+ dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys,
1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
- tegra_dma_free_channel(ads->out.dma_chan);
- ads->out.dma_chan = 0;
+ tegra_dma_free_channel(ads->out.dma_chan);
+ ads->out.dma_chan = 0;
+ }
return rc;
}
if (delta_us > max_delay_us) {
pr_debug("%s: too late by %lld us\n", __func__,
delta_us - max_delay_us);
- aos->errors++;
+ aos->errors.late_dma++;
}
- 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)) {
+ if (!completion_done(&aos->fifo_completion)) {
pr_debug("%s: complete (%d avail)\n", __func__,
kfifo_avail(&aos->fifo));
complete(&aos->fifo_completion);
}
- if (stop_playback_if_necessary(aos))
+ if (stop_playback_if_necessary(aos)) {
+ pr_debug("%s: done (stopped)\n", __func__);
+ if (!completion_done(&aos->stop_completion)) {
+ pr_debug("%s: signalling stop completion\n", __func__);
+ complete(&aos->stop_completion);
+ }
return;
+ }
+
spin_lock_irqsave(&aos->dma_req_lock, flags);
resume_dma_playback(aos);
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
static void dma_rx_complete_threshold(struct tegra_dma_req *req)
{
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
}
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)) {
+ if (!completion_done(&ais->fifo_completion)) {
pr_debug("%s: signalling fifo completion\n", __func__);
complete(&ais->fifo_completion);
}
req->to_memory = false;
req->dest_addr = i2s_get_fifo_phy_base(ads->i2s_phys, I2S_FIFO_TX);
req->dest_wrap = 4;
- req->dest_bus_width = 16;
+ if (ads->bit_format == TEGRA_AUDIO_BIT_FORMAT_DSP)
+ req->dest_bus_width = ads->pdata->dsp_bus_width;
+ else
+ req->dest_bus_width = ads->pdata->i2s_bus_width;
req->source_bus_width = 32;
req->source_wrap = 0;
req->req_sel = ads->dma_req_sel;
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;
+ if (ads->bit_format == TEGRA_AUDIO_BIT_FORMAT_DSP)
+ req->source_bus_width = ads->pdata->dsp_bus_width;
+ else
+ req->source_bus_width = ads->pdata->i2s_bus_width;
req->dest_bus_width = 32;
req->dest_wrap = 0;
req->req_sel = ads->dma_req_sel;
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
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);
pr_debug("%s\n", __func__);
i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 0);
while ((i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY) &&
- spin < 100)
+ spin < 100) {
+ udelay(10);
if (spin++ > 50)
pr_info("%s: spin %d\n", __func__, spin);
+ }
if (spin == 100)
pr_warn("%s: spinny\n", __func__);
}
/* 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) {
pr_debug("%s: recording already in progress\n", __func__);
- return 0;
+ return -EALREADY;
}
- 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;
}
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) &&
- spin < 100)
+ spin < 100) {
+ udelay(10);
if (spin++ > 50)
pr_info("%s: spin %d\n", __func__, spin);
+ }
if (spin == 100)
pr_warn("%s: spinny\n", __func__);
}
if (i2s_is_fifo_enabled(ads->i2s_base, I2S_FIFO_TX)) {
pr_debug("%s: playback is already in progress\n", __func__);
- return 0;
+ return -EALREADY;
}
pr_debug("%s\n", __func__);
i2s_fifo_clear(ads->i2s_base, I2S_FIFO_TX);
#endif
+ i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1);
+
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;
}
while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY)
/* spin */;
- pr_info("%s: interrupts %d\n", __func__,
+ pr_debug("%s: interrupts %d\n", __func__,
ads->pio_stats.i2s_interrupt_count);
pr_info("%s: sent %d\n", __func__,
ads->pio_stats.tx_fifo_written);
if (i2s_is_fifo_enabled(ads->i2s_base, I2S_FIFO_RX)) {
pr_debug("%s: already started\n", __func__);
- return 0;
+ return -EALREADY;
}
pr_debug("%s: start\n", __func__);
i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX);
#endif
+ i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1);
+
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;
}
if (status & I2S_I2S_FIFO_RX_ERR) {
ads->pio_stats.rx_fifo_errors++;
- ads->in.errors++;
+ ads->in.errors.full_empty++;
}
if (status & I2S_FIFO_ERR)
pr_debug("%s tx fifo is ready\n", __func__);
- if (kfifo_avail(&out->fifo) > threshold_size(out) &&
- !completion_done(&out->fifo_completion)) {
+ if (!completion_done(&out->fifo_completion)) {
pr_debug("%s: tx complete (%d avail)\n", __func__,
kfifo_avail(&out->fifo));
complete(&out->fifo_completion);
}
- if (stop_playback_if_necessary(out))
+ if (stop_playback_if_necessary(out)) {
+ pr_debug("%s: done (stopped)\n", __func__);
+ if (!completion_done(&out->stop_completion)) {
+ pr_debug("%s: signalling stop completion\n",
+ __func__);
+ complete(&out->stop_completion);
+ }
goto check_rx;
+ }
empty = i2s_get_fifo_full_empty_count(ads->i2s_base,
I2S_FIFO_TX);
ads->pio_stats.rx_fifo_read += full * sizeof(u16);
- if (kfifo_avail(&in->fifo) < threshold_size(in) &&
- !completion_done(&in->fifo_completion)) {
+ if (!completion_done(&in->fifo_completion)) {
pr_debug("%s: rx complete (%d avail)\n", __func__,
kfifo_avail(&in->fifo));
complete(&in->fifo_completion);
static ssize_t tegra_audio_write(struct file *file,
const char __user *buf, size_t size, loff_t *off)
{
- ssize_t rc, total = 0;
+ ssize_t rc = 0, total = 0;
unsigned nw = 0;
struct audio_driver_state *ads = ads_from_misc_out(file);
mutex_lock(&ads->out.lock);
- ads->out.active = true;
-
if (!IS_ALIGNED(size, 4)) {
pr_err("%s: user size request %d not aligned to 4\n",
__func__, size);
size, kfifo_avail(&ads->out.fifo));
again:
+ if (ads->out.stop) {
+ pr_info("%s: playback has been cancelled (%d/%d bytes)\n",
+ __func__, total, size);
+ goto done;
+ }
+
rc = kfifo_from_user(&ads->out.fifo, buf + total, size - total, &nw);
if (rc < 0) {
pr_err("%s: error copying from user\n", __func__);
}
rc = start_playback(&ads->out);
- if (rc < 0) {
+ if (rc < 0 && rc != -EALREADY) {
pr_err("%s: could not start playback: %d\n", __func__, rc);
goto done;
}
*off += total;
done:
- ads->out.active = false;
mutex_unlock(&ads->out.lock);
return rc;
}
rc = -EFAULT;
break;
}
- if (aos->active) {
+ if (kfifo_len(&aos->fifo)) {
pr_err("%s: playback in progress\n", __func__);
rc = -EBUSY;
break;
sizeof(aos->errors)))
rc = -EFAULT;
if (!rc)
- aos->errors = 0;
+ memset(&aos->errors, 0, sizeof(aos->errors));
+ break;
+ case TEGRA_AUDIO_OUT_FLUSH:
+ if (kfifo_len(&aos->fifo)) {
+ pr_debug("%s: flushing\n", __func__);
+ request_stop_nosync(aos);
+ pr_debug("%s: flushed\n", __func__);
+ }
+ aos->stop = false;
break;
- case TEGRA_AUDIO_OUT_PRELOAD_FIFO: {
- struct tegra_audio_out_preload preload;
- if (copy_from_user(&preload, (void __user *)arg,
- sizeof(preload))) {
+ default:
+ rc = -EINVAL;
+ }
+
+ mutex_unlock(&aos->lock);
+ return rc;
+}
+
+static long tegra_audio_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+ struct audio_driver_state *ads = ads_from_misc_ctl(file);
+ unsigned int mode;
+ bool dma_restart = false;
+
+ mutex_lock(&ads->out.lock);
+ mutex_lock(&ads->in.lock);
+
+ switch (cmd) {
+ case TEGRA_AUDIO_SET_BIT_FORMAT:
+ if (copy_from_user(&mode, (const void __user *)arg,
+ sizeof(mode))) {
rc = -EFAULT;
- break;
+ goto done;
}
- rc = kfifo_from_user(&ads->out.fifo,
- (void __user *)preload.data, preload.len,
- &preload.len_written);
- if (rc < 0) {
- pr_err("%s: error copying from user\n", __func__);
+ dma_restart = (mode != ads->bit_format);
+ switch (mode) {
+ case TEGRA_AUDIO_BIT_FORMAT_DEFAULT:
+ i2s_set_bit_format(ads->i2s_base, ads->pdata->mode);
+ ads->bit_format = mode;
break;
+ case TEGRA_AUDIO_BIT_FORMAT_DSP:
+ i2s_set_bit_format(ads->i2s_base, I2S_BIT_FORMAT_DSP);
+ ads->bit_format = mode;
+ break;
+ default:
+ pr_err("%s: Invald PCM mode %d", __func__, mode);
+ rc = -EINVAL;
+ goto done;
}
- if (copy_to_user((void __user *)arg, &preload, sizeof(preload)))
+ break;
+ case TEGRA_AUDIO_GET_BIT_FORMAT:
+ if (copy_to_user((void __user *)arg, &ads->bit_format,
+ sizeof(mode)))
rc = -EFAULT;
- pr_info("%s: preloaded output fifo with %d bytes\n", __func__,
- preload.len_written);
+ goto done;
}
- break;
- default:
- rc = -EINVAL;
+
+ if (dma_restart && ads->using_dma) {
+ pr_debug("%s: Restarting DMA due to configuration change.\n",
+ __func__);
+ if (kfifo_len(&ads->out.fifo) || ads->in.active) {
+ pr_err("%s: dma busy, cannot restart.\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ sound_ops->tear_down(ads);
+ i2s_configure(ads->pdev);
+ sound_ops->setup(ads);
}
- mutex_unlock(&aos->lock);
+done:
+ mutex_unlock(&ads->in.lock);
+ mutex_unlock(&ads->out.lock);
return rc;
}
switch (cmd) {
case TEGRA_AUDIO_IN_START:
- pr_info("%s: start recording\n", __func__);
- ads->recording_cancelled = false;
- start_recording_if_necessary(ais);
+ pr_debug("%s: start recording\n", __func__);
+ ais->stop = false;
+ rc = start_recording_if_necessary(ais);
+ ais->active = !rc || rc == -EALREADY;
break;
case TEGRA_AUDIO_IN_STOP:
- pr_info("%s: stop recording\n", __func__);
- if (ais->active && !ads->recording_cancelled) {
- ads->recording_cancelled = true;
- stop_recording(ais);
- if (!completion_done(&ais->fifo_completion)) {
- pr_info("%s: complete\n", __func__);
- complete(&ais->fifo_completion);
- }
- }
+ pr_debug("%s: start recording\n", __func__);
+ if (ais->active)
+ request_stop_nosync(ais);
break;
case TEGRA_AUDIO_IN_SET_CONFIG: {
struct tegra_audio_in_config cfg;
break;
}
+#ifdef SAMPLE_RATE_CONVERTER_IN_DRIVER
switch (cfg.rate) {
case 8000:
ads->in_divs = divs_8000;
rc = -EINVAL;
break;
}
-
+#endif
+ if (cfg.stereo && !ads->pdata->stereo_capture) {
+ pr_err("%s: not capable of stereo capture.",
+ __func__);
+ rc = -EINVAL;
+ }
if (!rc) {
pr_info("%s: setting input sampling rate to %d, %s\n",
__func__, cfg.rate,
sizeof(ais->errors)))
rc = -EFAULT;
if (!rc)
- ais->errors = 0;
+ memset(&ais->errors, 0, sizeof(ais->errors));
break;
default:
rc = -EINVAL;
return rc;
}
+static ssize_t __i2s_copy_to_user(struct audio_driver_state *ads,
+ void __user *dst, int dst_size,
+ void *src, int src_size,
+ int *num_consumed)
+{
+ int bytes_written = dst_size < src_size ? dst_size : src_size;
+ *num_consumed = bytes_written;
+ if (copy_to_user(dst, src, bytes_written)) {
+ pr_err("%s: error copying %d bytes to user\n", __func__,
+ bytes_written);
+ return -EFAULT;
+ }
+ return bytes_written;
+}
+
+#ifdef SAMPLE_RATE_CONVERTER_IN_DRIVER
+
/* downsample a 16-bit 44.1kHz PCM stereo stream to stereo or mono 16-bit PCM
* stream.
*/
const int *divs, int divs_len,
bool out_stereo)
{
+ /* Todo: Handle mono source streams */
int i, j;
int lsum, rsum;
int di, div;
}
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;
*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);
return bytes_ds;
}
+#endif /*SAMPLE_RATE_CONVERTER_IN_DRIVER*/
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;
- }
+#ifdef SAMPLE_RATE_CONVERTER_IN_DRIVER
+ ds_now = __downsample_to_user(
+#else
+ ds_now = __i2s_copy_to_user(
+#endif
+ 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;
}
mutex_lock(&ads->in.lock);
- ads->in.active = true;
-
if (!IS_ALIGNED(size, 4)) {
pr_err("%s: user size request %d not aligned to 4\n",
__func__, size);
smp_processor_id(),
size, kfifo_len(&ads->in.fifo));
- rc = start_recording_if_necessary(&ads->in);
- if (rc < 0) {
- pr_err("%s: could not start recording\n", __func__);
- goto done_err;
- }
-
again:
/* If we want recording to stop immediately after it gets cancelled,
* then we do not want to wait for the fifo to get drained.
*/
- if (ads->recording_cancelled /* && kfifo_is_empty(&ads->in.fifo) */) {
- pr_debug("%s: recording has been cancelled (read %d bytes)\n",
- __func__, total);
+ if (ads->in.stop /* && kfifo_is_empty(&ads->in.fifo) */) {
+ pr_debug("%s: recording has been cancelled (%d/%d bytes)\n",
+ __func__, total, size);
goto done_ok;
}
+ rc = start_recording_if_necessary(&ads->in);
+ if (rc < 0 && rc != -EALREADY) {
+ pr_err("%s: could not start recording\n", __func__);
+ goto done_err;
+ }
+
+ ads->in.active = true;
+
nr = 0;
do {
nr = downsample_to_user(ads, buf + total, size - total);
__func__, nr, total, size);
if (total < size) {
- /* If we lost data, recording was stopped, so we need to resume
- * it here.
- */
- rc = start_recording_if_necessary(&ads->in);
- if (rc < 0) {
- pr_err("%s: could not resume recording\n", __func__);
- goto done_err;
- }
-
mutex_unlock(&ads->in.lock);
pr_debug("%s: sleep (user %d total %d nr %d)\n", __func__,
size, total, nr);
*off += total;
done_err:
- ads->in.active = false;
mutex_unlock(&ads->in.lock);
return rc;
}
{
struct audio_driver_state *ads = ads_from_misc_out(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->out.lock);
if (!ads->out.opened++) {
- pr_info("%s: resetting fifo and error count\n", __func__);
- ads->out.errors = 0;
+ pr_debug("%s: resetting fifo and error count\n", __func__);
+ ads->out.stop = false;
+ memset(&ads->out.errors, 0, sizeof(ads->out.errors));
kfifo_reset(&ads->out.fifo);
}
mutex_unlock(&ads->out.lock);
{
struct audio_driver_state *ads = ads_from_misc_out(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->out.lock);
if (ads->out.opened)
ads->out.opened--;
- if (!ads->out.opened)
+ if (!ads->out.opened) {
stop_playback_if_necessary(&ads->out);
+ if (kfifo_len(&ads->out.fifo))
+ pr_err("%s: output fifo is not empty (%d bytes left)\n",
+ __func__, kfifo_len(&ads->out.fifo));
+ allow_suspend(&ads->out);
+ }
mutex_unlock(&ads->out.lock);
return 0;
{
struct audio_driver_state *ads = ads_from_misc_in(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->in.lock);
if (!ads->in.opened++) {
- pr_info("%s: resetting fifo\n", __func__);
+ pr_debug("%s: resetting fifo\n", __func__);
/* By default, do not start recording when someone reads from
* input device.
*/
- ads->recording_cancelled = false;
- ads->in.errors = 0;
+ ads->in.stop = false;
+ memset(&ads->in.errors, 0, sizeof(ads->in.errors));
kfifo_reset(&ads->in.fifo);
}
mutex_unlock(&ads->in.lock);
- pr_info("%s: done\n", __func__);
+ pr_debug("%s: done\n", __func__);
return 0;
}
{
struct audio_driver_state *ads = ads_from_misc_in(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
+
mutex_lock(&ads->in.lock);
if (ads->in.opened)
ads->in.opened--;
+
+ if (!ads->in.opened) {
+ if (ads->in.active)
+ request_stop_nosync(&ads->in);
+ if (kfifo_len(&ads->in.fifo))
+ pr_err("%s: input fifo is not empty (%d bytes left)\n",
+ __func__, kfifo_len(&ads->in.fifo));
+ allow_suspend(&ads->in);
+ }
+
mutex_unlock(&ads->in.lock);
- pr_info("%s: done\n", __func__);
+ pr_debug("%s: done\n", __func__);
return 0;
}
.unlocked_ioctl = tegra_audio_in_ioctl,
};
+static const struct file_operations tegra_audio_ctl_fops = {
+ .owner = THIS_MODULE,
+ .open = tegra_audio_ctl_open,
+ .release = tegra_audio_ctl_release,
+ .unlocked_ioctl = tegra_audio_ioctl,
+};
+
static int init_stream_buffer(struct audio_stream *s,
struct tegra_audio_buf_config *cfg,
unsigned padding)
{
- pr_info("%s (size %d threshold %d chunk %d)\n", __func__,
+ pr_debug("%s (size %d threshold %d chunk %d)\n", __func__,
cfg->size, cfg->threshold, cfg->chunk);
if (cfg->chunk < PCM_DMA_CHUNK_MIN_SIZE_ORDER) {
}
kfifo_init(&s->fifo, s->buffer, 1 << cfg->size);
+ sg_init_table(&s->sg, 1);
return 0;
}
mutex_lock(&ads->out.lock);
mutex_lock(&ads->in.lock);
- if (ads->out.active || ads->in.active) {
+ if (kfifo_len(&ads->out.fifo) || ads->in.active) {
dev_err(dev, "%s: playback or recording in progress.\n",
__func__);
mutex_unlock(&ads->in.lock);
return -EINVAL;
}
- mutex_lock(&as->lock);
- if (as->active) {
- pr_err("%s: in progress.\n", __func__);
- mutex_unlock(&as->lock);
- return -EBUSY;
- }
*fifo_lvl = lvl;
pr_info("%s: fifo level %d\n", __func__, *fifo_lvl);
- mutex_unlock(&as->lock);
return size;
}
struct device_attribute *attr,
const char *buf, size_t count)
{
+ ssize_t rc;
struct tegra_audio_platform_data *pdata = dev->platform_data;
struct audio_driver_state *ads = pdata->driver_data;
- return __attr_fifo_atn_write(ads, &ads->out,
- &ads->out.i2s_fifo_atn_level, buf, count);
+ mutex_lock(&ads->out.lock);
+ if (kfifo_len(&ads->out.fifo)) {
+ pr_err("%s: playback in progress.\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ rc = __attr_fifo_atn_write(ads, &ads->out,
+ &ads->out.i2s_fifo_atn_level,
+ buf, count);
+done:
+ mutex_unlock(&ads->out.lock);
+ return rc;
}
static DEVICE_ATTR(tx_fifo_atn, 0644, tx_fifo_atn_show, tx_fifo_atn_store);
struct device_attribute *attr,
const char *buf, size_t count)
{
+ ssize_t rc;
struct tegra_audio_platform_data *pdata = dev->platform_data;
struct audio_driver_state *ads = pdata->driver_data;
- return __attr_fifo_atn_write(ads, &ads->in,
- &ads->in.i2s_fifo_atn_level, buf, count);
+ mutex_lock(&ads->in.lock);
+ if (ads->in.active) {
+ pr_err("%s: recording in progress.\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ rc = __attr_fifo_atn_write(ads, &ads->in,
+ &ads->in.i2s_fifo_atn_level,
+ buf, count);
+done:
+ mutex_unlock(&ads->in.lock);
+ return rc;
}
static DEVICE_ATTR(rx_fifo_atn, 0644, rx_fifo_atn_show, rx_fifo_atn_store);
pr_info("%s\n", __func__);
- state = kmalloc(sizeof(*state), GFP_KERNEL);
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
state->pdata->driver_data = state;
BUG_ON(!state->pdata);
+ if (!(state->pdata->mask &
+ (TEGRA_AUDIO_ENABLE_TX | TEGRA_AUDIO_ENABLE_RX))) {
+ dev_err(&pdev->dev, "neither tx nor rx is enabled!\n");
+ return -EIO;
+ }
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no mem resource!\n");
return -EIO;
}
- state->out.i2s_fifo_atn_level = I2S_FIFO_ATN_LVL_FOUR_SLOTS;
- state->in.i2s_fifo_atn_level = I2S_FIFO_ATN_LVL_FOUR_SLOTS;
-
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(&pdev->dev, "no dma resource!\n");
i2s_clk = clk_get(&pdev->dev, NULL);
if (!i2s_clk) {
- dev_err(&pdev->dev, "%s: could not get i2s1 clock\n",
+ dev_err(&pdev->dev, "%s: could not get i2s clock\n",
__func__);
return -EIO;
}
clk_set_rate(i2s_clk, state->pdata->i2s_clk_rate);
if (clk_enable(i2s_clk)) {
- dev_err(&pdev->dev, "%s: failed to enable i2s1 clock\n",
+ dev_err(&pdev->dev, "%s: failed to enable i2s clock\n",
__func__);
return -EIO;
}
}
clk_enable(audio_sync_clk);
- /* disable interrupts from I2S */
- i2s_fifo_clear(state->i2s_base, I2S_FIFO_TX);
- i2s_fifo_clear(state->i2s_base, I2S_FIFO_RX);
- i2s_enable_fifos(state->i2s_base, 0);
+ rc = i2s_configure(pdev);
+ if (rc < 0)
+ return rc;
- i2s_set_left_right_control_polarity(state->i2s_base, 0); /* default */
+ if ((state->pdata->mask & TEGRA_AUDIO_ENABLE_TX)) {
+ state->out.opened = 0;
+ state->out.active = false;
+ mutex_init(&state->out.lock);
+ init_completion(&state->out.fifo_completion);
+ init_completion(&state->out.stop_completion);
+ spin_lock_init(&state->out.dma_req_lock);
+ state->out.buf_phys = 0;
+ state->out.dma_chan = NULL;
+ state->out.dma_has_it = false;
+
+ state->out.i2s_fifo_atn_level = I2S_FIFO_ATN_LVL_FOUR_SLOTS;
+ state->out.buffer = 0;
+ state->out.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
+ state->out.buf_config.threshold = PCM_BUFFER_THRESHOLD_ORDER;
+ state->out.buf_config.chunk = PCM_BUFFER_DMA_CHUNK_SIZE_ORDER;
+ rc = init_stream_buffer(&state->out, &state->out.buf_config, 0);
+ if (rc < 0)
+ return rc;
- if (state->pdata->master)
- i2s_set_channel_bit_count(state->i2s_base, 44100,
- clk_get_rate(i2s_clk));
- i2s_set_master(state->i2s_base, state->pdata->master);
+ pm_qos_add_request(&state->out.pm_qos, PM_QOS_CPU_DMA_LATENCY,
+ PM_QOS_DEFAULT_VALUE);
- i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_TX, 1);
- i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_RX, 0);
+ rc = setup_misc_device(&state->misc_out,
+ &tegra_audio_out_fops,
+ "audio%d_out", state->pdev->id);
+ if (rc < 0)
+ return rc;
- 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);
+ rc = setup_misc_device(&state->misc_out_ctl,
+ &tegra_audio_out_ctl_fops,
+ "audio%d_out_ctl", state->pdev->id);
+ if (rc < 0)
+ return rc;
+ }
+
+ if ((state->pdata->mask & TEGRA_AUDIO_ENABLE_RX)) {
+ state->in.opened = 0;
+ state->in.active = false;
+ mutex_init(&state->in.lock);
+ init_completion(&state->in.fifo_completion);
+ init_completion(&state->in.stop_completion);
+ spin_lock_init(&state->in.dma_req_lock);
+ state->in.buf_phys = 0;
+ state->in.dma_chan = NULL;
+ state->in.dma_has_it = false;
+
+ state->in.i2s_fifo_atn_level = I2S_FIFO_ATN_LVL_FOUR_SLOTS;
+ state->in.buffer = 0;
+ state->in.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
+ state->in.buf_config.threshold = PCM_BUFFER_THRESHOLD_ORDER;
+ state->in.buf_config.chunk = PCM_BUFFER_DMA_CHUNK_SIZE_ORDER;
+ rc = init_stream_buffer(&state->in, &state->in.buf_config,
+ PCM_IN_BUFFER_PADDING);
+ if (rc < 0)
+ return rc;
- state->out.opened = 0;
- state->out.active = false;
- mutex_init(&state->out.lock);
- init_completion(&state->out.fifo_completion);
- init_completion(&state->out.stop_completion);
- spin_lock_init(&state->out.dma_req_lock);
- state->out.buf_phys = 0;
- state->out.dma_chan = NULL;
- state->out.dma_has_it = false;
-
- state->in.opened = 0;
- state->in.active = false;
- mutex_init(&state->in.lock);
- init_completion(&state->in.fifo_completion);
- init_completion(&state->in.stop_completion);
- spin_lock_init(&state->in.dma_req_lock);
- state->in.buf_phys = 0;
- state->in.dma_chan = NULL;
- state->in.dma_has_it = false;
-
- state->out.buffer = 0;
- state->out.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
- state->out.buf_config.threshold = PCM_BUFFER_THRESHOLD_ORDER;
- state->out.buf_config.chunk = PCM_BUFFER_DMA_CHUNK_SIZE_ORDER;
- rc = init_stream_buffer(&state->out, &state->out.buf_config, 0);
- if (rc < 0)
- return rc;
+ pm_qos_add_request(&state->in.pm_qos, PM_QOS_CPU_DMA_LATENCY,
+ PM_QOS_DEFAULT_VALUE);
- state->in.buffer = 0;
- state->in.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
- state->in.buf_config.threshold = PCM_BUFFER_THRESHOLD_ORDER;
- state->in.buf_config.chunk = PCM_BUFFER_DMA_CHUNK_SIZE_ORDER;
- rc = init_stream_buffer(&state->in, &state->in.buf_config,
- PCM_IN_BUFFER_PADDING);
- if (rc < 0)
- return rc;
+ rc = setup_misc_device(&state->misc_in,
+ &tegra_audio_in_fops,
+ "audio%d_in", state->pdev->id);
+ if (rc < 0)
+ return rc;
+
+ rc = setup_misc_device(&state->misc_in_ctl,
+ &tegra_audio_in_ctl_fops,
+ "audio%d_in_ctl", state->pdev->id);
+ if (rc < 0)
+ return rc;
+ }
if (request_irq(state->irq, i2s_interrupt,
IRQF_DISABLED, state->pdev->name, state) < 0) {
return -EIO;
}
- rc = setup_misc_device(&state->misc_out,
- &tegra_audio_out_fops,
- "audio%d_out", state->pdev->id);
- if (rc < 0)
- return rc;
-
- rc = setup_misc_device(&state->misc_out_ctl,
- &tegra_audio_out_ctl_fops,
- "audio%d_out_ctl", state->pdev->id);
- if (rc < 0)
- return rc;
-
- rc = setup_misc_device(&state->misc_in,
- &tegra_audio_in_fops,
- "audio%d_in", state->pdev->id);
- if (rc < 0)
- return rc;
-
- rc = setup_misc_device(&state->misc_in_ctl,
- &tegra_audio_in_ctl_fops,
- "audio%d_in_ctl", state->pdev->id);
+ rc = setup_misc_device(&state->misc_ctl,
+ &tegra_audio_ctl_fops,
+ "audio%d_ctl", state->pdev->id);
if (rc < 0)
return rc;
return 0;
}
+#ifdef CONFIG_PM
+static int tegra_audio_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ /* dev_info(&pdev->dev, "%s\n", __func__); */
+ return 0;
+}
+
+static int tegra_audio_resume(struct platform_device *pdev)
+{
+ return i2s_configure(pdev);
+}
+#endif /* CONFIG_PM */
+
static struct platform_driver tegra_audio_driver = {
.driver = {
.name = "i2s",
.owner = THIS_MODULE,
},
.probe = tegra_audio_probe,
+#ifdef CONFIG_PM
+ .suspend = tegra_audio_suspend,
+ .resume = tegra_audio_resume,
+#endif
};
static int __init tegra_audio_init(void)