Merge branch 'linux-tegra-2.6.36' into android-tegra-2.6.36
[firefly-linux-kernel-4.4.55.git] / arch / arm / mach-tegra / tegra_i2s_audio.c
index 19a7796e55b8f2f815501720130f7b1a882792ad..f8a67f1e2043aec7c91d15e49276068d4b0cd73e 100644 (file)
 #include <linux/ktime.h>
 #include <linux/sysfs.h>
 #include <linux/pm_qos_params.h>
+#include <linux/wakelock.h>
 #include <linux/delay.h>
 #include <linux/tegra_audio.h>
+#include <linux/pm.h>
+#include <linux/workqueue.h>
 
 #include <mach/dma.h>
 #include <mach/iomap.h>
 
 #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 {
        int opened;
@@ -85,6 +93,9 @@ struct audio_stream {
        struct tegra_dma_req dma_req;
 
        struct pm_qos_request_list pm_qos;
+       struct work_struct allow_suspend_work;
+       struct wake_lock wake_lock;
+       char wake_lock_name[100];
 };
 
 struct i2s_pio_stats {
@@ -213,13 +224,24 @@ static inline struct audio_driver_state *ads_from_in(
 static inline void prevent_suspend(struct audio_stream *as)
 {
        pr_debug("%s\n", __func__);
+       cancel_work_sync(&as->allow_suspend_work);
+       wake_lock(&as->wake_lock);
        pm_qos_update_request(&as->pm_qos, 0);
 }
 
-static inline void allow_suspend(struct audio_stream *as)
+static void allow_suspend_worker(struct work_struct *w)
 {
+       struct audio_stream *as = container_of(w,
+                       struct audio_stream, allow_suspend_work);
+
        pr_debug("%s\n", __func__);
        pm_qos_update_request(&as->pm_qos, PM_QOS_DEFAULT_VALUE);
+       wake_unlock(&as->wake_lock);
+}
+
+static inline void allow_suspend(struct audio_stream *as)
+{
+       schedule_work(&as->allow_suspend_work);
 }
 
 #define I2S_I2S_FIFO_TX_BUSY   I2S_I2S_STATUS_FIFO1_BSY
@@ -365,6 +387,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;
@@ -377,8 +448,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;
 }
 
@@ -502,6 +579,58 @@ 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;
 }
 
+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);
 
@@ -612,8 +741,10 @@ static bool wait_till_stopped(struct audio_stream *as)
 {
        int rc;
        pr_debug("%s: wait for completion\n", __func__);
-       rc = wait_for_completion_interruptible(
-                       &as->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;
@@ -662,46 +793,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;
 }
@@ -829,7 +972,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;
@@ -848,7 +994,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;
@@ -941,7 +1090,7 @@ static int resume_dma_recording(struct audio_stream *ais)
 
        if (ais->dma_has_it) {
                pr_debug("%s: recording already in progress\n", __func__);
-               return 0;
+               return -EALREADY;
        }
 
        /* Don't send all the data yet. */
@@ -1029,7 +1178,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__);
@@ -1074,7 +1223,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__);
@@ -1279,7 +1428,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;
        }
@@ -1369,15 +1518,20 @@ static long tegra_audio_ioctl(struct file *file,
        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;
                }
-               switch(mode) {
+               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;
@@ -1389,16 +1543,32 @@ static long tegra_audio_ioctl(struct file *file,
                default:
                        pr_err("%s: Invald PCM mode %d", __func__, mode);
                        rc = -EINVAL;
-                       break;
+                       goto done;
                }
                break;
        case TEGRA_AUDIO_GET_BIT_FORMAT:
                if (copy_to_user((void __user *)arg, &ads->bit_format,
-                               sizeof(mode))) {
+                               sizeof(mode)))
                        rc = -EFAULT;
+               goto done;
+       }
+
+       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;
                }
-               break;
+               sound_ops->tear_down(ads);
+               i2s_configure(ads->pdev);
+               sound_ops->setup(ads);
        }
+
+done:
+       mutex_unlock(&ads->in.lock);
+       mutex_unlock(&ads->out.lock);
        return rc;
 }
 
@@ -1437,6 +1607,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;
@@ -1464,7 +1635,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,
@@ -1516,6 +1692,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.
  */
@@ -1526,6 +1719,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;
@@ -1588,6 +1782,7 @@ static ssize_t __downsample_to_user(struct audio_driver_state *ads,
 
        return bytes_ds;
 }
