MLK-11398: ASoC: fsl_asrc: Add Memory to Memory support
authorShengjiu Wang <shengjiu.wang@freescale.com>
Tue, 9 Sep 2014 09:21:25 +0000 (17:21 +0800)
committerNitin Garg <nitin.garg@nxp.com>
Mon, 19 Mar 2018 19:48:02 +0000 (14:48 -0500)
cherry-pick below patch from v3.14.y:
ENGR00330403-4: ASoC: fsl_asrc: Add Memory to Memory support

ASRC M2M function is not able to put upstream due to its self-designed
ioctl protocol. So I just make a single patch for it and make it merge
into P2P driver as simply as possible.

The patch can only be maintained internally unless some one designs a
new protocol or implement the originally protocol by using some common
approach provided by Linux Kernel.

Signed-off-by: Shengjiu Wang <shengjiu.wang@freescale.com>
(cherry picked from commit b5a7a98da076b0202334db01ecc3833342a7ca11)

include/uapi/linux/mxc_asrc.h [new file with mode: 0644]
sound/soc/fsl/fsl_asrc.c
sound/soc/fsl/fsl_asrc.h
sound/soc/fsl/fsl_asrc_m2m.c [new file with mode: 0644]

diff --git a/include/uapi/linux/mxc_asrc.h b/include/uapi/linux/mxc_asrc.h
new file mode 100644 (file)
index 0000000..7a2b7df
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file mxc_asrc.h
+ *
+ * @brief i.MX Asynchronous Sample Rate Converter
+ *
+ * @ingroup Audio
+ */
+
+#ifndef __MXC_ASRC_UAPI_H__
+#define __MXC_ASRC_UAPI_H__
+
+#define ASRC_IOC_MAGIC         'C'
+
+#define ASRC_REQ_PAIR          _IOWR(ASRC_IOC_MAGIC, 0, struct asrc_req)
+#define ASRC_CONFIG_PAIR       _IOWR(ASRC_IOC_MAGIC, 1, struct asrc_config)
+#define ASRC_RELEASE_PAIR      _IOW(ASRC_IOC_MAGIC, 2, enum asrc_pair_index)
+#define ASRC_CONVERT           _IOW(ASRC_IOC_MAGIC, 3, struct asrc_convert_buffer)
+#define ASRC_START_CONV                _IOW(ASRC_IOC_MAGIC, 4, enum asrc_pair_index)
+#define ASRC_STOP_CONV         _IOW(ASRC_IOC_MAGIC, 5, enum asrc_pair_index)
+#define ASRC_STATUS            _IOW(ASRC_IOC_MAGIC, 6, struct asrc_status_flags)
+#define ASRC_FLUSH             _IOW(ASRC_IOC_MAGIC, 7, enum asrc_pair_index)
+
+enum asrc_pair_index {
+       ASRC_INVALID_PAIR = -1,
+       ASRC_PAIR_A = 0,
+       ASRC_PAIR_B = 1,
+       ASRC_PAIR_C = 2,
+};
+
+#define ASRC_PAIR_MAX_NUM      (ASRC_PAIR_C + 1)
+
+enum asrc_inclk {
+       INCLK_NONE = 0x03,
+       INCLK_ESAI_RX = 0x00,
+       INCLK_SSI1_RX = 0x01,
+       INCLK_SSI2_RX = 0x02,
+       INCLK_SSI3_RX = 0x07,
+       INCLK_SPDIF_RX = 0x04,
+       INCLK_MLB_CLK = 0x05,
+       INCLK_PAD = 0x06,
+       INCLK_ESAI_TX = 0x08,
+       INCLK_SSI1_TX = 0x09,
+       INCLK_SSI2_TX = 0x0a,
+       INCLK_SSI3_TX = 0x0b,
+       INCLK_SPDIF_TX = 0x0c,
+       INCLK_ASRCK1_CLK = 0x0f,
+};
+
+enum asrc_outclk {
+       OUTCLK_NONE = 0x03,
+       OUTCLK_ESAI_TX = 0x00,
+       OUTCLK_SSI1_TX = 0x01,
+       OUTCLK_SSI2_TX = 0x02,
+       OUTCLK_SSI3_TX = 0x07,
+       OUTCLK_SPDIF_TX = 0x04,
+       OUTCLK_MLB_CLK = 0x05,
+       OUTCLK_PAD = 0x06,
+       OUTCLK_ESAI_RX = 0x08,
+       OUTCLK_SSI1_RX = 0x09,
+       OUTCLK_SSI2_RX = 0x0a,
+       OUTCLK_SSI3_RX = 0x0b,
+       OUTCLK_SPDIF_RX = 0x0c,
+       OUTCLK_ASRCK1_CLK = 0x0f,
+};
+
+enum asrc_word_width {
+       ASRC_WIDTH_24_BIT = 0,
+       ASRC_WIDTH_16_BIT = 1,
+       ASRC_WIDTH_8_BIT = 2,
+};
+
+struct asrc_config {
+       enum asrc_pair_index pair;
+       unsigned int channel_num;
+       unsigned int buffer_num;
+       unsigned int dma_buffer_size;
+       unsigned int input_sample_rate;
+       unsigned int output_sample_rate;
+       enum asrc_word_width input_word_width;
+       enum asrc_word_width output_word_width;
+       enum asrc_inclk inclk;
+       enum asrc_outclk outclk;
+};
+
+struct asrc_req {
+       unsigned int chn_num;
+       enum asrc_pair_index index;
+};
+
+struct asrc_querybuf {
+       unsigned int buffer_index;
+       unsigned int input_length;
+       unsigned int output_length;
+       unsigned long input_offset;
+       unsigned long output_offset;
+};
+
+struct asrc_convert_buffer {
+       void *input_buffer_vaddr;
+       void *output_buffer_vaddr;
+       unsigned int input_buffer_length;
+       unsigned int output_buffer_length;
+};
+
+struct asrc_status_flags {
+       enum asrc_pair_index index;
+       unsigned int overload_error;
+};
+
+enum asrc_error_status {
+       ASRC_TASK_Q_OVERLOAD            = 0x01,
+       ASRC_OUTPUT_TASK_OVERLOAD       = 0x02,
+       ASRC_INPUT_TASK_OVERLOAD        = 0x04,
+       ASRC_OUTPUT_BUFFER_OVERFLOW     = 0x08,
+       ASRC_INPUT_BUFFER_UNDERRUN      = 0x10,
+};
+#endif/* __MXC_ASRC_UAPI_H__ */
index 1d82f68..9d95ca1 100644 (file)
@@ -729,6 +729,8 @@ static const struct regmap_config fsl_asrc_regmap_config = {
        .cache_type = REGCACHE_FLAT,
 };
 
