ARM: tegra: clock: Drop set_rate on audio clocks
[firefly-linux-kernel-4.4.55.git] / arch / arm / mach-tegra / tegra_i2s_audio.c
index 395b33e4b8945bdd9dc35bf9dcef87271656bace..e5c0436d5f1bd7d046f5a4d1ebefbae8e174f48a 100644 (file)
 #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 {
@@ -63,17 +75,21 @@ 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 {
@@ -104,7 +120,6 @@ struct audio_driver_state {
 
        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;
@@ -116,6 +131,10 @@ struct audio_driver_state {
        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)
@@ -173,6 +192,17 @@ static inline struct audio_driver_state *ads_from_misc_in_ctl(
        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)
 {
@@ -185,6 +215,18 @@ static inline struct audio_driver_state *ads_from_in(
        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
@@ -328,6 +370,55 @@ static void i2s_set_master(unsigned long base, int master)
        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;
@@ -340,8 +431,14 @@ static int i2s_set_bit_format(unsigned long base, unsigned fmt)
        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;
 }
 
@@ -465,11 +562,57 @@ 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 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);
@@ -526,21 +669,26 @@ static int start_playback(struct audio_stream *aos)
        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;
 }
 
@@ -550,9 +698,8 @@ static bool stop_playback_if_necessary(struct audio_stream *aos)
        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);
@@ -562,11 +709,9 @@ static bool stop_playback_if_necessary(struct audio_stream *aos)
 
 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;
        }
@@ -574,16 +719,39 @@ static bool stop_recording_if_necessary_nosync(struct audio_stream *ais)
        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");
@@ -608,46 +776,58 @@ static int setup_dma(struct audio_driver_state *ads)
        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;
 }
@@ -687,20 +867,27 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req)
        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);
@@ -708,7 +895,7 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req)
 
 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)
@@ -727,10 +914,10 @@ 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);
        }
@@ -768,7 +955,10 @@ static void setup_dma_tx_request(struct tegra_dma_req *req,
        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;
@@ -787,7 +977,10 @@ static void setup_dma_rx_request(struct tegra_dma_req *req,
        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;
@@ -800,19 +993,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 +1016,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);
 
@@ -860,9 +1052,11 @@ static void stop_dma_playback(struct audio_stream *aos)
        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__);
 }
@@ -871,38 +1065,35 @@ 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) {
                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;
        }
 
@@ -940,9 +1131,11 @@ static void stop_dma_recording(struct audio_stream *ais)
        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__);
 }
@@ -968,7 +1161,7 @@ static int start_pio_playback(struct audio_stream *aos)
 
        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__);
@@ -979,9 +1172,10 @@ static int start_pio_playback(struct audio_stream *aos)
        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;
 }
@@ -996,7 +1190,7 @@ static void stop_pio_playback(struct audio_stream *aos)
        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);
@@ -1012,7 +1206,7 @@ static int start_pio_recording(struct audio_stream *ais)
 
        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__);
@@ -1023,11 +1217,11 @@ static int start_pio_recording(struct audio_stream *ais)
        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;
 }
 
@@ -1066,7 +1260,7 @@ static irqreturn_t i2s_interrupt(int irq, void *data)
 
        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)
@@ -1088,15 +1282,21 @@ static irqreturn_t i2s_interrupt(int irq, void *data)
 
                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);
@@ -1152,8 +1352,7 @@ check_rx:
 
                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);
@@ -1181,15 +1380,13 @@ done:
 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);
@@ -1201,6 +1398,12 @@ static ssize_t tegra_audio_write(struct file *file,
                        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__);
@@ -1208,7 +1411,7 @@ again:
        }
 
        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;
        }
@@ -1233,7 +1436,6 @@ again:
        *off += total;
 
 done:
-       ads->out.active = false;
        mutex_unlock(&ads->out.lock);
        return rc;
 }
@@ -1254,7 +1456,7 @@ static long tegra_audio_out_ioctl(struct file *file,
                        rc = -EFAULT;
                        break;
                }
