summaryrefslogtreecommitdiff
path: root/drivers/mxc/asrc/mvf_asrc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mxc/asrc/mvf_asrc.c')
-rw-r--r--drivers/mxc/asrc/mvf_asrc.c1941
1 files changed, 1941 insertions, 0 deletions
diff --git a/drivers/mxc/asrc/mvf_asrc.c b/drivers/mxc/asrc/mvf_asrc.c
new file mode 100644
index 000000000000..fc3f5dfa4f9a
--- /dev/null
+++ b/drivers/mxc/asrc/mvf_asrc.c
@@ -0,0 +1,1941 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Vybrid ASRC driver based on mxc_asrc.c
+ *
+ *
+ * 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
+ */
+
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/dma-mapping.h>
+#include <linux/mxc_asrc.h>
+#include <linux/fsl_devices.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <asm/irq.h>
+#include <asm/memory.h>
+#include <mach/mvf.h>
+#include <mach/dma.h>
+#include <mach/mxc_asrc.h>
+#include <mach/mcf_edma.h>
+
+static int asrc_major;
+static struct class *asrc_class;
+#define ASRC_PROC_PATH "driver/asrc"
+
+#define ASRC_RATIO_DECIMAL_DEPTH 26
+
+DEFINE_SPINLOCK(data_lock);
+DEFINE_SPINLOCK(input_int_lock);
+DEFINE_SPINLOCK(output_int_lock);
+
+#if 1
+#define DBG(x...)
+#else
+#define DBG(x...) printk(x)
+#endif
+
+/* ASRC control register bits */
+/* pair C/B/A automatic selection for processing options */
+#define ASRCTR_ATSC (0x01 << 22)
+#define ASRCTR_ATSB (0x01 << 21)
+#define ASRCTR_ATSA (0x01 << 20)
+#define ASRCTR_USRC (0x01 << 18) /* use ratio for pair C */
+#define ASRCTR_IDRC (0x01 << 17) /* use ideal ratio for pair C */
+#define ASRCTR_USRB (0x01 << 16) /* use ratio for pair B */
+#define ASRCTR_IDRB (0x01 << 15) /* use ideal ratio for pair B */
+#define ASRCTR_USRA (0x01 << 14) /* use ratio for pair A */
+#define ASRCTR_IDRA (0x01 << 13) /* use ideal ratio for pair A */
+#define ASRCTR_SRST (0x01 << 4) /* software reset */
+#define ASRCTR_ASREC (0x01 << 3) /* ASRC enable C */
+#define ASRCTR_ASREB (0x01 << 2) /* ASRC enable B */
+#define ASRCTR_ASREA (0x01 << 1) /* ASRC enable A */
+#define ASRCTR_ASRCEN (0x01 << 0) /* ASRC enable */
+
+/* clock divider register bits */
+#define AICPA 0 /* Input Clock Divider A Offset */
+#define AICDA 3 /* Input Clock Prescaler A Offset */
+#define AICPB 6 /* Input Clock Divider B Offset */
+#define AICDB 9 /* Input Clock Prescaler B Offset */
+#define AOCPA 12 /* Output Clock Divider A Offset */
+#define AOCDA 15 /* Output Clock Prescaler A Offset */
+#define AOCPB 18 /* Output Clock Divider B Offset */
+#define AOCDB 21 /* Output Clock Prescaler B Offset */
+#define AICPC 0 /* Input Clock Divider C Offset */
+#define AICDC 3 /* Input Clock Prescaler C Offset */
+#define AOCDC 6 /* Output Clock Prescaler C Offset */
+#define AOCPC 9 /* Output Clock Divider C Offset */
+
+char *asrc_pair_id[] = {
+ [0] = "ASRC RX PAIR A",
+ [1] = "ASRC TX PAIR A",
+ [2] = "ASRC RX PAIR B",
+ [3] = "ASRC TX PAIR B",
+ [4] = "ASRC RX PAIR C",
+ [5] = "ASRC TX PAIR C",
+};
+
+enum asrc_status {
+ ASRC_ASRSTR_AIDEA = 0x01,
+ ASRC_ASRSTR_AIDEB = 0x02,
+ ASRC_ASRSTR_AIDEC = 0x04,
+ ASRC_ASRSTR_AODFA = 0x08,
+ ASRC_ASRSTR_AODFB = 0x10,
+ ASRC_ASRSTR_AODFC = 0x20,
+ ASRC_ASRSTR_AOLE = 0x40,
+ ASRC_ASRSTR_FPWT = 0x80,
+ ASRC_ASRSTR_AIDUA = 0x100,
+ ASRC_ASRSTR_AIDUB = 0x200,
+ ASRC_ASRSTR_AIDUC = 0x400,
+ ASRC_ASRSTR_AODOA = 0x800,
+ ASRC_ASRSTR_AODOB = 0x1000,
+ ASRC_ASRSTR_AODOC = 0x2000,
+ ASRC_ASRSTR_AIOLA = 0x4000,
+ ASRC_ASRSTR_AIOLB = 0x8000,
+ ASRC_ASRSTR_AIOLC = 0x10000,
+ ASRC_ASRSTR_AOOLA = 0x20000,
+ ASRC_ASRSTR_AOOLB = 0x40000,
+ ASRC_ASRSTR_AOOLC = 0x80000,
+ ASRC_ASRSTR_ATQOL = 0x100000,
+ ASRC_ASRSTR_DSLCNT = 0x200000,
+};
+
+/* Sample rates are aligned with that defined in pcm.h file */
+static const unsigned char asrc_process_table[][8][2] = {
+ /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 128kHz 192kHz */
+/*8kHz*/
+ {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},
+/*11025Hz*/
+ {{0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},
+/*16kHz*/
+ {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},
+/*22050Hz*/
+ {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},
+/*32kHz*/
+ {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},},
+/*44.1kHz*/
+ {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},},
+/*48kHz*/
+ {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},},
+/*64kHz*/
+ {{0, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},},
+/*88.2kHz*/
+ {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},},
+/*96kHz*/
+ {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},},
+/*128kHz*/
+ {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},},
+/*192kHz*/
+ {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},},
+};
+
+static struct asrc_data *g_asrc_data;
+static struct proc_dir_entry *proc_asrc;
+static unsigned long asrc_vrt_base_addr;
+static unsigned long asrc_phy_base_addr;
+static struct imx_asrc_platform_data *mxc_asrc_data;
+struct dma_async_tx_descriptor *desc_in;
+struct dma_async_tx_descriptor *desc_out;
+static int asrc_dmarx[3];
+static int asrc_dmatx[3];
+#ifdef CONFIG_ARCH_MVF
+static struct dma_async_tx_descriptor dummy_desc;
+#endif
+
+/* The following tables map the relationship between asrc_inclk/asrc_outclk in
+ * mxc_asrc.h and the registers of ASRCSR
+ */
+static unsigned char input_clk_map_v1[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+static unsigned char output_clk_map_v1[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+static unsigned char input_clk_map_v2[] = {
+ 0, 1, 2, 3, 4, 5, 0xf, 0xf, 0xf, 8, 9, 0xa, 0xb, 0xc, 0xf, 0xd,
+};
+
+static unsigned char output_clk_map_v2[] = {
+ 8, 9, 0xa, 0, 0xc, 0x5, 0xf, 0xf, 0, 1, 2, 0xf, 0xf, 4, 0xf, 0xd,
+};
+
+static unsigned char input_clk_map_v3[] = {
+ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+ 0, 2, 3, 0xf, 5, 6, 1, 4, 7, 0xd, 0x0f, 9, 0xa, 8, 0xf, 0xc,
+};
+
+static unsigned char output_clk_map_v3[] = {
+ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+ 7, 0xd, 0xf, 0xf, 0xa, 6, 8, 1, 0, 2, 3, 4, 5, 9, 0xf, 0xc,
+};
+
+static unsigned char *input_clk_map, *output_clk_map;
+
+#ifdef CONFIG_ARCH_MVF
+static struct dma_async_tx_descriptor *imx_asrc_dma_config(u32 ch, u32 dma_addr,
+ void *buf_addr, u32 buf_len, int in);
+#endif
+/* Ideal Ratio calc */
+static int asrc_set_clock_ratio(enum asrc_pair_index index,
+ int input_sample_rate, int output_sample_rate)
+{
+ int i;
+ int integ = 0;
+ unsigned long reg_val = 0;
+
+ DBG("%s:in/out sample rate = %d/%d\n", __func__,
+ input_sample_rate, output_sample_rate);
+
+ if (output_sample_rate == 0)
+ return -1;
+ while (input_sample_rate >= output_sample_rate) {
+ input_sample_rate -= output_sample_rate;
+ integ++;
+ }
+ reg_val |= (integ << 26);
+
+ for (i = 1; i <= ASRC_RATIO_DECIMAL_DEPTH; i++) {
+ if ((input_sample_rate * 2) >= output_sample_rate) {
+ reg_val |= (1 << (ASRC_RATIO_DECIMAL_DEPTH - i));
+ input_sample_rate =
+ input_sample_rate * 2 - output_sample_rate;
+ } else
+ input_sample_rate = input_sample_rate << 1;
+
+ if (input_sample_rate == 0)
+ break;
+ }
+
+ __raw_writel(reg_val,
+ (asrc_vrt_base_addr + ASRC_ASRIDRLA_REG + (index << 3)));
+ __raw_writel((reg_val >> 24),
+ (asrc_vrt_base_addr + ASRC_ASRIDRHA_REG + (index << 3)));
+ return 0;
+}
+
+static int asrc_set_process_configuration(enum asrc_pair_index index,
+ int input_sample_rate,
+ int output_sample_rate)
+{
+ int i = 0, j = 0;
+ unsigned long reg;
+
+ DBG("%s:in/out sample rate = %d/%d\n", __func__,
+ input_sample_rate, output_sample_rate);
+
+ switch (input_sample_rate) {
+ case 8000:
+ i = 0;
+ break;
+ case 11025:
+ i = 1;
+ break;
+ case 16000:
+ i = 2;
+ break;
+ case 22050:
+ i = 3;
+ break;
+ case 32000:
+ i = 4;
+ break;
+ case 44100:
+ i = 5;
+ break;
+ case 48000:
+ i = 6;
+ break;
+ case 64000:
+ i = 7;
+ break;
+ case 88200:
+ i = 8;
+ break;
+ case 96000:
+ i = 9;
+ break;
+ case 128000:
+ i = 10;
+ break;
+ case 192000:
+ i = 11;
+ break;
+ default:
+ return -1;
+ }
+
+ switch (output_sample_rate) {
+ case 32000:
+ j = 0;
+ break;
+ case 44100:
+ j = 1;
+ break;
+ case 48000:
+ j = 2;
+ break;
+ case 64000:
+ j = 3;
+ break;
+ case 88200:
+ j = 4;
+ break;
+ case 96000:
+ j = 5;
+ break;
+ case 128000:
+ j = 6;
+ break;
+ case 192000:
+ j = 7;
+ break;
+ default:
+ return -1;
+ }
+
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG);
+ reg &= ~(0x0f << (6 + (index << 2)));
+ reg |=
+ ((asrc_process_table[i][j][0] << (6 + (index << 2))) |
+ (asrc_process_table[i][j][1] << (8 + (index << 2))));
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCFG_REG);
+
+ return 0;
+}
+
+static int asrc_get_asrck_clock_divider(int samplerate)
+{
+ unsigned int prescaler, divider;
+ unsigned int i;
+ unsigned int ratio, ra;
+ unsigned long bitclk = clk_get_rate(mxc_asrc_data->asrc_audio_clk);
+
+ DBG("%s:bitclk/sample rate = %lu/%d\n", __func__,
+ bitclk, samplerate);
+
+ ra = bitclk/samplerate;
+ ratio = ra;
+ /*calculate the prescaler*/
+ i = 0;
+ while (ratio > 8) {
+ i++;
+ ratio = ratio >> 1;
+ }
+ prescaler = i;
+ /*calculate the divider*/
+ if (i >= 1)
+ divider = ((ra + (1 << (i - 1)) - 1) >> i) - 1;
+ else
+ divider = ra - 1;
+
+ /*the totally divider is (2^prescaler)*divider*/
+ return (divider << 3) + prescaler;
+}
+
+int asrc_req_pair(int chn_num, enum asrc_pair_index *index)
+{
+ int err = 0;
+ unsigned long lock_flags;
+ spin_lock_irqsave(&data_lock, lock_flags);
+
+ if (chn_num > 2) {
+ if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active
+ || (chn_num > g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_max))
+ err = -EBUSY;
+ else {
+ *index = ASRC_PAIR_C;
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_num = chn_num;
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].active = 1;
+ }
+ } else {
+ if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active ||
+ (g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_max == 0)) {
+ if (g_asrc_data->asrc_pair[ASRC_PAIR_B].
+ active
+ || (g_asrc_data->asrc_pair[ASRC_PAIR_B].
+ chn_max == 0))
+ err = -EBUSY;
+ else {
+ *index = ASRC_PAIR_B;
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].chn_num = 2;
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].active = 1;
+ }
+ } else {
+ *index = ASRC_PAIR_A;
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_num = 2;
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].active = 1;
+ }
+ }
+
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+ return err;
+}
+EXPORT_SYMBOL(asrc_req_pair);
+
+void asrc_release_pair(enum asrc_pair_index index)
+{
+ unsigned long reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&data_lock, lock_flags);
+ g_asrc_data->asrc_pair[index].active = 0;
+ g_asrc_data->asrc_pair[index].overload_error = 0;
+ /********Disable PAIR*************/
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ reg &= ~(1 << (index + 1));
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+}
+EXPORT_SYMBOL(asrc_release_pair);
+
+int asrc_config_pair(struct asrc_config *config)
+{
+ int err = 0;
+ int reg, tmp, channel_num;
+ unsigned long lock_flags;
+
+ DBG("%s\n", __func__);
+
+ /* Set the channel number */
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+ spin_lock_irqsave(&data_lock, lock_flags);
+ g_asrc_data->asrc_pair[config->pair].chn_num = config->channel_num;
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+ reg &=
+ ~((0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)) <<
+ (mxc_asrc_data->channel_bits * config->pair));
+ if (mxc_asrc_data->channel_bits > 3)
+ channel_num = config->channel_num;
+ else
+ channel_num = (config->channel_num + 1) / 2;
+ tmp = channel_num << (mxc_asrc_data->channel_bits * config->pair);
+ reg |= tmp;
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+#if 0
+ /* Set processing options */
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ /* disable automatic selection for processing options */
+ reg &= ~(1 << (20 + config->pair));
+ /* enable idea ratio for pair */
+ reg |= (0x3 << (13 + (config->pair << 1)));
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+#endif
+ /* Set the clock source */
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCSR_REG);
+ tmp = ~(0x0f << (config->pair << 2));
+ reg &= tmp;
+ tmp = ~(0x0f << (12 + (config->pair << 2)));
+ reg &= tmp;
+ reg |=
+ ((input_clk_map[config->inclk] << (config->pair << 2)) |
+ (output_clk_map[config->outclk] << (12 + (config->pair << 2))));
+
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCSR_REG);
+
+ /* config Misc Control Register 1 for Pair X
+ * all FIFO data align LSB without sign extension
+ */
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRMCR1A_REG +
+ config->pair * 4);
+ reg &= ~((0x0F << 8) | 0x06);
+ if (config->word_width == 16) {
+ /* 16-bits output */
+ reg |= 0x01;
+ /* 16-bits input */
+ reg |= (0x01 << 9);
+ } else {
+ /* 24-bits output */
+ reg &= ~0x01;
+ /* 24-bits input */
+ }
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRMCR1A_REG +
+ config->pair * 4);
+
+ /* Default Clock Divider Setting */
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG);
+ if (config->pair == ASRC_PAIR_A) {
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG);
+ reg &= 0xfc0fc0;
+ /* Input Part */
+ if ((config->inclk & 0x0f) == INCLK_SPDIF_RX)
+ reg |= 7 << AICPA;
+ else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX)
+ reg |= 6 << AICPA;
+ else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ input_sample_rate);
+ reg |= tmp << AICPA;
+ } else if ((config->inclk & 0x0F) == INCLK_NONE) {
+ /* do nothing, will use ideal ratio for convert */
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AICPA;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AICPA;
+ else
+ err = -EFAULT;
+ }
+ /* Output Part */
+ if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX)
+ reg |= 7 << AOCPA;
+ else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX)
+ reg |= 6 << AOCPA;
+ else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ output_sample_rate);
+ reg |= tmp << AOCPA;
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AOCPA;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AOCPA;
+ else
+ err = -EFAULT;
+ }
+
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG);
+
+ } else if (config->pair == ASRC_PAIR_B) {
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG);
+ reg &= 0x03f03f;
+ /* Input Part */
+ if ((config->inclk & 0x0f) == INCLK_SPDIF_RX)
+ reg |= 7 << AICPB;
+ else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX)
+ reg |= 6 << AICPB;
+ else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ input_sample_rate);
+ reg |= tmp << AICPB;
+ } else if ((config->inclk & 0x0F) == INCLK_NONE) {
+ /* pass through */
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AICPB;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AICPB;
+ else
+ err = -EFAULT;
+ }
+ /* Output Part */
+ if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX)
+ reg |= 7 << AOCPB;
+ else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX)
+ reg |= 6 << AOCPB;
+ else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ output_sample_rate);
+ reg |= tmp << AOCPB;
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AOCPB;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AOCPB;
+ else
+ err = -EFAULT;
+ }
+
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG);
+
+ } else {
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR2_REG);
+ reg &= 0;
+ /* Input Part */
+ if ((config->inclk & 0x0f) == INCLK_SPDIF_RX)
+ reg |= 7 << AICPC;
+ else if ((config->inclk & 0x0f) == INCLK_SPDIF_TX)
+ reg |= 6 << AICPC;
+ else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ input_sample_rate);
+ reg |= tmp << AICPC;
+ } else if ((config->inclk & 0x0F) == INCLK_NONE) {
+ /* pass through */
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AICPC;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AICPC;
+ else
+ err = -EFAULT;
+ }
+ /* Output Part */
+ if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX)
+ reg |= 7 << AOCPC;
+ else if ((config->outclk & 0x0f) == OUTCLK_SPDIF_TX)
+ reg |= 6 << AOCPC;
+ else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) {
+ tmp =
+ asrc_get_asrck_clock_divider(config->
+ output_sample_rate);
+ reg |= tmp << AOCPC;
+ } else {
+ if (config->word_width == 16 || config->word_width == 8)
+ reg |= 5 << AOCPC;
+ else if (config->word_width == 32
+ || config->word_width == 24)
+ reg |= 6 << AOCPC;
+ else
+ err = -EFAULT;
+ }
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR2_REG);
+
+ }
+
+ /* check whether ideal ratio is a must */
+ if ((config->inclk & 0x0f) == INCLK_NONE) {
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ /* clear ATSC */
+ reg &= ~(1 << (20 + config->pair));
+ reg |= (0x03 << (13 + (config->pair << 1)));
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ err = asrc_set_clock_ratio(config->pair,
+ config->input_sample_rate,
+ config->output_sample_rate);
+ if (err < 0)
+ return err;
+
+ err = asrc_set_process_configuration(config->pair,
+ config->input_sample_rate,
+ config->
+ output_sample_rate);
+ if (err < 0)
+ return err;
+
+ } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) {
+ if (config->input_sample_rate < 24000) {
+ pr_info
+ ("ASRC core clock cann't support sample rate %d\n",
+ config->input_sample_rate);
+ err = -EFAULT;
+ }
+ if (config->input_sample_rate == 44100
+ || config->input_sample_rate == 88200) {
+ pr_info
+ ("ASRC core clock cann't support sample rate %d\n",
+ config->input_sample_rate);
+ err = -EFAULT;
+ }
+ } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) {
+ if (config->output_sample_rate < 24000) {
+ pr_info
+ ("ASRC core clock cann't support sample rate %d\n",
+ config->input_sample_rate);
+ err = -EFAULT;
+ }
+ if (config->output_sample_rate == 44100
+ || config->output_sample_rate == 88200) {
+ pr_info
+ ("ASRC core clock cann't support sample rate %d\n",
+ config->input_sample_rate);
+ err = -EFAULT;
+ }
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(asrc_config_pair);
+
+void asrc_start_conv(enum asrc_pair_index index)
+{
+ int reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&data_lock, lock_flags);
+
+ __raw_writel(0x40, asrc_vrt_base_addr + ASRC_ASRIER_REG);
+
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+
+ reg |= ASRCTR_ASRCEN; /* enable the ASRC */
+ reg |= ASRCTR_ASREA << index; /* enable pair A or B or C convert */
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+
+ return;
+}
+EXPORT_SYMBOL(asrc_start_conv);
+
+void asrc_stop_conv(enum asrc_pair_index index)
+{
+ unsigned long reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&data_lock, lock_flags);
+
+ __raw_writel(0, asrc_vrt_base_addr + ASRC_ASRIER_REG);
+
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ reg &= ~(0x1 << (1 + index));
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+ return;
+}
+EXPORT_SYMBOL(asrc_stop_conv);
+
+/*!
+ * @brief asrc interrupt handler
+ */
+static irqreturn_t asrc_isr(int irq, void *dev_id)
+{
+ unsigned long status;
+
+ status = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG);
+
+ if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active == 1) {
+ if (status & ASRC_ASRSTR_ATQOL)
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |=
+ ASRC_TASK_Q_OVERLOAD;
+ if (status & ASRC_ASRSTR_AOOLA)
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |=
+ ASRC_OUTPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AIOLA)
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |=
+ ASRC_INPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AODOA)
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |=
+ ASRC_OUTPUT_BUFFER_OVERFLOW;
+ if (status & ASRC_ASRSTR_AIDUA)
+ g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |=
+ ASRC_INPUT_BUFFER_UNDERRUN;
+ } else if (g_asrc_data->asrc_pair[ASRC_PAIR_B].active == 1) {
+ if (status & ASRC_ASRSTR_ATQOL)
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |=
+ ASRC_TASK_Q_OVERLOAD;
+ if (status & ASRC_ASRSTR_AOOLB)
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |=
+ ASRC_OUTPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AIOLB)
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |=
+ ASRC_INPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AODOB)
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |=
+ ASRC_OUTPUT_BUFFER_OVERFLOW;
+ if (status & ASRC_ASRSTR_AIDUB)
+ g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |=
+ ASRC_INPUT_BUFFER_UNDERRUN;
+ } else if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active == 1) {
+ if (status & ASRC_ASRSTR_ATQOL)
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |=
+ ASRC_TASK_Q_OVERLOAD;
+ if (status & ASRC_ASRSTR_AOOLC)
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |=
+ ASRC_OUTPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AIOLC)
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |=
+ ASRC_INPUT_TASK_OVERLOAD;
+ if (status & ASRC_ASRSTR_AODOC)
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |=
+ ASRC_OUTPUT_BUFFER_OVERFLOW;
+ if (status & ASRC_ASRSTR_AIDUC)
+ g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |=
+ ASRC_INPUT_BUFFER_UNDERRUN;
+ }
+
+ /* try to clean the overload error */
+ __raw_writel(status, asrc_vrt_base_addr + ASRC_ASRSTR_REG);
+
+ return IRQ_HANDLED;
+}
+
+void asrc_get_status(struct asrc_status_flags *flags)
+{
+ unsigned long lock_flags;
+ enum asrc_pair_index index;
+
+ spin_lock_irqsave(&data_lock, lock_flags);
+ index = flags->index;
+ flags->overload_error = g_asrc_data->asrc_pair[index].overload_error;
+
+ spin_unlock_irqrestore(&data_lock, lock_flags);
+ return;
+}
+EXPORT_SYMBOL(asrc_get_status);
+
+static int mxc_init_asrc(void)
+{
+ DBG("%s\n", __func__);
+
+ __raw_writel(ASRCTR_ASRCEN, asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ /* do a soft reset for ASRC on Faraday */
+ __raw_writel(ASRCTR_SRST | ASRCTR_ASRCEN,
+ asrc_vrt_base_addr + ASRC_ASRCTR_REG);
+ /* wait for reset finished */
+ while (__raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG) & ASRCTR_SRST)
+ ;
+
+ /* Disable all interrupt */
+ __raw_writel(0x00, asrc_vrt_base_addr + ASRC_ASRIER_REG);
+
+ /* Default 6: 2: 2 channel assignment for pair C, B, A */
+ __raw_writel((0x06 << mxc_asrc_data->channel_bits *
+ 2) | (0x02 << mxc_asrc_data->channel_bits) | 0x02,
+ asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+
+ /* Parameter Registers recommended settings */
+ __raw_writel(0x7fffff, asrc_vrt_base_addr + ASRC_ASRPM1_REG);
+ __raw_writel(0x255555, asrc_vrt_base_addr + ASRC_ASRPM2_REG);
+ __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM3_REG);
+ __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM4_REG);
+ __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM5_REG);
+
+ __raw_writel(0x001f00, asrc_vrt_base_addr + ASRC_ASRTFR1);
+
+ /* 132M ipg bus clock */
+ __raw_writel(0x6C8, asrc_vrt_base_addr + ASRC_ASR76K_REG);
+ __raw_writel(0x935, asrc_vrt_base_addr + ASRC_ASR56K_REG);
+
+ return 0;
+}
+
+static int asrc_get_output_buffer_size(int input_buffer_size,
+ int input_sample_rate,
+ int output_sample_rate)
+{
+ int i = 0;
+ int outbuffer_size = 0;
+ int outsample = output_sample_rate;
+
+ while (outsample >= input_sample_rate) {
+ ++i;
+ outsample -= input_sample_rate;
+ }
+ outbuffer_size = i * input_buffer_size;
+ i = 1;
+ while (((input_buffer_size >> i) > 2) && (outsample != 0)) {
+ if (((outsample << 1) - input_sample_rate) >= 0) {
+ outsample = (outsample << 1) - input_sample_rate;
+ outbuffer_size += (input_buffer_size >> i);
+ } else {
+ outsample = outsample << 1;
+ }
+ i++;
+ }
+ outbuffer_size = (outbuffer_size >> 5) << 5;
+
+ return outbuffer_size;
+}
+
+static irqreturn_t asrc_input_dma_callback(int ch, void *data)
+{
+ struct asrc_pair_params *params;
+ struct dma_block *block;
+ u32 dma_addr;
+ void *buf_addr;
+
+ params = (struct asrc_pair_params *)data;
+
+ params->input_queue_empty--;
+ if (!list_empty(&params->input_queue)) {
+ block =
+ list_entry(params->input_queue.next,
+ struct dma_block, queue);
+ dma_addr =
+ (asrc_phy_base_addr + ASRC_ASRDIA_REG +
+ (params->index << 3));
+ buf_addr = block->dma_vaddr;
+ desc_in = imx_asrc_dma_config(
+ params->input_dma_channel,
+ dma_addr, buf_addr,
+ block->length, 1);
+ if (!desc_in)
+ printk(KERN_DEBUG "%s:%d failed to config dma\n",
+ __func__, __LINE__);
+ list_del(params->input_queue.next);
+ list_add_tail(&block->queue, &params->input_done_queue);
+ params->input_queue_empty++;
+
+ mcf_edma_enable_transfer(params->input_dma_channel);
+
+ }
+ params->input_counter++;
+ wake_up_interruptible(&params->input_wait_queue);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t asrc_output_dma_callback(int ch, void *data)
+{
+ struct asrc_pair_params *params;
+ struct dma_block *block;
+ u32 dma_addr;
+ void *buf_addr;
+
+ params = (struct asrc_pair_params *)data;
+
+ params->output_queue_empty--;
+
+ if (!list_empty(&params->output_queue)) {
+ block =
+ list_entry(params->output_queue.next,
+ struct dma_block, queue);
+
+ dma_addr =
+ (asrc_phy_base_addr + ASRC_ASRDOA_REG +
+ (params->index << 3));
+ buf_addr = block->dma_vaddr;
+ desc_out = imx_asrc_dma_config(
+ params->output_dma_channel,
+ dma_addr, buf_addr,
+ block->length, 0);
+ if (!desc_out)
+ printk(KERN_DEBUG "%s:%d failed to config dma\n",
+ __func__, __LINE__);
+
+ list_del(params->output_queue.next);
+ list_add_tail(&block->queue, &params->output_done_queue);
+ params->output_queue_empty++;
+
+ mcf_edma_enable_transfer(params->output_dma_channel);
+
+ }
+ params->output_counter++;
+
+ wake_up_interruptible(&params->output_wait_queue);
+ return IRQ_HANDLED;
+}
+
+static void mxc_free_dma_buf(struct asrc_pair_params *params)
+{
+ int i;
+
+ if (params->input_dma[0].dma_vaddr != NULL) {
+ kfree(params->input_dma[0].dma_vaddr);
+ for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) {
+ params->input_dma[i].dma_vaddr = NULL;
+ params->input_dma[i].dma_paddr = 0;
+ }
+ }
+ if (params->output_dma[0].dma_vaddr != NULL) {
+ kfree(params->output_dma[0].dma_vaddr);
+ for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) {
+ params->output_dma[i].dma_vaddr = NULL;
+ params->output_dma[i].dma_paddr = 0;
+ }
+ }
+
+ return;
+}
+
+static int mxc_allocate_dma_buf(struct asrc_pair_params *params)
+{
+ int i;
+
+ params->input_dma[0].dma_vaddr = NULL;
+ params->input_dma[0].dma_vaddr =
+ kzalloc(PAGE_ALIGN(params->input_buffer_size) *
+ ASRC_DMA_BUFFER_NUM,
+ GFP_DMA/*GFP_KERNEL*/);
+
+ if (!params->input_dma[0].dma_vaddr) {
+ mxc_free_dma_buf(params);
+ pr_info("can't allocate buff\n");
+ return -ENOBUFS;
+ }
+
+ for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) {
+ params->input_dma[i].dma_vaddr =
+ (unsigned char *)
+ ((unsigned long)params->input_dma[0].dma_vaddr +
+ i * PAGE_ALIGN(params->input_buffer_size));
+ params->input_dma[i].dma_paddr =
+ virt_to_dma(NULL, params->input_dma[i].dma_vaddr);
+ if (params->input_dma[i].dma_vaddr == NULL) {
+ mxc_free_dma_buf(params);
+ pr_info("can't allocate input buff\n");
+ return -ENOBUFS;
+ }
+ }
+
+ params->output_dma[0].dma_vaddr = NULL;
+ params->output_dma[0].dma_vaddr =
+ kzalloc(PAGE_ALIGN(params->output_buffer_size) *
+ ASRC_DMA_BUFFER_NUM,
+ GFP_DMA/*GFP_KERNEL*/);
+ if (!params->output_dma[0].dma_vaddr) {
+ mxc_free_dma_buf(params);
+ pr_info("can't allocate buff\n");
+ return -ENOBUFS;
+ }
+
+ for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) {
+ params->output_dma[i].dma_vaddr =
+ (unsigned char *)
+ ((unsigned long)params->output_dma[0].dma_vaddr +
+ i * PAGE_ALIGN(params->output_buffer_size));
+ params->output_dma[i].dma_paddr =
+ virt_to_dma(NULL, params->output_dma[i].dma_vaddr);
+ if (params->output_dma[i].dma_vaddr == NULL) {
+ mxc_free_dma_buf(params);
+ pr_info("can't allocate output buff\n");
+ return -ENOBUFS;
+ }
+ }
+ return 0;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = param;
+ return true;
+}
+
+
+#ifdef CONFIG_ARCH_MVF
+static struct dma_async_tx_descriptor *imx_asrc_dma_config(u32 ch,
+ u32 dma_addr, void *buf_addr,
+ u32 buf_len, int in)
+{
+ struct scatterlist sg;
+ u32 src, dst;
+ int ret;
+
+ if (buf_len <= 0) {
+ printk(KERN_INFO " asrc config dma size error %d\n", buf_len);
+ return NULL;
+ }
+ /* make sure the eDMA channel is stopped */
+ mcf_edma_stop_transfer(ch);
+
+ sg_init_one(&sg, buf_addr, buf_len);
+ ret = dma_map_sg(NULL, &sg, 1, in ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (ret != 1)
+ return NULL;
+ if (in) { /* input: buffer to engine */
+ dst = dma_addr; /* reg IN */
+ src = (u32)sg_phys(&sg);
+ mcf_edma_set_tcd_params(ch,
+ src, dst,
+ MCF_EDMA_TCD_ATTR_SSIZE_32BIT |
+ MCF_EDMA_TCD_ATTR_DSIZE_32BIT,
+ 4, /* soff */
+ 4 * 32, /* nbyte */
+ 0, /* slast */
+ buf_len/(4 * 32), /* citer */
+ buf_len/(4 * 32), /* biter */
+ 0, 0, /* doff, dlast */
+ 1, 1, 0); /* major int, disable request, sg */
+ /*DBG("config dma(%d) IN, cnt=%d, buf=%#x\n", ch, buf_len/4, src);*/
+
+ } else { /* output: engine to buffer */
+ dst = (u32)sg_phys(&sg);
+ src = dma_addr;
+ mcf_edma_set_tcd_params(ch,
+ src, dst,
+ MCF_EDMA_TCD_ATTR_SSIZE_32BIT |
+ MCF_EDMA_TCD_ATTR_DSIZE_32BIT,
+ 0,
+ 4 * 32,
+ 0,
+ buf_len/(4 * 32),
+ buf_len/(4 * 32),
+ 4, 0,
+ 1, 1, 0);
+ /*DBG("config dma(%d) OUT, cnt=%d, buf=%#x\n", ch, buf_len/4, dst);*/
+ }
+
+ return &dummy_desc;
+}
+#endif
+
+/*!
+ * asrc interface - ioctl function
+ *
+ * @param inode struct inode *
+ *
+ * @param file struct file *
+ *
+ * @param cmd unsigned int
+ *
+ * @param arg unsigned long
+ *
+ * @return 0 success, ENODEV for invalid device instance,
+ * -1 for other errors.
+ */
+static long asrc_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct asrc_pair_params *params;
+ params = file->private_data;
+ if (down_interruptible(&params->busy_lock))
+ return -EBUSY;
+ switch (cmd) {
+ case ASRC_REQ_PAIR:
+ {
+ struct asrc_req req;
+ if (copy_from_user(&req, (void __user *)arg,
+ sizeof(struct asrc_req))) {
+ err = -EFAULT;
+ break;
+ }
+ err = asrc_req_pair(req.chn_num, &req.index);
+ if (err < 0)
+ break;
+ params->pair_hold = 1;
+ params->index = req.index;
+ if (copy_to_user
+ ((void __user *)arg, &req, sizeof(struct asrc_req)))
+ err = -EFAULT;
+
+ break;
+ }
+ case ASRC_CONFIG_PAIR:
+ {
+ struct asrc_config config;
+ u32 rx_id, tx_id;
+ char *rx_name, *tx_name;
+ if (copy_from_user
+ (&config, (void __user *)arg,
+ sizeof(struct asrc_config))) {
+ err = -EFAULT;
+ break;
+ }
+ err = asrc_config_pair(&config);
+ if (err < 0)
+ break;
+ params->output_buffer_size =
+ asrc_get_output_buffer_size(config.
+ dma_buffer_size,
+ config.
+ input_sample_rate,
+ config.
+ output_sample_rate);
+ params->input_buffer_size = config.dma_buffer_size;
+ if (config.buffer_num > ASRC_DMA_BUFFER_NUM)
+ params->buffer_num = ASRC_DMA_BUFFER_NUM;
+ else
+ params->buffer_num = config.buffer_num;
+ err = mxc_allocate_dma_buf(params);
+ if (err < 0)
+ break;
+
+ /* TBD - need to update when new SDMA interface ready */
+ if (config.pair == ASRC_PAIR_A) {
+ rx_id = asrc_dmarx[ASRC_PAIR_A];
+ tx_id = asrc_dmatx[ASRC_PAIR_A];
+ rx_name = asrc_pair_id[0];
+ tx_name = asrc_pair_id[1];
+ } else if (config.pair == ASRC_PAIR_B) {
+ rx_id = asrc_dmarx[ASRC_PAIR_B];
+ tx_id = asrc_dmatx[ASRC_PAIR_B];
+ rx_name = asrc_pair_id[2];
+ tx_name = asrc_pair_id[3];
+ } else {
+ rx_id = asrc_dmarx[ASRC_PAIR_C];
+ tx_id = asrc_dmatx[ASRC_PAIR_C];
+ rx_name = asrc_pair_id[4];
+ tx_name = asrc_pair_id[5];
+ }
+
+ params->input_dma_channel = mcf_edma_request_channel(
+ tx_id,
+ asrc_input_dma_callback, NULL,
+ 6, params, NULL, tx_name);
+
+ params->output_dma_channel = mcf_edma_request_channel(
+ rx_id,
+ asrc_output_dma_callback, NULL,
+ 6, params, NULL, rx_name);
+
+ params->input_queue_empty = 0;
+ params->output_queue_empty = 0;
+ INIT_LIST_HEAD(&params->input_queue);
+ INIT_LIST_HEAD(&params->input_done_queue);
+ INIT_LIST_HEAD(&params->output_queue);
+ INIT_LIST_HEAD(&params->output_done_queue);
+ init_waitqueue_head(&params->input_wait_queue);
+ init_waitqueue_head(&params->output_wait_queue);
+
+ if (copy_to_user
+ ((void __user *)arg, &config,
+ sizeof(struct asrc_config)))
+ err = -EFAULT;
+ break;
+ }
+ case ASRC_QUERYBUF:
+ {
+ struct asrc_querybuf buffer;
+ if (copy_from_user
+ (&buffer, (void __user *)arg,
+ sizeof(struct asrc_querybuf))) {
+ err = -EFAULT;
+ break;
+ }
+ buffer.input_offset =
+ (unsigned long)params->input_dma[buffer.
+ buffer_index].
+ dma_paddr;
+ buffer.input_length = params->input_buffer_size;
+ buffer.output_offset =
+ (unsigned long)params->output_dma[buffer.
+ buffer_index].
+ dma_paddr;
+ buffer.output_length = params->output_buffer_size;
+ if (copy_to_user
+ ((void __user *)arg, &buffer,
+ sizeof(struct asrc_querybuf)))
+ err = -EFAULT;
+ break;
+ }
+ case ASRC_RELEASE_PAIR:
+ {
+ enum asrc_pair_index index;
+ if (copy_from_user
+ (&index, (void __user *)arg,
+ sizeof(enum asrc_pair_index))) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (params->input_dma_channel)
+ mcf_edma_free_channel(
+ params->input_dma_channel, params);
+ if (params->output_dma_channel)
+ mcf_edma_free_channel(
+ params->output_dma_channel, params);
+
+ mxc_free_dma_buf(params);
+ asrc_release_pair(index);
+ params->pair_hold = 0;
+ break;
+ }
+ case ASRC_Q_INBUF: /* queue input buffer */
+ {
+ struct asrc_buffer buf;
+ struct dma_block *block;
+ u32 dma_addr;
+ void *buf_addr;
+ unsigned long lock_flags;
+ if (copy_from_user
+ (&buf, (void __user *)arg,
+ sizeof(struct asrc_buffer))) {
+ err = -EFAULT;
+ break;
+ }
+
+ spin_lock_irqsave(&input_int_lock, lock_flags);
+ params->input_dma[buf.index].index = buf.index;
+ params->input_dma[buf.index].length = buf.length;
+ list_add_tail(&params->input_dma[buf.index].
+ queue, &params->input_queue);
+ if (params->input_queue_empty == 0) {
+ block =
+ list_entry(params->input_queue.next,
+ struct dma_block, queue);
+ dma_addr =
+ (asrc_phy_base_addr + ASRC_ASRDIA_REG +
+ (params->index << 3));
+ buf_addr = block->dma_vaddr;
+ spin_unlock_irqrestore(&input_int_lock,
+ lock_flags);
+ desc_in = imx_asrc_dma_config(
+ params->input_dma_channel,
+ dma_addr, buf_addr,
+ block->length, 1);
+
+ spin_lock_irqsave(&input_int_lock, lock_flags);
+ params->input_queue_empty++;
+ list_del(params->input_queue.next);
+ list_add_tail(&block->queue,
+ &params->input_done_queue);
+ if (params->asrc_active)
+ mcf_edma_enable_transfer(
+ params->input_dma_channel);
+ }
+
+ spin_unlock_irqrestore(&input_int_lock, lock_flags);
+ break;
+ }
+ case ASRC_DQ_INBUF:{ /* dequeue input buffer */
+ struct asrc_buffer buf;
+ struct dma_block *block;
+ unsigned long lock_flags;
+ if (copy_from_user
+ (&buf, (void __user *)arg,
+ sizeof(struct asrc_buffer))) {
+ err = -EFAULT;
+ break;
+ }
+
+ /* if ASRC is inactive, nonsense to DQ buffer */
+ if (params->asrc_active == 0) {
+ err = -EFAULT;
+ buf.buf_valid = ASRC_BUF_NA;
+ if (copy_to_user
+ ((void __user *)arg, &buf,
+ sizeof(struct asrc_buffer)))
+ err = -EFAULT;
+ break;
+ }
+
+ if (!wait_event_interruptible_timeout
+ (params->input_wait_queue,
+ params->input_counter != 0, 10 * HZ)) {
+ pr_info
+ ("ASRC_DQ_INBUF timeout counter %x\n",
+ params->input_counter);
+ err = -ETIME;
+ break;
+ } else if (signal_pending(current)) {
+ pr_info("ASRC_DQ_INBUF interrupt received\n");
+ err = -ERESTARTSYS;
+ break;
+ }
+ spin_lock_irqsave(&input_int_lock, lock_flags);
+ params->input_counter--;
+ block =
+ list_entry(params->input_done_queue.next,
+ struct dma_block, queue);
+ list_del(params->input_done_queue.next);
+ spin_unlock_irqrestore(&input_int_lock, lock_flags);
+ buf.index = block->index;
+ buf.length = block->length;
+ buf.buf_valid = ASRC_BUF_AV;
+ if (copy_to_user
+ ((void __user *)arg, &buf,
+ sizeof(struct asrc_buffer)))
+ err = -EFAULT;
+
+ break;
+ }
+ case ASRC_Q_OUTBUF:{ /* queue output buffer */
+ struct asrc_buffer buf;
+ struct dma_block *block;
+ u32 dma_addr;
+ void *buf_addr;
+ unsigned long lock_flags;
+ if (copy_from_user
+ (&buf, (void __user *)arg,
+ sizeof(struct asrc_buffer))) {
+ err = -EFAULT;
+ break;
+ }
+
+ spin_lock_irqsave(&output_int_lock, lock_flags);
+ params->output_dma[buf.index].index = buf.index;
+ params->output_dma[buf.index].length = buf.length;
+ list_add_tail(&params->output_dma[buf.index].
+ queue, &params->output_queue);
+ if (params->output_queue_empty == 0) {
+ block =
+ list_entry(params->output_queue.
+ next, struct dma_block, queue);
+ dma_addr =
+ (asrc_phy_base_addr + ASRC_ASRDOA_REG +
+ (params->index << 3));
+ buf_addr = block->dma_vaddr;
+ spin_unlock_irqrestore(&output_int_lock,
+ lock_flags);
+
+ desc_out = imx_asrc_dma_config(
+ params->output_dma_channel,
+ dma_addr, buf_addr, block->length, 0);
+ spin_lock_irqsave(&output_int_lock, lock_flags);
+
+ list_del(params->output_queue.next);
+ list_add_tail(&block->queue,
+ &params->output_done_queue);
+ params->output_queue_empty++;
+
+ if (params->asrc_active)
+ mcf_edma_enable_transfer(
+ params->output_dma_channel);
+ }
+
+ spin_unlock_irqrestore(&output_int_lock, lock_flags);
+ break;
+ }
+ case ASRC_DQ_OUTBUF:{
+ struct asrc_buffer buf;
+ struct dma_block *block;
+ unsigned long lock_flags;
+ if (copy_from_user
+ (&buf, (void __user *)arg,
+ sizeof(struct asrc_buffer))) {
+ err = -EFAULT;
+ break;
+ }
+
+ /* if ASRC is inactive, nonsense to DQ buffer */
+ if (params->asrc_active == 0) {
+ buf.buf_valid = ASRC_BUF_NA;
+ err = -EFAULT;
+ if (copy_to_user
+ ((void __user *)arg, &buf,
+ sizeof(struct asrc_buffer)))
+ err = -EFAULT;
+ break;
+ }
+
+ if (!wait_event_interruptible_timeout
+ (params->output_wait_queue,
+ params->output_counter != 0, 10 * HZ)) {
+ pr_info
+ ("ASRC_DQ_OUTBUF timeout counter %x\n",
+ params->output_counter);
+ err = -ETIME;
+ break;
+ } else if (signal_pending(current)) {
+ pr_info("ASRC_DQ_INBUF interrupt received\n");
+ err = -ERESTARTSYS;
+ break;
+ }
+ spin_lock_irqsave(&output_int_lock, lock_flags);
+ params->output_counter--;
+ block =
+ list_entry(params->output_done_queue.next,
+ struct dma_block, queue);
+ list_del(params->output_done_queue.next);
+ spin_unlock_irqrestore(&output_int_lock, lock_flags);
+ buf.index = block->index;
+ buf.length = block->length;
+ buf.buf_valid = ASRC_BUF_AV;
+ if (copy_to_user
+ ((void __user *)arg, &buf,
+ sizeof(struct asrc_buffer)))
+ err = -EFAULT;
+
+ break;
+ }
+ case ASRC_START_CONV:{
+ enum asrc_pair_index index;
+ unsigned long lock_flags;
+
+ if (copy_from_user
+ (&index, (void __user *)arg,
+ sizeof(enum asrc_pair_index))) {
+ err = -EFAULT;
+ break;
+ }
+ spin_lock_irqsave(&input_int_lock, lock_flags);
+ if (params->input_queue_empty == 0) {
+ err = -EFAULT;
+ pr_info
+ ("ASRC_START_CONV - no block available\n");
+ break;
+ }
+ spin_unlock_irqrestore(&input_int_lock, lock_flags);
+ params->asrc_active = 1;
+
+ mcf_edma_enable_transfer(params->output_dma_channel);
+ mcf_edma_enable_transfer(params->input_dma_channel);
+
+ asrc_start_conv(index);
+ break;
+ }
+ case ASRC_STOP_CONV:{
+ enum asrc_pair_index index;
+
+ if (copy_from_user
+ (&index, (void __user *)arg,
+ sizeof(enum asrc_pair_index))) {
+ err = -EFAULT;
+ break;
+ }
+ mcf_edma_stop_transfer(params->input_dma_channel);
+ mcf_edma_stop_transfer(params->output_dma_channel);
+ asrc_stop_conv(index);
+ params->asrc_active = 0;
+ break;
+ }
+ case ASRC_STATUS:{
+ struct asrc_status_flags flags;
+ if (copy_from_user
+ (&flags, (void __user *)arg,
+ sizeof(struct asrc_status_flags))) {
+ err = -EFAULT;
+ break;
+ }
+ asrc_get_status(&flags);
+ if (copy_to_user
+ ((void __user *)arg, &flags,
+ sizeof(struct asrc_status_flags)))
+ err = -EFAULT;
+ break;
+ }
+ case ASRC_FLUSH:{
+ /* flush input dma buffer */
+ unsigned long lock_flags;
+ u32 rx_id, tx_id;
+ char *rx_name, *tx_name;
+
+ spin_lock_irqsave(&input_int_lock, lock_flags);
+ while (!list_empty(&params->input_queue))
+ list_del(params->input_queue.next);
+ while (!list_empty(&params->input_done_queue))
+ list_del(params->input_done_queue.next);
+ params->input_counter = 0;
+ params->input_queue_empty = 0;
+ spin_unlock_irqrestore(&input_int_lock, lock_flags);
+
+ /* flush output dma buffer */
+ spin_lock_irqsave(&output_int_lock, lock_flags);
+ while (!list_empty(&params->output_queue))
+ list_del(params->output_queue.next);
+ while (!list_empty(&params->output_done_queue))
+ list_del(params->output_done_queue.next);
+ params->output_counter = 0;
+ params->output_queue_empty = 0;
+ spin_unlock_irqrestore(&output_int_lock, lock_flags);
+
+ /* release DMA and request again */
+ mcf_edma_free_channel(params->input_dma_channel,
+ params);
+ mcf_edma_free_channel(params->output_dma_channel,
+ params);
+
+ if (params->index == ASRC_PAIR_A) {
+ rx_id = asrc_dmarx[ASRC_PAIR_A];
+ tx_id = asrc_dmatx[ASRC_PAIR_A];
+ rx_name = asrc_pair_id[0];
+ tx_name = asrc_pair_id[1];
+ } else if (params->index == ASRC_PAIR_B) {
+ rx_id = asrc_dmarx[ASRC_PAIR_B];
+ tx_id = asrc_dmatx[ASRC_PAIR_B];
+ rx_name = asrc_pair_id[2];
+ tx_name = asrc_pair_id[3];
+ } else {
+ rx_id = asrc_dmarx[ASRC_PAIR_C];
+ tx_id = asrc_dmatx[ASRC_PAIR_C];
+ rx_name = asrc_pair_id[4];
+ tx_name = asrc_pair_id[5];
+ }
+ params->input_dma_channel = mcf_edma_request_channel(
+ tx_id, asrc_input_dma_callback, NULL,
+ 6, params, NULL, NULL);
+
+ if (params->input_dma_channel == 0) {
+ pr_err("unable to get input channel %d\n",
+ tx_id);
+ err = -EBUSY;
+ }
+
+ params->output_dma_channel = mcf_edma_request_channel(
+ rx_id, asrc_output_dma_callback, NULL,
+ 6, params, NULL, NULL);
+
+ if (params->output_dma_channel == 0) {
+ pr_err("unable to get output channel %d\n",
+ rx_id);
+ err = -EBUSY;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ up(&params->busy_lock);
+ return err;
+}
+
+/*!
+ * asrc interface - open function
+ *
+ * @param inode structure inode *
+ *
+ * @param file structure file *
+ *
+ * @return status 0 success, ENODEV invalid device instance,
+ * ENOBUFS failed to allocate buffer, ERESTARTSYS interrupted by user
+ */
+static int mxc_asrc_open(struct inode *inode, struct file *file)
+{
+ int err = 0;
+ struct asrc_pair_params *pair_params;
+
+ clk_enable(mxc_asrc_data->asrc_core_clk);
+ if (signal_pending(current))
+ return -EINTR;
+ pair_params = kzalloc(sizeof(struct asrc_pair_params), GFP_KERNEL);
+ if (pair_params == NULL) {
+ pr_debug("Failed to allocate pair_params\n");
+ err = -ENOBUFS;
+ }
+
+ sema_init(&pair_params->busy_lock, 1);
+ file->private_data = pair_params;
+
+ return err;
+}
+
+/*!
+ * asrc interface - close function
+ *
+ * @param inode struct inode *
+ * @param file structure file *
+ *
+ * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error
+ */
+static int mxc_asrc_close(struct inode *inode, struct file *file)
+{
+ struct asrc_pair_params *pair_params;
+ pair_params = file->private_data;
+
+ if (pair_params->asrc_active == 1) {
+ asrc_stop_conv(pair_params->index);
+ wake_up_interruptible(&pair_params->input_wait_queue);
+ wake_up_interruptible(&pair_params->output_wait_queue);
+ }
+ if (pair_params->pair_hold == 1) {
+ mcf_edma_free_channel(pair_params->input_dma_channel,
+ pair_params);
+ mcf_edma_free_channel(pair_params->output_dma_channel,
+ pair_params);
+ mxc_free_dma_buf(pair_params);
+ asrc_release_pair(pair_params->index);
+ }
+
+ kfree(pair_params);
+ file->private_data = NULL;
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+
+ return 0;
+}
+
+/*!
+ * asrc interface - mmap function
+ *
+ * @param file structure file *
+ *
+ * @param vma structure vm_area_struct *
+ *
+ * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error
+ */
+static int mxc_asrc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned long size;
+ int res = 0;
+ size = vma->vm_end - vma->vm_start;
+
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ if (remap_pfn_range(vma, vma->vm_start,
+ vma->vm_pgoff, size, vma->vm_page_prot))
+ return -ENOBUFS;
+
+ vma->vm_flags &= ~VM_IO;
+ return res;
+}
+
+static const struct file_operations asrc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = asrc_ioctl,
+ .mmap = mxc_asrc_mmap,
+ .open = mxc_asrc_open,
+ .release = mxc_asrc_close,
+};
+
+static int asrc_read_proc_attr(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ unsigned long reg;
+ int len = 0;
+ clk_enable(mxc_asrc_data->asrc_core_clk);
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+
+ len += sprintf(page, "ANCA: %d\n",
+ (int)(reg &
+ (0xFFFFFFFF >>
+ (32 - mxc_asrc_data->channel_bits))));
+ len +=
+ sprintf(page + len, "ANCB: %d\n",
+ (int)((reg >> mxc_asrc_data->
+ channel_bits) & (0xFFFFFFFF >> (32 -
+ mxc_asrc_data->
+ channel_bits))));
+ len +=
+ sprintf(page + len, "ANCC: %d\n",
+ (int)((reg >> (mxc_asrc_data->channel_bits * 2)) &
+ (0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits))));
+
+ if (off > len)
+ return 0;
+
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+
+ return min(count, len - (int)off);
+}
+
+static int asrc_write_proc_attr(struct file *file, const char *buffer,
+ unsigned long count, void *data)
+{
+ char buf[50];
+ unsigned long reg;
+ int na, nb, nc;
+ int total;
+ if (count > 48)
+ return -EINVAL;
+ if (copy_from_user(buf, buffer, count)) {
+ pr_debug("Attr proc write, Failed to copy buffer from user\n");
+ return -EFAULT;
+ }
+
+ clk_enable(mxc_asrc_data->asrc_core_clk);
+ reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+ sscanf(buf, "ANCA: %d\nANCB: %d\nANCC: %d", &na, &nb, &nc);
+ if (mxc_asrc_data->channel_bits > 3)
+ total = 10;
+ else
+ total = 5;
+ if ((na + nb + nc) != total) {
+ pr_info("Wrong ASRCNR settings\n");
+ return -EFAULT;
+ }
+ reg = na | (nb << mxc_asrc_data->
+ channel_bits) | (nc << (mxc_asrc_data->channel_bits * 2));
+
+ clk_enable(mxc_asrc_data->asrc_core_clk);
+ __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG);
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+
+ return count;
+}
+
+static void asrc_proc_create(void)
+{
+ struct proc_dir_entry *proc_attr;
+ proc_asrc = proc_mkdir(ASRC_PROC_PATH, NULL);
+ if (proc_asrc) {
+ proc_attr = create_proc_entry("ChSettings",
+ S_IFREG | S_IRUGO |
+ S_IWUSR, proc_asrc);
+ if (proc_attr) {
+ proc_attr->read_proc = asrc_read_proc_attr;
+ proc_attr->write_proc = asrc_write_proc_attr;
+ proc_attr->size = 48;
+ proc_attr->uid = proc_attr->gid = 0;
+ } else {
+ remove_proc_entry(ASRC_PROC_PATH, NULL);
+ pr_info("Failed to create proc attribute entry\n");
+ }
+ } else {
+ pr_info("ASRC: Failed to create proc entry %s\n",
+ ASRC_PROC_PATH);
+ }
+}
+
+/*!
+ * Entry point for the asrc device
+ *
+ * @param pdev Pionter to the registered platform device
+ * @return Error code indicating success or failure
+ */
+static int mxc_asrc_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct resource *res;
+ struct device *temp_class;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ g_asrc_data = kzalloc(sizeof(struct asrc_data), GFP_KERNEL);
+
+ if (g_asrc_data == NULL) {
+ pr_info("Failed to allocate g_asrc_data\n");
+ return -ENOMEM;
+ }
+
+ g_asrc_data->asrc_pair[0].chn_max = 2;
+ g_asrc_data->asrc_pair[1].chn_max = 2;
+ g_asrc_data->asrc_pair[2].chn_max = 6;
+ g_asrc_data->asrc_pair[0].overload_error = 0;
+ g_asrc_data->asrc_pair[1].overload_error = 0;
+ g_asrc_data->asrc_pair[2].overload_error = 0;
+
+ asrc_major = register_chrdev(asrc_major, "mxc_asrc", &asrc_fops);
+ if (asrc_major < 0) {
+ pr_info("Unable to register asrc device\n");
+ err = -EBUSY;
+ goto error;
+ }
+
+ asrc_class = class_create(THIS_MODULE, "mxc_asrc");
+ if (IS_ERR(asrc_class)) {
+ err = PTR_ERR(asrc_class);
+ goto err_out_chrdev;
+ }
+
+ temp_class = device_create(asrc_class, NULL, MKDEV(asrc_major, 0),
+ NULL, "mxc_asrc");
+ if (IS_ERR(temp_class)) {
+ err = PTR_ERR(temp_class);
+ goto err_out_class;
+ }
+
+ asrc_phy_base_addr = res->start;
+ asrc_vrt_base_addr =
+ (unsigned long)ioremap(res->start, res->end - res->start + 1);
+
+ mxc_asrc_data =
+ (struct imx_asrc_platform_data *)pdev->dev.platform_data;
+
+ clk_enable(mxc_asrc_data->asrc_core_clk);
+
+ switch (mxc_asrc_data->clk_map_ver) {
+ case 1:
+ input_clk_map = &input_clk_map_v1[0];
+ output_clk_map = &output_clk_map_v1[0];
+ break;
+ case 3:
+ input_clk_map = &input_clk_map_v3[0];
+ output_clk_map = &output_clk_map_v3[0];
+ break;
+ case 2:
+ default:
+ input_clk_map = &input_clk_map_v2[0];
+ output_clk_map = &output_clk_map_v2[0];
+ break;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx1");
+ if (res)
+ asrc_dmatx[0] = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx1");
+ if (res)
+ asrc_dmarx[0] = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx2");
+ if (res)
+ asrc_dmatx[1] = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx2");
+ if (res)
+ asrc_dmarx[1] = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx3");
+ if (res)
+ asrc_dmatx[2] = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx3");
+ if (res)
+ asrc_dmarx[2] = res->start;
+
+ irq = platform_get_irq(pdev, 0);
+ if (request_irq(irq, asrc_isr, 0, "asrc", NULL))
+ return -1;
+
+ asrc_proc_create();
+ err = mxc_init_asrc();
+ if (err < 0)
+ goto err_out_class;
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+ goto out;
+
+err_out_class:
+ clk_disable(mxc_asrc_data->asrc_core_clk);
+ device_destroy(asrc_class, MKDEV(asrc_major, 0));
+ class_destroy(asrc_class);
+err_out_chrdev:
+ unregister_chrdev(asrc_major, "mxc_asrc");
+error:
+ kfree(g_asrc_data);
+out:
+ pr_info("MVF ASRC registered\n");
+
+ return err;
+}
+
+/*!
+ * Exit asrc device
+ *
+ * @param pdev Pionter to the registered platform device
+ * @return Error code indicating success or failure
+ */
+static int mxc_asrc_remove(struct platform_device *pdev)
+{
+ int irq = platform_get_irq(pdev, 0);
+ free_irq(irq, NULL);
+ kfree(g_asrc_data);
+ mxc_asrc_data = NULL;
+ iounmap((unsigned long __iomem *)asrc_vrt_base_addr);
+ remove_proc_entry("ChSettings", proc_asrc);
+ remove_proc_entry(ASRC_PROC_PATH, NULL);
+ device_destroy(asrc_class, MKDEV(asrc_major, 0));
+ class_destroy(asrc_class);
+ unregister_chrdev(asrc_major, "mxc_asrc");
+ return 0;
+}
+
+/*! mxc asrc driver definition
+ *
+ */
+static struct platform_driver mxc_asrc_driver = {
+ .driver = {
+ .name = "mxc_asrc",
+ },
+ .probe = mxc_asrc_probe,
+ .remove = mxc_asrc_remove,
+};
+
+/*!
+ * Register asrc driver
+ *
+ */
+static __init int asrc_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&mxc_asrc_driver);
+ return ret;
+}
+
+/*!
+ * Exit and free the asrc data
+ *
+ */
+static void __exit asrc_exit(void)
+{
+ platform_driver_unregister(&mxc_asrc_driver);
+ return;
+}
+
+module_init(asrc_init);
+module_exit(asrc_exit);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Asynchronous Sample Rate Converter");
+MODULE_LICENSE("GPL");