+#endif /*SAMPLE_RATE_CONVERTER_IN_DRIVER*/
 
 static ssize_t downsample_to_user(struct audio_driver_state *ads,
                        void __user *buf,
@@ -1624,7 +1819,12 @@ static ssize_t downsample_to_user(struct audio_driver_state *ads,
                BUG_ON(!sgl[i].length);
 
 again:
-               ds_now = __downsample_to_user(ads,
+#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);
@@ -1792,6 +1992,8 @@ static int tegra_audio_out_release(struct inode *inode, struct file *file)
                ads->out.opened--;
        if (!ads->out.opened) {
                stop_playback_if_necessary(&ads->out);
+               if (wake_lock_active(&ads->out.wake_lock))
+                       pr_err("%s: wake lock is still held!\n", __func__);
                if (kfifo_len(&ads->out.fifo))
                        pr_err("%s: output fifo is not empty (%d bytes left)\n",
                                __func__, kfifo_len(&ads->out.fifo));
@@ -1837,6 +2039,8 @@ static int tegra_audio_in_release(struct inode *inode, struct file *file)
        if (!ads->in.opened) {
                if (ads->in.active)
                        request_stop_nosync(&ads->in);
+               if (wake_lock_active(&ads->in.wake_lock))
+                       pr_err("%s: wake lock is still held!\n", __func__);
                if (kfifo_len(&ads->in.fifo))
                        pr_err("%s: input fifo is not empty (%d bytes left)\n",
                                __func__, kfifo_len(&ads->in.fifo));
@@ -2172,6 +2376,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");
@@ -2191,9 +2401,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");
@@ -2212,14 +2419,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;
        }
@@ -2241,98 +2448,104 @@ 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);
-
-       i2s_set_left_right_control_polarity(state->i2s_base, 0); /* default */
-
-       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);
-
-       i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_TX, 1);
-       i2s_set_fifo_mode(state->i2s_base, I2S_FIFO_RX, 0);
-
-       i2s_set_bit_format(state->i2s_base, state->pdata->mode);
-       i2s_set_bit_size(state->i2s_base, state->pdata->bit_size);
-       i2s_set_fifo_format(state->i2s_base, state->pdata->fifo_fmt);
-
-       state->out.opened = 0;
-       state->out.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);
+       rc = i2s_configure(pdev);
        if (rc < 0)
                return rc;
 
-       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;
+       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;
 
-       pm_qos_add_request(&state->in.pm_qos, PM_QOS_CPU_DMA_LATENCY,
-                               PM_QOS_DEFAULT_VALUE);
-       pm_qos_add_request(&state->out.pm_qos, PM_QOS_CPU_DMA_LATENCY,
+               INIT_WORK(&state->out.allow_suspend_work, allow_suspend_worker);
+               pm_qos_add_request(&state->out.pm_qos, PM_QOS_CPU_DMA_LATENCY,
                                PM_QOS_DEFAULT_VALUE);
 
-       if (request_irq(state->irq, i2s_interrupt,
-                       IRQF_DISABLED, state->pdev->name, state) < 0) {
-               dev_err(&pdev->dev,
-                       "%s: could not register handler for irq %d\n",
-                       __func__, state->irq);
-               return -EIO;
-       }
+               snprintf(state->out.wake_lock_name,
+                       sizeof(state->out.wake_lock_name),
+                       "i2s.%d-audio-out", state->pdev->id);
+               wake_lock_init(&state->out.wake_lock, WAKE_LOCK_SUSPEND,
+                       state->out.wake_lock_name);
 
-       rc = setup_misc_device(&state->misc_out,
+               rc = setup_misc_device(&state->misc_out,
                        &tegra_audio_out_fops,
                        "audio%d_out", state->pdev->id);
-       if (rc < 0)
-               return rc;
+               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_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;
+
+               INIT_WORK(&state->in.allow_suspend_work, allow_suspend_worker);
+               pm_qos_add_request(&state->in.pm_qos, PM_QOS_CPU_DMA_LATENCY,
+                                       PM_QOS_DEFAULT_VALUE);
+
+               snprintf(state->in.wake_lock_name,
+                       sizeof(state->in.wake_lock_name),
+                       "i2s.%d-audio-in", state->pdev->id);
+               wake_lock_init(&state->in.wake_lock, WAKE_LOCK_SUSPEND,
+                       state->in.wake_lock_name);
 
-       rc = setup_misc_device(&state->misc_in,
+               rc = setup_misc_device(&state->misc_in,
                        &tegra_audio_in_fops,
                        "audio%d_in", state->pdev->id);
-       if (rc < 0)
-               return rc;
+               if (rc < 0)
+                       return rc;
 
-       rc = setup_misc_device(&state->misc_in_ctl,
+               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 (rc < 0)
+                       return rc;
+       }
+
+       if (request_irq(state->irq, i2s_interrupt,
+                       IRQF_DISABLED, state->pdev->name, state) < 0) {
+               dev_err(&pdev->dev,
+                       "%s: could not register handler for irq %d\n",
+                       __func__, state->irq);
+               return -EIO;
+       }
 
        rc = setup_misc_device(&state->misc_ctl,
                        &tegra_audio_ctl_fops,
@@ -2374,12 +2587,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)