+#include "fsl_asrc_m2m.c"
+
 /**
  * Initialize ASRC registers with a default configurations
  */
@@ -933,6 +935,12 @@ static int fsl_asrc_probe(struct platform_device *pdev)
                return ret;
        }
 
+       ret = fsl_asrc_m2m_init(asrc_priv);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to init m2m device %d\n", ret);
+               return ret;
+       }
+
        return 0;
 }
 
@@ -994,6 +1002,8 @@ static int fsl_asrc_suspend(struct device *dev)
 {
        struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
 
+       fsl_asrc_m2m_suspend(asrc_priv);
+
        regmap_read(asrc_priv->regmap, REG_ASRCFG,
                    &asrc_priv->regcache_cfg);
 
@@ -1043,6 +1053,7 @@ MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
 
 static struct platform_driver fsl_asrc_driver = {
        .probe = fsl_asrc_probe,
+       .remove = fsl_asrc_m2m_remove,
        .driver = {
                .name = "fsl-asrc",
                .of_match_table = fsl_asrc_ids,
index 0f163ab..c0ea39b 100644 (file)
@@ -13,6 +13,8 @@
 #ifndef _FSL_ASRC_H
 #define _FSL_ASRC_H
 
+#include <uapi/linux/mxc_asrc.h>
+
 #define IN     0
 #define OUT    1
 
 #define ASRMCR1i_OW16_MASK             (1 << ASRMCR1i_OW16_SHIFT)
 #define ASRMCR1i_OW16(v)               ((v) << ASRMCR1i_OW16_SHIFT)
 
-
-enum asrc_pair_index {
-       ASRC_INVALID_PAIR = -1,
-       ASRC_PAIR_A = 0,
-       ASRC_PAIR_B = 1,
-       ASRC_PAIR_C = 2,
-};
-
-#define ASRC_PAIR_MAX_NUM      (ASRC_PAIR_C + 1)
-
-enum asrc_inclk {
-       INCLK_NONE = 0x03,
-       INCLK_ESAI_RX = 0x00,
-       INCLK_SSI1_RX = 0x01,
-       INCLK_SSI2_RX = 0x02,
-       INCLK_SSI3_RX = 0x07,
-       INCLK_SPDIF_RX = 0x04,
-       INCLK_MLB_CLK = 0x05,
-       INCLK_PAD = 0x06,
-       INCLK_ESAI_TX = 0x08,
-       INCLK_SSI1_TX = 0x09,
-       INCLK_SSI2_TX = 0x0a,
-       INCLK_SSI3_TX = 0x0b,
-       INCLK_SPDIF_TX = 0x0c,
-       INCLK_ASRCK1_CLK = 0x0f,
-};
-
-enum asrc_outclk {
-       OUTCLK_NONE = 0x03,
-       OUTCLK_ESAI_TX = 0x00,
-       OUTCLK_SSI1_TX = 0x01,
-       OUTCLK_SSI2_TX = 0x02,
-       OUTCLK_SSI3_TX = 0x07,
-       OUTCLK_SPDIF_TX = 0x04,
-       OUTCLK_MLB_CLK = 0x05,
-       OUTCLK_PAD = 0x06,
-       OUTCLK_ESAI_RX = 0x08,
-       OUTCLK_SSI1_RX = 0x09,
-       OUTCLK_SSI2_RX = 0x0a,
-       OUTCLK_SSI3_RX = 0x0b,
-       OUTCLK_SPDIF_RX = 0x0c,
-       OUTCLK_ASRCK1_CLK = 0x0f,
-};
-
 #define ASRC_CLK_MAX_NUM       16
 
-enum asrc_word_width {
-       ASRC_WIDTH_24_BIT = 0,
-       ASRC_WIDTH_16_BIT = 1,
-       ASRC_WIDTH_8_BIT = 2,
-};
-
-struct asrc_config {
-       enum asrc_pair_index pair;
-       unsigned int channel_num;
-       unsigned int buffer_num;
-       unsigned int dma_buffer_size;
-       unsigned int input_sample_rate;
-       unsigned int output_sample_rate;
-       enum asrc_word_width input_word_width;
-       enum asrc_word_width output_word_width;
-       enum asrc_inclk inclk;
-       enum asrc_outclk outclk;
-};
-
-struct asrc_req {
-       unsigned int chn_num;
-       enum asrc_pair_index index;
-};
-
-struct asrc_querybuf {
-       unsigned int buffer_index;
-       unsigned int input_length;
-       unsigned int output_length;
-       unsigned long input_offset;
-       unsigned long output_offset;
-};
-
-struct asrc_convert_buffer {
-       void *input_buffer_vaddr;
-       void *output_buffer_vaddr;
-       unsigned int input_buffer_length;
-       unsigned int output_buffer_length;
-};
-
-struct asrc_status_flags {
-       enum asrc_pair_index index;
-       unsigned int overload_error;
-};
-
-enum asrc_error_status {
-       ASRC_TASK_Q_OVERLOAD            = 0x01,
-       ASRC_OUTPUT_TASK_OVERLOAD       = 0x02,
-       ASRC_INPUT_TASK_OVERLOAD        = 0x04,
-       ASRC_OUTPUT_BUFFER_OVERFLOW     = 0x08,
-       ASRC_INPUT_BUFFER_UNDERRUN      = 0x10,
-};
 
 struct dma_block {
        dma_addr_t dma_paddr;
@@ -464,4 +371,5 @@ struct fsl_asrc {
 
 extern struct snd_soc_platform_driver fsl_asrc_platform;
 struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
+
 #endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c
new file mode 100644 (file)
index 0000000..7871948
--- /dev/null
@@ -0,0 +1,941 @@
+/*
+ * Freescale ASRC Memory to Memory (M2M) driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#define FSL_ASRC_INPUTFIFO_WML 0x4
+#define FSL_ASRC_OUTPUTFIFO_WML        0x2
+
+#define DIR_STR(dir) dir == IN ? "in" : "out"
+
+struct fsl_asrc_m2m {
+       struct fsl_asrc_pair *pair;
+       struct completion complete[2];
+       struct dma_block dma_block[2];
+       unsigned int pair_hold;
+       unsigned int asrc_active;
+       unsigned int sg_nodes[2];
+       struct scatterlist sg[2][4];
+
+       enum asrc_word_width word_width[2];
+       unsigned int rate[2];
+       u32 watermark[2];
+       spinlock_t lock;
+};
+
+static struct miscdevice asrc_miscdev = {
+       .name   = "mxc_asrc",
+       .minor  = MISC_DYNAMIC_MINOR,
+};
+
+static void fsl_asrc_get_status(struct fsl_asrc_pair *pair,
+                               struct asrc_status_flags *flags)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       unsigned long lock_flags;
+
+       spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+       flags->overload_error = pair->error;
+
+       spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+}
+
+#define ASRC_xPUT_DMA_CALLBACK(dir) \
+       ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback)
+
+static void fsl_asrc_input_dma_callback(void *data)
+{
+       struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+       struct fsl_asrc_m2m *m2m = pair->private;
+
+       complete(&m2m->complete[IN]);
+}
+
+static void fsl_asrc_output_dma_callback(void *data)
+{
+       struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+       struct fsl_asrc_m2m *m2m = pair->private;
+
+       complete(&m2m->complete[OUT]);
+}
+
+static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+       u32 val;
+
+       regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val);
+
+       val &= ASRFSTi_OUTPUT_FIFO_MASK;
+
+       return val >> ASRFSTi_OUTPUT_FIFO_SHIFT;
+}
+
+static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+       struct dma_block *output = &m2m->dma_block[OUT];
+       u32 i, reg, size, t_size = 0;
+       u32 *reg24 = NULL;
+       u16 *reg16 = NULL;
+
+       if (m2m->word_width[OUT] == ASRC_WIDTH_24_BIT)
+               reg24 = output->dma_vaddr + output->length;
+       else
+               reg16 = output->dma_vaddr + output->length;
+
+retry:
+       size = fsl_asrc_get_output_FIFO_size(pair);
+
+       for (i = 0; i < size * pair->channels; i++) {
+               regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+               if (reg24) {
+                       *(reg24) = reg;
+                       reg24++;
+               } else {
+                       *(reg16) = (u16)reg;
+                       reg16++;
+               }
+       }
+       t_size += size;
+
+       if (size)
+               goto retry;
+
+       if (t_size > ASRC_OUTPUT_LAST_SAMPLE)
+               t_size = ASRC_OUTPUT_LAST_SAMPLE;
+
+       if (reg24)
+               output->length += t_size * pair->channels * 4;
+       else
+               output->length += t_size * pair->channels * 2;
+}
+
+static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct dma_block *input = &m2m->dma_block[IN];
+       struct dma_block *output = &m2m->dma_block[OUT];
+       enum asrc_pair_index index = pair->index;
+
+       input->dma_vaddr = kzalloc(input->length, GFP_KERNEL);
+       if (!input->dma_vaddr) {
+               pair_err("failed to allocate input DMA buffer\n");
+               return -ENOMEM;
+       }
+       input->dma_paddr = virt_to_dma(NULL, input->dma_vaddr);
+
+       output->dma_vaddr = kzalloc(output->length, GFP_KERNEL);
+       if (!output->dma_vaddr) {
+               pair_err("failed to allocate output DMA buffer\n");
+               goto exit;
+       }
+       output->dma_paddr = virt_to_dma(NULL, output->dma_vaddr);
+
+       return 0;
+
+exit:
+       kfree(input->dma_vaddr);
+
+       return -ENOMEM;
+}
+
+static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan,
+                             u32 dma_addr, void *buf_addr, u32 buf_len,
+                             bool dir, enum asrc_word_width word_width)
+{
+       struct dma_async_tx_descriptor *desc = pair->desc[dir];
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       unsigned int sg_nent = m2m->sg_nodes[dir];
+       enum asrc_pair_index index = pair->index;
+       struct scatterlist *sg = m2m->sg[dir];
+       struct dma_slave_config slave_config;
+       enum dma_slave_buswidth buswidth;
+       int ret, i;
+
+       switch (word_width) {
+       case ASRC_WIDTH_16_BIT:
+               buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+               break;
+       case ASRC_WIDTH_24_BIT:
+               buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+               break;
+       default:
+               pair_err("invalid word width\n");
+               return -EINVAL;
+       }
+
+       if (dir == IN) {
+               slave_config.direction = DMA_MEM_TO_DEV;
+               slave_config.dst_addr = dma_addr;
+               slave_config.dst_addr_width = buswidth;
+               slave_config.dst_maxburst =
+                       m2m->watermark[IN] * pair->channels / buswidth;
+       } else {
+               slave_config.direction = DMA_DEV_TO_MEM;
+               slave_config.src_addr = dma_addr;
+               slave_config.src_addr_width = buswidth;
+               slave_config.src_maxburst =
+                       m2m->watermark[OUT] * pair->channels / buswidth;
+       }
+
+       ret = dmaengine_slave_config(chan, &slave_config);
+       if (ret) {
+               pair_err("failed to config dmaengine for %sput task: %d\n",
+                               DIR_STR(dir), ret);
+               return -EINVAL;
+       }
+
+       sg_init_table(sg, sg_nent);
+       switch (sg_nent) {
+       case 1:
+               sg_init_one(sg, buf_addr, buf_len);
+               break;
+       case 2:
+       case 3:
+       case 4:
+               for (i = 0; i < (sg_nent - 1); i++)
+                       sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+                                       ASRC_MAX_BUFFER_SIZE);
+
+               sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+                               buf_len - ASRC_MAX_BUFFER_SIZE * i);
+               break;
+       default:
+               pair_err("invalid input DMA nodes number: %d\n", sg_nent);
+               return -EINVAL;
+       }
+
+       ret = dma_map_sg(NULL, sg, sg_nent, slave_config.direction);
+       if (ret != sg_nent) {
+               pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir));
+               return -EINVAL;
+       }
+
+       desc = dmaengine_prep_slave_sg(chan, sg, sg_nent,
+                       slave_config.direction, DMA_PREP_INTERRUPT);
+       if (!desc) {
+               pair_err("failed to prepare dmaengine for %sput task\n",
+                               DIR_STR(dir));
+               return -EINVAL;
+       }
+
+       pair->desc[dir] = desc;
+       pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+
+       desc->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+       desc->callback_param = pair;
+
+       return 0;
+}
+
+static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair,
+                                     struct asrc_convert_buffer *pbuf, bool dir)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       unsigned int *dma_len = &m2m->dma_block[dir].length;
+       enum asrc_word_width width = m2m->word_width[dir];
+       void *dma_vaddr = m2m->dma_block[dir].dma_vaddr;
+       struct dma_chan *dma_chan = pair->dma_chan[dir];
+       unsigned int buf_len, wm = m2m->watermark[dir];
+       unsigned int *sg_nodes = &m2m->sg_nodes[dir];
+       enum asrc_pair_index index = pair->index;
+       u32 word_size, fifo_addr;
+       void __user *buf_vaddr;
+
+       /* Clean the DMA buffer */
+       memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE);
+
+       if (dir == IN) {
+               buf_vaddr = (void __user *)pbuf->input_buffer_vaddr;
+               buf_len = pbuf->input_buffer_length;
+       } else {
+               buf_vaddr = (void __user *)pbuf->output_buffer_vaddr;
+               buf_len = pbuf->output_buffer_length;
+       }
+
+       if (width == ASRC_WIDTH_24_BIT)
+               word_size = 4;
+       else
+               word_size = 2;
+
+       if (buf_len < word_size * pair->channels * wm) {
+               pair_err("%sput buffer size is too small: [%d]\n",
+                               DIR_STR(dir), buf_len);
+               return -EINVAL;
+       }
+
+       /* Copy origin data into input buffer */
+       if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len))
+               return -EFAULT;
+
+       *dma_len = buf_len;
+       if (dir == OUT)
+               *dma_len -= ASRC_OUTPUT_LAST_SAMPLE * word_size * pair->channels;
+
+       *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1;
+
+       fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index);
+
+       return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr,
+                                 *dma_len, dir, width);
+}
+
+static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair,
+                                  struct asrc_convert_buffer *pbuf)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+       int ret;
+
+       ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN);
+       if (ret) {
+               pair_err("failed to prepare input buffer: %d\n", ret);
+               return ret;
+       }
+
+       ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT);
+       if (ret) {
+               pair_err("failed to prepare output buffer: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+int fsl_asrc_process_buffer_pre(struct completion *complete,
+                               enum asrc_pair_index index, bool dir)
+{
+       if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) {
+               pr_err("%sput DMA task timeout\n", DIR_STR(dir));
+               return -ETIME;
+       } else if (signal_pending(current)) {
+               pr_err("%sput task forcibly aborted\n", DIR_STR(dir));
+               return -ERESTARTSYS;
+       }
+
+       init_completion(complete);
+
+       return 0;
+}
+
+#define mxc_asrc_dma_umap(m2m) \
+       do { \
+               dma_unmap_sg(NULL, m2m->sg[IN], m2m->sg_nodes[IN], \
+                            DMA_MEM_TO_DEV); \
+               dma_unmap_sg(NULL, m2m->sg[OUT], m2m->sg_nodes[OUT], \
+                            DMA_DEV_TO_MEM); \
+       } while (0)
+
+int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair,
+                           struct asrc_convert_buffer *pbuf)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       enum asrc_pair_index index = pair->index;
+       unsigned long lock_flags;
+       int ret;
+
+       /* Check input task first */
+       ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, OUT);
+       if (ret) {
+               mxc_asrc_dma_umap(m2m);
+               return ret;
+       }
+
+       /* ...then output task*/
+       ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, IN);
+       if (ret) {
+               mxc_asrc_dma_umap(m2m);
+               return ret;
+       }
+
+       mxc_asrc_dma_umap(m2m);
+
+       /* Fetch the remaining data */
+       spin_lock_irqsave(&m2m->lock, lock_flags);
+       if (!m2m->pair_hold) {
+               spin_unlock_irqrestore(&m2m->lock, lock_flags);
+               return -EFAULT;
+       }
+       fsl_asrc_read_last_FIFO(pair);
+       spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+       /* Update final lengths after getting last FIFO */
+       pbuf->input_buffer_length = m2m->dma_block[IN].length;
+       pbuf->output_buffer_length = m2m->dma_block[OUT].length;
+
+       if (copy_to_user((void __user *)pbuf->output_buffer_vaddr,
+                        m2m->dma_block[OUT].dma_vaddr,
+                        m2m->dma_block[OUT].length))
+               return -EFAULT;
+
+       return 0;
+}
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */
+static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       enum asrc_pair_index index = pair->index;
+       u32 *in24 = m2m->dma_block[IN].dma_vaddr;
+       u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4);
+       u32 *reg24 = m2m->dma_block[OUT].dma_vaddr;
+       u32 size, i, j, t_size, reg;
+
+       t_size = 0;
+
+       for (i = 0; i < dma_len; ) {
+               for (j = 0; j < 2; j++) {
+                       regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+                       in24++;
+                       regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+                       in24++;
+                       i++;
+               }
+               udelay(50);
+               udelay(50 * m2m->rate[OUT] / m2m->rate[IN]);
+
+               size = fsl_asrc_get_output_FIFO_size(index);
+               for (j = 0; j < size; j++) {
+                       regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+                       *(reg24) = reg;
+                       reg24++;
+                       regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+                       *(reg24) = reg;
+                       reg24++;
+               }
+               t_size += size;
+       }
+
+       mdelay(1);
+       size = fsl_asrc_get_output_FIFO_size(index);
+       for (j = 0; j < size; j++) {
+               regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+               *(reg24) = reg;
+               reg24++;
+               regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+               *(reg24) = reg;
+               reg24++;
+       }
+       t_size += size;
+
+       m2m->dma_block[OUT].length = t_size * pair->channels * 4;
+
+       complete(&m2m->complete[OUT]);
+       complete(&m2m->complete[IN]);
+}
+#else
+static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       enum asrc_pair_index index = pair->index;
+       u32 size = fsl_asrc_get_output_FIFO_size(pair);
+       int i;
+
+       /* Read all data in OUTPUT FIFO */
+       while (size) {
+               u32 val;
+               for (i = 0; i < size * pair->channels; i++)
+                       regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val);
+               /* Fetch the data every 100us */
+               udelay(100);
+
+               size = fsl_asrc_get_output_FIFO_size(pair);
+       }
+
+       /* Submit DMA request */
+       dmaengine_submit(pair->desc[IN]);
+       dma_async_issue_pending(pair->desc[IN]->chan);
+
+       dmaengine_submit(pair->desc[OUT]);
+       dma_async_issue_pending(pair->desc[OUT]->chan);
+
+       /*
+        * Clear DMA request during the stall state of ASRC:
+        * During STALL state, the remaining in input fifo would never be
+        * smaller than the input threshold while the output fifo would not
+        * be bigger than output one. Thus the DMA request would be cleared.
+        */
+       fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN,
+                               ASRC_FIFO_THRESHOLD_MAX);
+
+       /* Update the real input threshold to raise DMA request */
+       fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+}
+#endif /* ASRC_POLLING_WITHOUT_DMA */
+
+static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair,
+                                   void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct device *dev = &asrc_priv->pdev->dev;
+       struct asrc_req req;
+       long ret;
+
+       ret = copy_from_user(&req, user, sizeof(req));
+       if (ret) {
+               dev_err(dev, "failed to get req from user space: %ld\n", ret);
+               return ret;
+       }
+
+       ret = fsl_asrc_request_pair(req.chn_num, pair);
+       if (ret) {
+               dev_err(dev, "failed to request pair: %ld\n", ret);
+               return ret;
+       }
+
+       m2m->pair_hold = 1;
+       pair->channels = req.chn_num;
+
+       req.index = pair->index;
+
+       ret = copy_to_user(user, &req, sizeof(req));
+       if (ret) {
+               dev_err(dev, "failed to send req to user space: %ld\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair,
+                                      void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct device *dev = &asrc_priv->pdev->dev;
+       struct asrc_config config;
+       enum asrc_pair_index index;
+       long ret;
+
+       ret = copy_from_user(&config, user, sizeof(config));
+       if (ret) {
+               dev_err(dev, "failed to get config from user space: %ld\n", ret);
+               return ret;
+       }
+
+       index = config.pair;
+
+       pair->config = &config;
+       ret = fsl_asrc_config_pair(pair);
+       if (ret) {
+               pair_err("failed to config pair: %ld\n", ret);
+               return ret;
+       }
+
+       m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML;
+       m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML;
+
+       fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+
+       m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE;
+       m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE;
+
+       m2m->word_width[IN] = config.input_word_width;
+       m2m->word_width[OUT] = config.output_word_width;
+
+       m2m->rate[IN] = config.input_sample_rate;
+       m2m->rate[OUT] = config.output_sample_rate;
+
+       ret = fsl_allocate_dma_buf(pair);
+       if (ret) {
+               pair_err("failed to allocate DMA buffer: %ld\n", ret);
+               return ret;
+       }
+
+       /* Request DMA channel for both input and output */
+       pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+       if (pair->dma_chan[IN] == NULL) {
+               pair_err("failed to request input task DMA channel\n");
+               return  -EBUSY;
+       }
+
+       pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+       if (pair->dma_chan[OUT] == NULL) {
+               pair_err("failed to request output task DMA channel\n");
+               return  -EBUSY;
+       }
+
+       init_completion(&m2m->complete[IN]);
+       init_completion(&m2m->complete[OUT]);
+
+       ret = copy_to_user(user, &config, sizeof(config));
+       if (ret) {
+               pair_err("failed to send config to user space: %ld\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair,
+                                       void __user *user)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index;
+       unsigned long lock_flags;
+       long ret;
+
+       ret = copy_from_user(&index, user, sizeof(index));
+       if (ret) {
+               pair_err("failed to get index from user space: %ld\n", ret);
+               return ret;
+       }
+
+       /* index might be not valid due to some application failure. */
+       if (index < 0)
+               return -EINVAL;
+
+       m2m->asrc_active = 0;
+
+       spin_lock_irqsave(&m2m->lock, lock_flags);
+       m2m->pair_hold = 0;
+       spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+       if (pair->dma_chan[IN])
+               dma_release_channel(pair->dma_chan[IN]);
+       if (pair->dma_chan[OUT])
+               dma_release_channel(pair->dma_chan[OUT]);
+       kfree(m2m->dma_block[IN].dma_vaddr);
+       kfree(m2m->dma_block[OUT].dma_vaddr);
+       fsl_asrc_release_pair(pair);
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair,
+                                  void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+       struct asrc_convert_buffer buf;
+       long ret;
+
+       ret = copy_from_user(&buf, user, sizeof(buf));
+       if (ret) {
+               pair_err("failed to get buf from user space: %ld\n", ret);
+               return ret;
+       }
+
+       ret = fsl_asrc_prepare_buffer(pair, &buf);
+       if (ret) {
+               pair_err("failed to prepare buffer: %ld\n", ret);
+               return ret;
+       }
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+       fsl_asrc_polling_debug(pair);
+#else
+       fsl_asrc_submit_dma(pair);
+#endif
+
+       ret = fsl_asrc_process_buffer(pair, &buf);
+       if (ret) {
+               pair_err("failed to process buffer: %ld\n", ret);
+               return ret;
+       }
+
+       ret = copy_to_user(user, &buf, sizeof(buf));
+       if (ret) {
+               pair_err("failed to send buf to user space: %ld\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair,
+                                     void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       enum asrc_pair_index index;
+       long ret;
+
+       ret = copy_from_user(&index, user, sizeof(index));
+       if (ret) {
+               pair_err("failed to get index from user space: %ld\n", ret);
+               return ret;
+       }
+
+       m2m->asrc_active = 1;
+       fsl_asrc_start_pair(pair);
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair,
+                                    void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       enum asrc_pair_index index;
+       long ret;
+
+       ret = copy_from_user(&index, user, sizeof(index));
+       if (ret) {
+               pair_err("failed to get index from user space: %ld\n", ret);
+               return ret;
+       }
+
+       dmaengine_terminate_all(pair->dma_chan[IN]);
+       dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+       fsl_asrc_stop_pair(pair);
+       m2m->asrc_active = 0;
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user)
+{
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+       struct asrc_status_flags flags;
+       long ret;
+
+       ret = copy_from_user(&flags, user, sizeof(flags));
+       if (ret) {
+               pair_err("failed to get flags from user space: %ld\n", ret);
+               return ret;
+       }
+
+       fsl_asrc_get_status(pair, &flags);
+
+       ret = copy_to_user(user, &flags, sizeof(flags));
+       if (ret) {
+               pair_err("failed to send flags to user space: %ld\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user)
+{
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       enum asrc_pair_index index = pair->index;
+
+       init_completion(&m2m->complete[IN]);
+       init_completion(&m2m->complete[OUT]);
+
+       /* Release DMA and request again */
+       dma_release_channel(pair->dma_chan[IN]);
+       dma_release_channel(pair->dma_chan[OUT]);
+
+       pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+       if (pair->dma_chan[IN] == NULL) {
+               pair_err("failed to request input task DMA channel\n");
+               return -EBUSY;
+       }
+
+       pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+       if (pair->dma_chan[OUT] == NULL) {
+               pair_err("failed to request output task DMA channel\n");
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
+static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct fsl_asrc_pair *pair = file->private_data;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       void __user *user = (void __user *)arg;
+       long ret = 0;
+
+       switch (cmd) {
+       case ASRC_REQ_PAIR:
+               ret = fsl_asrc_ioctl_req_pair(pair, user);
+               break;
+       case ASRC_CONFIG_PAIR:
+               ret = fsl_asrc_ioctl_config_pair(pair, user);
+               break;
+       case ASRC_RELEASE_PAIR:
+               ret = fsl_asrc_ioctl_release_pair(pair, user);
+               break;
+       case ASRC_CONVERT:
+               ret = fsl_asrc_ioctl_convert(pair, user);
+               break;
+       case ASRC_START_CONV:
+               ret = fsl_asrc_ioctl_start_conv(pair, user);
+               break;
+       case ASRC_STOP_CONV:
+               ret = fsl_asrc_ioctl_stop_conv(pair, user);
+               break;
+       case ASRC_STATUS:
+               ret = fsl_asrc_ioctl_status(pair, user);
+               break;
+       case ASRC_FLUSH:
+               ret = fsl_asrc_ioctl_flush(pair, user);
+               break;
+       default:
+               dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n");
+               break;
+       }
+
+       return ret;
+}
+
+static int fsl_asrc_open(struct inode *inode, struct file *file)
+{
+       struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev.this_device);
+       struct device *dev = &asrc_priv->pdev->dev;
+       struct fsl_asrc_pair *pair;
+       struct fsl_asrc_m2m *m2m;
+       int ret;
+
+       ret = signal_pending(current);
+       if (ret) {
+               dev_err(dev, "current process has a signal pending\n");
+               return ret;
+       }
+
+       pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
+       if (!pair) {
+               dev_err(dev, "failed to allocate pair\n");
+               return -ENOMEM;
+       }
+
+       m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL);
+       if (!pair) {
+               dev_err(dev, "failed to allocate m2m resource\n");
+               return -ENOMEM;
+       }
+
+       pair->private = m2m;
+       pair->asrc_priv = asrc_priv;
+
+       spin_lock_init(&m2m->lock);
+
+       file->private_data = pair;
+
+       pm_runtime_get_sync(dev);
+
+       return 0;
+}
+
+static int fsl_asrc_close(struct inode *inode, struct file *file)
+{
+       struct fsl_asrc_pair *pair = file->private_data;
+       struct fsl_asrc_m2m *m2m = pair->private;
+       struct fsl_asrc *asrc_priv = pair->asrc_priv;
+       struct device *dev = &asrc_priv->pdev->dev;
+       unsigned long lock_flags;
+       int i;
+
+       if (!pair)
+               goto out;
+
+       /* Make sure we have clear the pointer */
+       for (i = 0; i < ASRC_PAIR_MAX_NUM; i++)
+               if (asrc_priv->pair[i] == pair)
+                       asrc_priv->pair[i] = NULL;
+
+       if (m2m->asrc_active) {
+               m2m->asrc_active = 0;
+
+               dmaengine_terminate_all(pair->dma_chan[IN]);
+               dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+               fsl_asrc_stop_pair(pair);
+               fsl_asrc_input_dma_callback((void *)pair);
+               fsl_asrc_output_dma_callback((void *)pair);
+       }
+
+       if (m2m->pair_hold) {
+               spin_lock_irqsave(&m2m->lock, lock_flags);
+               m2m->pair_hold = 0;
+               spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+               if (pair->dma_chan[IN])
+                       dma_release_channel(pair->dma_chan[IN]);
+               if (pair->dma_chan[OUT])
+                       dma_release_channel(pair->dma_chan[OUT]);
+
+               kfree(m2m->dma_block[IN].dma_vaddr);
+               kfree(m2m->dma_block[OUT].dma_vaddr);
+
+               fsl_asrc_release_pair(pair);
+       }
+
+       kfree(m2m);
+       kfree(pair);
+       file->private_data = NULL;
+
+out:
+       pm_runtime_put_sync(dev);
+
+       return 0;
+}
+
+static const struct file_operations asrc_fops = {
+       .owner          = THIS_MODULE,
+       .unlocked_ioctl = fsl_asrc_ioctl,
+       .open           = fsl_asrc_open,
+       .release        = fsl_asrc_close,
+};
+
+static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv)
+{
+       struct device *dev = &asrc_priv->pdev->dev;
+       int ret;
+
+       asrc_miscdev.fops = &asrc_fops,
+       ret = misc_register(&asrc_miscdev);
+       if (ret) {
+               dev_err(dev, "failed to register char device %d\n", ret);
+               return ret;
+       }
+       dev_set_drvdata(asrc_miscdev.this_device, asrc_priv);
+
+       return 0;
+}
+
+static int fsl_asrc_m2m_remove(struct platform_device *pdev)
+{
+       misc_deregister(&asrc_miscdev);
+       return 0;
+}
+
+static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv)
+{
+       struct fsl_asrc_pair *pair;
+       struct fsl_asrc_m2m *m2m;
+       int i;
+
+       for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) {
+               pair = asrc_priv->pair[i];
+               if (!pair || !pair->private)
+                       continue;
+
+               m2m = pair->private;
+
+               if (!completion_done(&m2m->complete[IN])) {
+                       dmaengine_terminate_all(pair->dma_chan[IN]);
+                       fsl_asrc_input_dma_callback((void *)pair);
+               }
+               if (!completion_done(&m2m->complete[OUT])) {
+                       dmaengine_terminate_all(pair->dma_chan[OUT]);
+                       fsl_asrc_output_dma_callback((void *)pair);
+               }
+       }
+}