-               if (aos->active) {
+               if (kfifo_len(&aos->fifo)) {
                        pr_err("%s: playback in progress\n", __func__);
                        rc = -EBUSY;
                        break;
@@ -1275,33 +1477,81 @@ static long tegra_audio_out_ioctl(struct file *file,
                                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;
 }
 
@@ -1316,20 +1566,15 @@ static long tegra_audio_in_ioctl(struct file *file,
 
        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;
@@ -1345,6 +1590,7 @@ static long tegra_audio_in_ioctl(struct file *file,
                        break;
                }
 
+#ifdef SAMPLE_RATE_CONVERTER_IN_DRIVER
                switch (cfg.rate) {
                case 8000:
                        ads->in_divs = divs_8000;
@@ -1372,7 +1618,12 @@ static long tegra_audio_in_ioctl(struct file *file,
                        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,
@@ -1414,7 +1665,7 @@ static long tegra_audio_in_ioctl(struct file *file,
                                sizeof(ais->errors)))
                        rc = -EFAULT;
                if (!rc)
-                       ais->errors = 0;
+                       memset(&ais->errors, 0, sizeof(ais->errors));
                break;
        default:
                rc = -EINVAL;
@@ -1424,6 +1675,23 @@ static long tegra_audio_in_ioctl(struct file *file,
        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.
  */
@@ -1434,6 +1702,7 @@ static int downsample(const s16 *in, int in_len,
                const int *divs, int divs_len,
                bool out_stereo)
 {
+       /* Todo: Handle mono source streams */
        int i, j;
        int lsum, rsum;
        int di, div;
@@ -1468,22 +1737,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,87 +1760,110 @@ 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);
 
        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;
 }
 
@@ -1586,8 +1877,6 @@ static ssize_t tegra_audio_read(struct file *file, char __user *buf,
 
        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);
@@ -1599,22 +1888,24 @@ static ssize_t tegra_audio_read(struct file *file, char __user *buf,
                        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);
@@ -1629,15 +1920,6 @@ again:
                        __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);
@@ -1660,7 +1942,6 @@ done_ok:
        *off += total;
 
 done_err:
-       ads->in.active = false;
        mutex_unlock(&ads->in.lock);
        return rc;
 }
@@ -1669,12 +1950,13 @@ static int tegra_audio_out_open(struct inode *inode, struct file *file)
 {
        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);
@@ -1686,13 +1968,18 @@ static int tegra_audio_out_release(struct inode *inode, struct file *file)
 {
        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;
@@ -1702,21 +1989,21 @@ 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__);
+       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;
 }
 
@@ -1724,12 +2011,23 @@ 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__);
+       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;
 }
 
@@ -1771,11 +2069,18 @@ static const struct file_operations tegra_audio_in_ctl_fops = {
        .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) {
@@ -1820,6 +2125,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;
 }
 
@@ -1886,7 +2192,7 @@ static ssize_t dma_toggle_store(struct device *dev,
 
        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);
@@ -1961,15 +2267,8 @@ static ssize_t __attr_fifo_atn_write(struct audio_driver_state *ads,
                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;
 }
@@ -1987,10 +2286,21 @@ static ssize_t tx_fifo_atn_store(struct device *dev,
                        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);
@@ -2008,10 +2318,21 @@ static ssize_t rx_fifo_atn_store(struct device *dev,
                        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);
@@ -2025,7 +2346,7 @@ static int tegra_audio_probe(struct platform_device *pdev)
 
        pr_info("%s\n", __func__);
 
-       state = kmalloc(sizeof(*state), GFP_KERNEL);
+       state = kzalloc(sizeof(*state), GFP_KERNEL);
        if (!state)
                return -ENOMEM;
 
@@ -2034,6 +2355,12 @@ static int tegra_audio_probe(struct platform_device *pdev)
        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");
@@ -2053,9 +2380,6 @@ static int tegra_audio_probe(struct platform_device *pdev)
                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");
@@ -2074,14 +2398,14 @@ static int tegra_audio_probe(struct platform_device *pdev)
 
        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;
        }
@@ -2103,61 +2427,82 @@ static int tegra_audio_probe(struct platform_device *pdev)
        }
        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) {
@@ -2167,27 +2512,9 @@ static int tegra_audio_probe(struct platform_device *pdev)
                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;
 
@@ -2225,12 +2552,29 @@ static int tegra_audio_probe(struct platform_device *pdev)
        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)