summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/dai
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sprd/dai')
-rw-r--r--sound/soc/sprd/dai/Kconfig35
-rw-r--r--sound/soc/sprd/dai/Makefile11
-rw-r--r--sound/soc/sprd/dai/i2s/Kconfig2
-rw-r--r--sound/soc/sprd/dai/i2s/Makefile6
-rw-r--r--sound/soc/sprd/dai/i2s/i2s.c1441
-rw-r--r--sound/soc/sprd/dai/sprd-dmaengine-pcm.c1069
-rw-r--r--sound/soc/sprd/dai/sprd-dmaengine-pcm.h56
-rw-r--r--sound/soc/sprd/dai/sprd-pcm.c1025
-rw-r--r--sound/soc/sprd/dai/sprd-pcm.h49
-rw-r--r--sound/soc/sprd/dai/vaudio/Kconfig2
-rw-r--r--sound/soc/sprd/dai/vaudio/Makefile6
-rw-r--r--sound/soc/sprd/dai/vaudio/vaudio.c157
-rw-r--r--sound/soc/sprd/dai/vaudio/vaudio.h22
-rw-r--r--sound/soc/sprd/dai/vbc/Kconfig19
-rw-r--r--sound/soc/sprd/dai/vbc/dfm.h27
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/Kconfig3
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/Makefile6
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c1571
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h29
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c229
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h219
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc.c637
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc.h22
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/Kconfig20
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/Makefile6
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c2596
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h30
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c374
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h299
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc.c807
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc.h22
31 files changed, 10797 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/Kconfig b/sound/soc/sprd/dai/Kconfig
new file mode 100644
index 00000000..eb68a154
--- /dev/null
+++ b/sound/soc/sprd/dai/Kconfig
@@ -0,0 +1,35 @@
+menu "SPRD AUDIO DMA"
+config SND_SOC_SPRD_AUDIO_DMA
+ tristate
+
+config SND_SOC_SPRD_AUDIO_DMA_ENGINE
+ bool "Use DMA driver based on dmaengine"
+ depends on DMA_SPRD
+ default y if DMA_SPRD
+ help
+ Say Y if you want to use dma driver based on linux dmaengine.
+
+config SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ bool "Use IRAM when sound playback"
+ help
+ Say Y if you want use IRAM replacment RAM when playback.
+ sometimes, use IRAM maybe reduce power consumption.
+ but, it will cause system more busy, becuase the buffer smaller.
+
+config SND_SOC_SPRD_IRAM_BACKUP
+ bool "Backup IRAM when sound playback"
+ depends on SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ help
+ Say Y if you want backup IRAM when playback. sometimes,
+ IRAM maybe shared with other modules,
+ then you need to open this.
+
+config SND_SOC_SPRD_AUDIO_USE_AON_DMA
+ bool "Use AON DMA when sound playback"
+ depends on SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM && DMA_SPRD && SND_SOC_SPRD_AUDIO_DMA_ENGINE
+ help
+ Say Y if you want to use AON DMA when playback, sometimes,
+ AON DMA maybe reduce power consumption. But AON DMA can only
+ access IRAM, so we must depend on IRAM switch.
+
+endmenu
diff --git a/sound/soc/sprd/dai/Makefile b/sound/soc/sprd/dai/Makefile
new file mode 100644
index 00000000..e3daa77f
--- /dev/null
+++ b/sound/soc/sprd/dai/Makefile
@@ -0,0 +1,11 @@
+# SPRD DMA Support
+SPRD_SOUND_TREE := sound/soc/sprd/
+KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai/vbc/ -I$(SPRD_SOUND_TREE)/dai/vaudio/
+
+ifneq ($(CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE),)
+ snd-soc-sprd-audio-dma-objs := sprd-dmaengine-pcm.o
+else
+ snd-soc-sprd-audio-dma-objs := sprd-pcm.o
+endif
+
+obj-$(CONFIG_SND_SOC_SPRD_AUDIO_DMA) += snd-soc-sprd-audio-dma.o
diff --git a/sound/soc/sprd/dai/i2s/Kconfig b/sound/soc/sprd/dai/i2s/Kconfig
new file mode 100644
index 00000000..76da44c9
--- /dev/null
+++ b/sound/soc/sprd/dai/i2s/Kconfig
@@ -0,0 +1,2 @@
+config SND_SOC_SPRD_I2S
+ tristate #"I2S"
diff --git a/sound/soc/sprd/dai/i2s/Makefile b/sound/soc/sprd/dai/i2s/Makefile
new file mode 100644
index 00000000..c6d03d85
--- /dev/null
+++ b/sound/soc/sprd/dai/i2s/Makefile
@@ -0,0 +1,6 @@
+# SPRD I2S Support
+SPRD_SOUND_TREE := sound/soc/sprd/
+KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai
+
+snd-soc-sprd-i2s-objs := i2s.o
+obj-$(CONFIG_SND_SOC_SPRD_I2S) += snd-soc-sprd-i2s.o
diff --git a/sound/soc/sprd/dai/i2s/i2s.c b/sound/soc/sprd/dai/i2s/i2s.c
new file mode 100644
index 00000000..a29819a9
--- /dev/null
+++ b/sound/soc/sprd/dai/i2s/i2s.c
@@ -0,0 +1,1441 @@
+/*
+ * sound/soc/sprd/dai/i2s/i2s.c
+ *
+ * SPRD SoC CPU-DAI -- SpreadTrum SOC DAI i2s.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" I2S ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <soc/sprd/hardware.h>
+
+#include "sprd-asoc-common.h"
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE
+#include "sprd-pcm.h"
+#else
+#include "sprd-dmaengine-pcm.h"
+#endif
+#include <soc/sprd/i2s.h>
+
+/* register offset */
+#define IIS_TXD (0x0000)
+#define IIS_CLKD (0x0004)
+#define IIS_CTRL0 (0x0008)
+#define IIS_CTRL1 (0x000C)
+#define IIS_CTRL2 (0x0010)
+#define IIS_CTRL3 (0x0014)
+#define IIS_INT_IEN (0x0018)
+#define IIS_INT_CLR (0x001C)
+#define IIS_INT_RAW (0x0020)
+#define IIS_INT_STS (0x0024)
+#define IIS_STS1 (0x0028)
+#define IIS_STS2 (0x002C)
+#define IIS_STS3 (0x0030)
+#define IIS_DSPWAIT (0x0034)
+#define IIS_CTRL4 (0x0038)
+#define IIS_STS4 (0x003C)
+
+#define I2S_REG(i2s, offset) ((unsigned long)((i2s)->membase + (offset)))
+#define I2S_PHY_REG(i2s, offset) (((unsigned int)(i2s)->memphys + (offset)))
+
+static unsigned int membase = 0;
+static struct i2s_config *dup_config = NULL;
+
+#define CMD_BUFFER_LENGTH 64
+
+
+struct i2s_rtx {
+ int dma_no;
+};
+#define DAI_NAME_SIZE 20
+struct i2s_priv {
+ int id;
+ int hw_port;
+ struct device *dev;
+ struct list_head list;
+ struct i2s_rtx rx;
+ struct i2s_rtx tx;
+ atomic_t open_cnt;
+ char dai_name[DAI_NAME_SIZE];
+ int irq_no;
+ void __iomem *membase;
+ unsigned int *memphys;
+ unsigned int reg_size;
+ struct i2s_config config;
+ struct clk *i2s_clk;
+ struct snd_soc_dai_driver i2s_dai_driver[2];
+ int i2s_type;
+};
+
+static struct sprd_pcm_dma_params i2s_pcm_stereo_out = {
+ .name = "I2S PCM Stereo out",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = WORD_WIDTH,
+ .src_step = 4,
+ .des_step = 0,
+ },
+};
+
+static struct sprd_pcm_dma_params i2s_pcm_stereo_in = {
+ .name = "I2S PCM Stereo in",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = WORD_WIDTH,
+ .src_step = 0,
+ .des_step = 4,
+ },
+};
+
+/* default pcm config */
+const static struct i2s_config def_pcm_config = {
+ .hw_port = 0,
+ .fs = 8000,
+ .slave_timeout = 0xF11,
+ .bus_type = PCM_BUS,
+ .byte_per_chan = I2S_BPCH_16,
+ .mode = I2S_MASTER,
+ .lsb = I2S_MSB,
+ .rtx_mode = I2S_RTX_MODE,
+ .sync_mode = I2S_SYNC,// I2S_SYNC better!
+ .lrck_inv = I2S_L_LEFT,
+ .clk_inv = I2S_CLK_N,
+ .pcm_bus_mode = I2S_SHORT_FRAME,
+ .pcm_slot = 0x1,
+ .pcm_cycle = 1,
+ .tx_watermark = 12,
+ .rx_watermark = 20,
+};
+
+/* default i2s config */
+const static struct i2s_config def_i2s_config = {
+ .hw_port = 0,
+ .fs = 32000,
+ .slave_timeout = 0xF11,
+ .bus_type = I2S_BUS,
+ .byte_per_chan = I2S_BPCH_16,
+ .mode = I2S_SLAVE,
+ .lsb = I2S_MSB,
+ .rtx_mode = I2S_RX_MODE,
+ .sync_mode = I2S_LRCK,
+ .lrck_inv = I2S_L_LEFT,
+ .clk_inv = I2S_CLK_N,
+ .i2s_bus_mode = I2S_MSBJUSTFIED,
+ .tx_watermark = 12,
+ .rx_watermark = 20,
+};
+
+static DEFINE_SPINLOCK(i2s_lock);
+
+static inline int i2s_reg_read(unsigned long reg)
+{
+ return __raw_readl((void *__iomem)reg);
+}
+
+static inline void i2s_reg_raw_write(unsigned long reg, int val)
+{
+ __raw_writel(val, (void *__iomem)reg);
+}
+
+#if 0
+static int i2s_reg_write(unsigned int reg, int val)
+{
+ spin_lock(&i2s_lock);
+ i2s_reg_raw_write(reg, val);
+ spin_unlock(&i2s_lock);
+ sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val,
+ i2s_reg_read(reg));
+ return 0;
+}
+#endif
+
+
+static inline struct i2s_dai *to_info(struct snd_soc_dai *dai)
+{
+ return snd_soc_dai_get_drvdata(dai);
+}
+
+/*
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static int i2s_reg_update(unsigned long reg, int val, int mask)
+{
+ int new, old;
+ spin_lock(&i2s_lock);
+ old = i2s_reg_read(reg);
+ new = (old & ~mask) | (val & mask);
+ i2s_reg_raw_write(reg, new);
+ spin_unlock(&i2s_lock);
+ sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new,
+ i2s_reg_read(reg));
+ return old != new;
+}
+
+static int i2s_global_disable(struct i2s_priv *i2s)
+{
+ sp_asoc_pr_dbg("%s\n", __func__);
+ return arch_audio_i2s_disable(i2s->hw_port);
+}
+
+static int i2s_global_enable(struct i2s_priv *i2s)
+{
+ sp_asoc_pr_dbg("%s\n", __func__);
+ arch_audio_i2s_enable(i2s->hw_port);
+ return arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL);
+}
+
+static int i2s_soft_reset(struct i2s_priv *i2s)
+{
+ sp_asoc_pr_dbg("%s\n", __func__);
+ return arch_audio_i2s_reset(i2s->hw_port);
+}
+
+static int i2s_calc_clk(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int bit_clk;
+ int cycle;
+ int source_clk;
+ int bit;
+ int val;
+ struct clk *clk_parent;
+ switch (config->fs) {
+ case 8000:
+ case 16000:
+ case 32000:
+ clk_parent = clk_get(NULL, "clk_128m");
+ break;
+ case 9600:
+ case 12000:
+ case 24000:
+ case 48000:
+ clk_parent = clk_get(NULL, "clk_76m8");
+ break;
+ default:
+ pr_err("ERR:I2S Can't Support %d Clock\n", config->fs);
+ return -ENOTSUPP;
+ }
+
+ if (IS_ERR(clk_parent)) {
+ int ret = PTR_ERR(clk_parent);
+ pr_err("ERR:I2S Get Clock Source Error %d!\n", ret);
+ return ret;
+ }
+ clk_set_parent(i2s->i2s_clk, clk_parent);
+ source_clk = clk_get_rate(clk_parent);
+ clk_set_rate(i2s->i2s_clk, source_clk);
+ clk_put(clk_parent);
+
+ sp_asoc_pr_dbg("I2S Source Clock is %d HZ\n", source_clk);
+ cycle = (PCM_BUS == config->bus_type) ? (config->pcm_cycle + 1) : 2;
+ bit = 8 << config->byte_per_chan;
+ bit_clk = config->fs * cycle * bit;
+ sp_asoc_pr_dbg("I2S BIT Clock is %d HZ\n", bit_clk);
+ if (source_clk % (bit_clk << 1))
+ return -ENOTSUPP;
+ val = (source_clk / bit_clk) >> 1;
+ --val;
+ return val;
+}
+
+static int i2s_set_clkd(struct i2s_priv *i2s)
+{
+ int shift = 0;
+ int mask = 0xFFFF << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CLKD);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = i2s_calc_clk(i2s);
+ if (val < 0)
+ return val;
+ i2s_reg_update(reg, val, mask);
+ return 0;
+}
+
+static int i2s_set_clk_m_n(struct i2s_priv *i2s)
+{
+ /* This will support futher */
+ return -ENOTSUPP;
+}
+
+static void i2s_set_bus_type(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(15);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (PCM_BUS == config->bus_type) ? mask : 0, mask);
+}
+
+static void i2s_set_byte_per_channal(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 4;
+ int mask = 0x3 << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = config->byte_per_chan;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_mode(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(3);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s mode %d\n", __func__, config->mode);
+ i2s_reg_update(reg, (I2S_SLAVE == config->mode) ? mask : 0, mask);
+}
+
+static void i2s_set_lsb(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(2);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (I2S_LSB == config->lsb) ? mask : 0, mask);
+}
+
+static void i2s_set_rtx_mode(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 6;
+ int mask = 0x3 << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = config->rtx_mode;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_sync_mode(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(9);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (I2S_SYNC == config->sync_mode) ? mask : 0, mask);
+}
+
+static void i2s_set_lrck_invert(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(10);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (I2S_L_RIGTH == config->lrck_inv) ? mask : 0, mask);
+}
+
+static void i2s_set_clk_invert(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(11);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (I2S_CLK_R == config->clk_inv) ? mask : 0, mask);
+}
+
+static void i2s_set_i2s_bus_mode(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(8);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg, (I2S_COMPATIBLE == config->i2s_bus_mode) ? mask : 0,
+ mask);
+}
+
+static void i2s_set_pcm_bus_mode(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int mask = BIT(8);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ i2s_reg_update(reg,
+ (I2S_SHORT_FRAME == config->pcm_bus_mode) ? mask : 0,
+ mask);
+}
+
+static void i2s_set_pcm_slot(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 0;
+ int mask = 0x7 << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL2);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = config->pcm_slot;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_pcm_cycle(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 3;
+ int mask = 0x7F << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL2);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = config->pcm_cycle;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_rx_watermark(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 0;
+ int mask = 0x1F1F << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL3);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ /* full watermark */
+ val = config->rx_watermark;
+ /* empty watermark */
+ val |= (I2S_FIFO_DEPTH - config->rx_watermark) << 8;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_tx_watermark(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 0;
+ int mask = 0x1F1F << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL4);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ /* empty watermark */
+ val = config->tx_watermark << 8;
+ /* full watermark */
+ val |= I2S_FIFO_DEPTH - config->tx_watermark;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static void i2s_set_slave_timeout(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ int shift = 0;
+ int mask = 0xFFF << shift;
+ int val = 0;
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL1);
+ sp_asoc_pr_dbg("%s\n", __func__);
+ val = config->slave_timeout;
+ i2s_reg_update(reg, val << shift, mask);
+}
+
+static int i2s_get_dma_data_width(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ if (PCM_BUS == config->bus_type) {
+ if (config->byte_per_chan == I2S_BPCH_16) {
+ return SHORT_WIDTH;
+ }
+ }
+ return WORD_WIDTH;
+}
+
+static int i2s_get_dma_step(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ if (PCM_BUS == config->bus_type) {
+ if (config->byte_per_chan == I2S_BPCH_16) {
+ return 2;
+ }
+ }
+ return 4;
+}
+
+static int i2s_get_data_position(struct i2s_priv *i2s)
+{
+ struct i2s_config *config = &i2s->config;
+ if (PCM_BUS == config->bus_type) {
+ if (config->byte_per_chan == I2S_BPCH_16) {
+ if (I2S_SLAVE == config->mode) {
+ return 2;
+ }
+ }
+ }
+ return 0;
+}
+
+static int i2s_config_rate(struct i2s_priv *i2s)
+{
+ int ret = 0;
+ struct i2s_config *config = &i2s->config;
+ if (I2S_MASTER == config->mode) {
+ ret = i2s_set_clkd(i2s);
+ }
+ if (ret) {
+ ret = i2s_set_clk_m_n(i2s);
+ }
+ return ret;
+}
+
+static int i2s_config_apply(struct i2s_priv *i2s)
+{
+ int ret = 0;
+ struct i2s_config *config = &i2s->config;
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ if (I2S_BPCH_8 == config->byte_per_chan) {
+ pr_err("ERR:The I2S can't Support 8byte Mode in this code\n");
+ ret = -ENOTSUPP;
+ }
+
+ i2s_set_bus_type(i2s);
+ i2s_set_mode(i2s);
+ i2s_set_byte_per_channal(i2s);
+ i2s_set_rtx_mode(i2s);
+ i2s_set_sync_mode(i2s);
+ i2s_set_lsb(i2s);
+ i2s_set_lrck_invert(i2s);
+ i2s_set_clk_invert(i2s);
+ i2s_set_rx_watermark(i2s);
+ i2s_set_tx_watermark(i2s);
+ if (I2S_SLAVE == config->mode) {
+ i2s_set_slave_timeout(i2s);
+ }
+ if (I2S_BUS == config->bus_type) {
+ i2s_set_i2s_bus_mode(i2s);
+ }
+ if (PCM_BUS == config->bus_type) {
+ i2s_set_pcm_bus_mode(i2s);
+ i2s_set_pcm_slot(i2s);
+ i2s_set_pcm_cycle(i2s);
+ }
+
+ ret = i2s_config_rate(i2s);
+
+ return ret;
+}
+
+static void i2s_dma_ctrl(struct i2s_priv *i2s, int enable)
+{
+ int mask = BIT(14);
+ unsigned long reg = I2S_REG(i2s, IIS_CTRL0);
+ sp_asoc_pr_dbg("%s Enable = %d\n", __func__, enable);
+ if (!enable) {
+ if (atomic_read(&i2s->open_cnt) <= 0) {
+ i2s_reg_update(reg, 0, mask);
+ }
+ } else {
+ i2s_reg_update(reg, mask, mask);
+ }
+}
+
+static int i2s_close(struct i2s_priv *i2s)
+{
+ sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt));
+ if (atomic_dec_and_test(&i2s->open_cnt)) {
+ i2s_soft_reset(i2s);
+ i2s_global_disable(i2s);
+ if (!IS_ERR(i2s->i2s_clk)) {
+ clk_disable_unprepare(i2s->i2s_clk);
+ clk_put(i2s->i2s_clk);
+ }
+ }
+ return 0;
+}
+
+static int i2s_open(struct i2s_priv *i2s)
+{
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt));
+
+ atomic_inc(&i2s->open_cnt);
+ if (atomic_read(&i2s->open_cnt) == 1) {
+ i2s->i2s_clk =
+ clk_get(NULL, arch_audio_i2s_clk_name(i2s->hw_port));
+ if (IS_ERR(i2s->i2s_clk)) {
+ ret = PTR_ERR(i2s->i2s_clk);
+ pr_err("ERR:I2S Get clk Error %d!\n", ret);
+ return ret;
+ }
+ i2s_global_enable(i2s);
+ i2s_soft_reset(i2s);
+ i2s_dma_ctrl(i2s, 0);
+ ret = i2s_config_apply(i2s);
+ clk_prepare_enable(i2s->i2s_clk);
+ }
+
+ return ret;
+}
+
+static int i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct i2s_priv *i2s = to_info(dai);
+ printk("i2s->id %d \n",i2s->id);
+ dai->ac97_pdata = &i2s->config;
+ ret = i2s_open(i2s);
+ if (ret < 0) {
+ pr_err("ERR:I2S Open Error!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct i2s_config *config = dai->ac97_pdata;
+ struct i2s_priv *i2s = container_of(config, struct i2s_priv, config);
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ i2s_close(i2s);
+}
+
+static int i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct sprd_pcm_dma_params *dma_data;
+ struct i2s_config *config = dai->ac97_pdata;
+ struct i2s_priv *i2s = container_of(config, struct i2s_priv, config);
+
+ sp_asoc_pr_dbg("%s Port %d\n", __func__, i2s->hw_port);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_data = &i2s_pcm_stereo_out;
+ dma_data->channels[0] = i2s->tx.dma_no;
+ } else {
+ dma_data = &i2s_pcm_stereo_in;
+ dma_data->channels[0] = i2s->rx.dma_no;
+ }
+ dma_data->desc.datawidth = i2s_get_dma_data_width(i2s);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data->desc.src_step = i2s_get_dma_step(i2s);
+ else
+ dma_data->desc.des_step = i2s_get_dma_step(i2s);
+
+ dma_data->dev_paddr[0] =
+ I2S_PHY_REG(i2s, IIS_TXD) + i2s_get_data_position(i2s);
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ i2s->config.fs = params_rate(params);
+ ret = i2s_config_rate(i2s);
+ if (ret) {
+ return ret;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ ret = -ENOTSUPP;
+ pr_err("ERR:I2S Only Supports format S16_LE now!\n");
+ break;
+ }
+
+ if (params_channels(params) > 2) {
+ ret = -ENOTSUPP;
+ pr_err("ERR:I2S Can not Supports Grate 2 Channels\n");
+ }
+
+ return ret;
+}
+
+static int i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct i2s_config *config = dai->ac97_pdata;
+ struct i2s_priv *i2s = container_of(config, struct i2s_priv, config);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ i2s_dma_ctrl(i2s, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ i2s_dma_ctrl(i2s, 0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops sprd_i2s_dai_ops = {
+ .startup = i2s_startup,
+ .shutdown = i2s_shutdown,
+ .hw_params = i2s_hw_params,
+ .trigger = i2s_trigger,
+};
+static void i2s_config_setting(int index,int value,struct i2s_config *config)
+{
+ pr_debug("i2s_config_setting i2s index %d,value %d \n",index,value);
+ if (config == NULL) {
+ pr_err("i2s_config_setting config is NULL,error! \n");
+ return;
+ }
+ switch (index) {
+ case FS:
+ if (value >= SAMPLATE_MIN && value <= SAMPLATE_MAX)
+ config->fs = value;
+ break;
+ case HW_PROT:
+ if (value >= 0 && value <= 3)
+ config->hw_port = value;
+ break;
+ case SLAVE_TIMEOUT:
+ config->slave_timeout = value;
+ break;
+ case BUS_TYPE:
+ if (value == I2S_BUS || value == PCM_BUS)
+ config->bus_type = value;
+ break;
+ case BYTE_PER_CHAN:
+ if (value == I2S_BPCH_8 || value == I2S_BPCH_16 || value == I2S_BPCH_32)
+ config->byte_per_chan = value;
+ break;
+ case MODE:
+ if (value == I2S_MASTER || value == I2S_SLAVE)
+ config->mode = value;
+ break;
+ case LSB:
+ if (value == I2S_MSB || value == I2S_LSB)
+ config->lsb = value;
+ break;
+ case TRX_MODE:
+ if (value >= I2S_RTX_DIS && value <= I2S_RTX_MODE)
+ config->rtx_mode = value;
+ break;
+ case LRCK_INV:
+ if (value == I2S_L_LEFT || value == I2S_L_RIGTH)
+ config->lrck_inv = value;
+ break;
+ case SYNC_MODE:
+ if (value == I2S_SYNC || value == I2S_LRCK)
+ config->sync_mode = value;
+ break;
+ case CLK_INV:
+ if (value == I2S_CLK_N || value == I2S_CLK_R)
+ config->clk_inv = value;
+ break;
+ case I2S_BUS_MODE:
+ if (value == I2S_MSBJUSTFIED || value == I2S_COMPATIBLE)
+ config->i2s_bus_mode = value;
+ break;
+ case PCM_BUS_MODE:
+ if (value == I2S_SHORT_FRAME || value == I2S_LONG_FRAME)
+ config->pcm_bus_mode = value;
+ break;
+ case PCM_SLOT:
+ if (value >= 1 && value <= 4)
+ config->pcm_slot = value;
+ break;
+ case PCM_CYCLE:
+ if (value >= 0 && value <= 127)
+ config->pcm_cycle = value;
+ break;
+ case TX_WATERMARK:
+ if (value >= 0 && value <= I2S_FIFO_DEPTH)
+ config->tx_watermark = value;
+ break;
+ case RX_WATERMARK:
+ if (value >= 0 && value <= I2S_FIFO_DEPTH)
+ config->rx_watermark = value;
+ break;
+ default:
+ pr_err("i2s echo cmd is invalide \n");
+ break;
+ }
+}
+
+static int i2s_config_getting(int index,struct i2s_config *config)
+{
+ pr_debug("i2s_config_getting i2s index %d\n",index);
+ if (config == NULL) {
+ pr_err("i2s_config_getting config is NULL,error\n");
+ return -1;
+ }
+ switch (index) {
+ case FS:
+ return config->fs;
+ case HW_PROT:
+ return config->hw_port;
+ case SLAVE_TIMEOUT:
+ return config->slave_timeout;
+ case BUS_TYPE:
+ return config->bus_type;
+ case BYTE_PER_CHAN:
+ return config->byte_per_chan;
+ case MODE:
+ return config->mode;
+ case LSB:
+ return config->lsb;
+ case TRX_MODE:
+ return config->rtx_mode;
+ case LRCK_INV:
+ return config->lrck_inv;
+ case SYNC_MODE:
+ return config->sync_mode;
+ case CLK_INV:
+ return config->clk_inv;
+ case I2S_BUS_MODE:
+ return config->i2s_bus_mode;
+ case PCM_BUS_MODE:
+ return config->pcm_bus_mode;
+ case PCM_SLOT:
+ return config->pcm_slot;
+ case PCM_CYCLE:
+ return config->pcm_cycle;
+ case TX_WATERMARK:
+ return config->tx_watermark;
+ case RX_WATERMARK:
+ return config->rx_watermark;
+ default:
+ pr_err("i2s config index is invalide \n");
+ return -1;
+ }
+}
+
+static int get_index(char *line_first,char *line_end)
+{
+ char *line_index = line_first;
+ char *line_first_dummy = line_first;
+ if (line_first == NULL || line_end == NULL)
+ return -1;
+ for (line_first;line_first < line_end;line_first++) {
+ if (*line_first >= '0' && *line_first <= '9') {
+ *line_index = *line_first;
+ line_index++;
+ }
+ }
+ *line_index = '\0';
+ return simple_strtoul(line_first_dummy, NULL, 10);
+}
+
+static int get_index_value(char *line_first)
+{
+ char *line_index_value = line_first;
+ char *line_first_dummy = line_first;
+ if (line_first == NULL)
+ return -1;
+ pr_debug("i2s get_index_value %s\n",line_first);
+ for (line_first;;line_first++) {
+ if (*line_first == '\0')
+ break;
+ if (*line_first >= '0' && *line_first <= '9') {
+ *line_index_value = *line_first;
+ line_index_value ++;
+ }
+ }
+ *line_index_value = '\0';
+ return simple_strtoul(line_first_dummy, NULL, 10);
+}
+
+void i2s_debug_write(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ int index;
+ int index_value;
+ char *line = NULL;
+ char *sym_eq = NULL;
+ if (dup_config == NULL) {
+ pr_err("i2s_debug_write failed! \n");
+ return;
+ }
+ line = kzalloc(CMD_BUFFER_LENGTH,GFP_KERNEL);
+ if (!line) {
+ pr_err("malloc line buffer failed! \n");
+ return;
+ }
+ if (!snd_info_get_line(buffer, line, CMD_BUFFER_LENGTH)) {
+ pr_debug("i2s: input line %s\n",line);
+ sym_eq = strchr(line,'=');
+ if (sym_eq != NULL) {
+ index = get_index(line,sym_eq);
+ pr_debug("i2s: index %d\n",index);
+ index_value = get_index_value(++sym_eq);
+ pr_debug("i2s: index_value %d \n",index_value);
+ if (index >= 0 && index_value >= 0)
+ i2s_config_setting(index,index_value,dup_config);
+ } else if (strstr(line,"pcm")) {
+ memcpy(dup_config,&def_pcm_config,sizeof(def_pcm_config));
+ } else if (strstr(line,"i2s")) {
+ memcpy(dup_config,&def_i2s_config,sizeof(def_i2s_config));
+ } else {
+ pr_err("i2s:echo str fmt not right \n");
+ }
+ } else {
+ pr_err("i2s echo error \n");
+ }
+ kfree(line);
+ return;
+}
+
+void i2s_debug_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ if (dup_config == NULL) {
+ snd_iprintf(buffer, "\n\n dup_config is NUll,error!\n");
+ return;
+ }
+ snd_iprintf(buffer, "\n\n using examples :\n");
+ snd_iprintf(buffer, " 1)echo 0=33 > i2s-debug \n");
+ snd_iprintf(buffer, " 2)echo i2s > i2s-debug \n");
+ snd_iprintf(buffer, " 3)echo pcm >i2s-debug \n\n");
+ snd_iprintf(buffer, " 0 fs 0d:%d\n 1 hw_port 0d:%d\n 2 slave_timeout 0x%x\n",
+ dup_config->fs,dup_config->hw_port,dup_config->slave_timeout);
+ snd_iprintf(buffer, " 3 bus_type(****tip:bus_type 0 is iis,1 is pcm****) 0x%x\n 4 byte_per_chan 0x%x\n",
+ dup_config->bus_type,dup_config->byte_per_chan);
+ snd_iprintf(buffer, " 5 mode(****tip:mode 0 is master,1 is slave****) 0x%x\n 6 lsb 0x%x\n",
+ dup_config->mode,dup_config->lsb);
+ snd_iprintf(buffer, " 7 rtx_mode 0x%x\n 8 lrck_inv 0x%x\n",
+ dup_config->rtx_mode,dup_config->lrck_inv);
+ snd_iprintf(buffer, " 9 sync_mode 0x%x\n 10 clk_inv 0x%x\n",
+ dup_config->sync_mode,dup_config->clk_inv);
+ snd_iprintf(buffer, " 11 i2s_bus_mode 0x%x\n 12 pcm_bus_mode 0x%x\n",
+ dup_config->i2s_bus_mode,dup_config->pcm_bus_mode);
+ snd_iprintf(buffer, " 13 pcm_slot 0x%x\n 14 pcm_cycle 0x%x\n",
+ dup_config->pcm_slot,dup_config->pcm_cycle);
+ snd_iprintf(buffer, " 15 tx_watermark 0d:%d\n 16 rx_watermark 0d:%d\n\n",
+ dup_config->tx_watermark,dup_config->rx_watermark);
+}
+
+static inline int iis_reg_read(unsigned int reg)
+{
+ return __raw_readl((void *__iomem)reg);
+}
+
+void i2s_register_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ int reg;
+ if (membase == 0) {
+ snd_iprintf(buffer,"\n\n i2s membase is NULL \n");
+ return;
+ }
+ pr_debug("i2s membase 0x%x \n",membase);
+ snd_iprintf(buffer, "i2s register dump\n");
+ for (reg = IIS_TXD + membase; reg <= IIS_STS4 + membase; reg += 0x10) {
+ snd_iprintf(buffer, "0x%08x | 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ (reg - IIS_TXD -membase)
+ , iis_reg_read(reg + 0x00)
+ , iis_reg_read(reg + 0x04)
+ , iis_reg_read(reg + 0x08)
+ , iis_reg_read(reg + 0x0C)
+ );
+ }
+}
+
+int i2s_config_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ if (dup_config == NULL) {
+ pr_err("i2s_config_get return \n");
+ return 0;
+ }
+ ucontrol->value.integer.value[0] = i2s_config_getting(id,dup_config);
+ pr_debug("i2s_config_get return value %d,id %d\n",ucontrol->value.integer.value[0],id);
+ return 0;
+}
+
+int i2s_config_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ int id = FUN_REG(mc->reg);
+ if (dup_config == NULL) {
+ pr_err("i2s_config_set return \n");
+ return 0;
+ }
+ i2s_config_setting(id, ucontrol->value.integer.value[0],dup_config);
+ return 0;
+}
+
+static const struct snd_soc_component_driver sprd_i2s_component = {
+ .name = "i2s",
+};
+
+static int i2s_config_from_node(struct device_node *node, struct i2s_priv *i2s)
+{
+ if (!of_find_property(node, "sprd,config", NULL)) {
+ i2s->config = def_i2s_config;
+ sp_asoc_pr_dbg("Use I2S default config!\n");
+ } else {
+ u32 val;
+ struct device_node *config_node;
+ config_node = of_parse_phandle(node, "sprd,config", 0);
+ if (of_find_property(config_node, "sprd,def_pcm_config", NULL)) {
+ i2s->config = def_pcm_config;
+ sp_asoc_pr_dbg("Use PCM default config!\n");
+ } else {
+ i2s->config = def_i2s_config;
+ sp_asoc_pr_dbg("Use I2S default config!\n");
+ }
+
+ if (!of_property_read_u32
+ (config_node, "sprd,_hw_port", &val)) {
+ i2s->config.hw_port = val;
+ sp_asoc_pr_dbg("Change _hw_port to %d!\n", val);
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,fs", &val)) {
+ i2s->config.fs = val;
+ sp_asoc_pr_dbg("Change fs to %d!\n", val);
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,bus_type", &val)) {
+ if (val){
+ i2s->config.bus_type = PCM_BUS;
+ } else {
+ i2s->config.bus_type = I2S_BUS;
+ }
+ sp_asoc_pr_dbg("Change bus_type to %d!\n", val);
+ }
+
+ if (!of_property_read_u32
+ (config_node, "sprd,rtx_mode", &val)) {
+ i2s->config.rtx_mode = val;
+ sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val);
+ }
+
+ if (!of_property_read_u32
+ (config_node, "sprd,slave_timeout", &val)) {
+ i2s->config.slave_timeout = val;
+ sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val);
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,byte_per_chan", &val)) {
+ i2s->config.byte_per_chan = val;
+ sp_asoc_pr_dbg("Change byte per channal to %d!\n", val);
+ }
+ if (!of_property_read_u32(config_node, "sprd,slave_mode", &val)) {
+ if (val) {
+ i2s->config.mode = I2S_SLAVE;
+ } else {
+ i2s->config.mode = I2S_MASTER;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "Slave" : "Master");
+ }
+ if (!of_property_read_u32(config_node, "sprd,lsb", &val)) {
+ if (val) {
+ i2s->config.lsb = I2S_LSB;
+ } else {
+ i2s->config.lsb = I2S_MSB;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n", val ? "LSB" : "MSB");
+ }
+ if (!of_property_read_u32(config_node, "sprd,lrck", &val)) {
+ if (val) {
+ i2s->config.sync_mode = I2S_LRCK;
+ } else {
+ i2s->config.sync_mode = I2S_SYNC;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "LRCK" : "SYNC");
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,low_for_left", &val)) {
+ if (val) {
+ i2s->config.lrck_inv = I2S_L_LEFT;
+ } else {
+ i2s->config.lrck_inv = I2S_L_RIGTH;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "Low for Left" : "Low for Right");
+ }
+ if (!of_property_read_u32(config_node, "sprd,clk_inv", &val)) {
+ if (val) {
+ i2s->config.clk_inv = I2S_CLK_R;
+ } else {
+ i2s->config.clk_inv = I2S_CLK_N;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "CLK INV" : "CLK Normal");
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,i2s_compatible", &val)) {
+ if (val) {
+ i2s->config.i2s_bus_mode = I2S_COMPATIBLE;
+ } else {
+ i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "Compatible" : "MSBJustfied");
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,pcm_short_frame", &val)) {
+ if (val) {
+ i2s->config.pcm_bus_mode = I2S_SHORT_FRAME;
+ } else {
+ i2s->config.pcm_bus_mode = I2S_LONG_FRAME;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val ? "Short Frame" : "Long Frame");
+ }
+ if (!of_property_read_u32(config_node, "sprd,pcm_slot", &val)) {
+ i2s->config.pcm_slot = val;
+ sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val);
+ }
+ if (!of_property_read_u32(config_node, "sprd,pcm_cycle", &val)) {
+ i2s->config.pcm_cycle = val;
+ sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val);
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,tx_watermark", &val)) {
+ i2s->config.tx_watermark = val;
+ sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val);
+ }
+ if (!of_property_read_u32
+ (config_node, "sprd,rx_watermark", &val)) {
+ i2s->config.rx_watermark = val;
+ sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val);
+ }
+ of_node_put(config_node);
+ }
+ return 0;
+}
+
+static int i2s_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct i2s_priv *i2s;
+ struct resource *res;
+ struct device_node *node = pdev->dev.of_node;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_priv), GFP_KERNEL);
+ if (!i2s) {
+ pr_err("ERR:No memery!\n");
+ return -ENOMEM;
+ }
+ i2s->dev = &pdev->dev;
+ if (node) {
+ u32 val[2];
+ if (of_property_read_u32(node, "sprd,id", &val[0])) {
+ pr_err("ERR:Must give me the id!\n");
+ goto out;
+ }
+ i2s->id = val[0];
+ if (of_property_read_u32(node, "sprd,hw_port", &val[0])) {
+ pr_err("ERR:Must give me the hw_port!\n");
+ goto out;
+ }
+ i2s->hw_port = val[0];
+ if (of_property_read_u32_array(node, "sprd,base", &val[0], 2)) {
+ pr_err("ERR:Must give me the base address!\n");
+ goto out;
+ }
+ i2s->memphys = (unsigned int *)val[0];
+ i2s->reg_size= (unsigned int)val[1];
+ i2s->membase = (void __iomem *)ioremap_nocache(i2s->memphys, i2s->reg_size);
+ if (!i2s->membase) {
+ pr_err("ERR:i2s reg address ioremap_nocache error !\n");
+ return -EINVAL;
+ }
+ if (of_property_read_u32(node, "sprd,dma_rx_no", &val[0])) {
+ pr_err("ERR:Must give me the dma rx number!\n");
+ goto out;
+ }
+ i2s->rx.dma_no = val[0];
+ if (of_property_read_u32(node, "sprd,dma_tx_no", &val[0])) {
+ pr_err("ERR:Must give me the dma tx number!\n");
+ goto out;
+ }
+ i2s->tx.dma_no = val[0];
+
+ const char *dai_name = NULL;
+ if (of_property_read_string(node, "sprd,dai_name", &dai_name)) {
+ pr_err("ERR:Must give me the dai_name!\n");
+ //goto out;
+ }
+ if (dai_name != NULL)
+ strcpy(i2s->dai_name,dai_name);
+ printk("dt version i2s->dai_name %s \n",i2s->dai_name);
+ ret = i2s_config_from_node(node, i2s);
+
+ const char * config_type = NULL;
+ if (of_property_read_string(node, "sprd,config_type", &config_type)) {
+ i2s->config = def_pcm_config;
+ i2s->i2s_type = 0;
+ printk("warning:Must give me the config_type! default is pcm\n");
+ }
+ printk("i2s config_type %s \n",config_type);
+
+ if (config_type != NULL){
+ if (strcmp(config_type,"i2s") == 0){
+ i2s->config = def_i2s_config;
+ i2s->i2s_type = 1;
+ } else if (strcmp(config_type,"pcm") == 0) {
+ i2s->config = def_pcm_config;
+ i2s->i2s_type = 0;
+ } else {
+ pr_err("ERR:config_type must be pcm or i2s !\n");
+ }
+ }
+
+ if (!of_property_read_u32
+ (node, "sprd,_hw_port", &val[0])) {
+ i2s->config.hw_port = val[0];
+ sp_asoc_pr_dbg("Change _hw_port to %d!\n", val[0]);
+ }
+ if (!of_property_read_u32
+ (node, "sprd,fs", &val[0])) {
+ i2s->config.fs = val[0];
+ sp_asoc_pr_dbg("Change fs to %d!\n", val[0]);
+ }
+ if (!of_property_read_u32
+ (node, "sprd,bus_type", &val[0])) {
+ if (val[0]){
+ i2s->config.bus_type = PCM_BUS;
+ } else {
+ i2s->config.bus_type = I2S_BUS;
+ }
+ sp_asoc_pr_dbg("Change bus_type to %d!\n", val[0]);
+ }
+
+ if (!of_property_read_u32
+ (node, "sprd,rtx_mode", &val[0])) {
+ i2s->config.rtx_mode = val[0];
+ sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val[0]);
+ }
+
+ if (!of_property_read_u32
+ (node, "sprd,slave_timeout", &val[0])) {
+ i2s->config.slave_timeout = val[0];
+ sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val[0]);
+ }
+ if (!of_property_read_u32
+ (node, "sprd,byte_per_chan", &val[0])) {
+ i2s->config.byte_per_chan = val[0];
+ sp_asoc_pr_dbg("Change byte per channal to %d!\n", val[0]);
+ }
+ if (!of_property_read_u32(node, "sprd,slave_mode", &val[0])) {
+ if (val[0]) {
+ i2s->config.mode = I2S_SLAVE;
+ } else {
+ i2s->config.mode = I2S_MASTER;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "Slave" : "Master");
+ }
+ if (!of_property_read_u32(node, "sprd,lsb", &val[0])) {
+ if (val[0]) {
+ i2s->config.lsb = I2S_LSB;
+ } else {
+ i2s->config.lsb = I2S_MSB;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n", val[0] ? "LSB" : "MSB");
+ }
+ if (!of_property_read_u32(node, "sprd,lrck", &val[0])) {
+ if (val[0]) {
+ i2s->config.sync_mode = I2S_LRCK;
+ } else {
+ i2s->config.sync_mode = I2S_SYNC;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "LRCK" : "SYNC");
+ }
+ if (!of_property_read_u32
+ (node, "sprd,low_for_left", &val[0])) {
+ if (val[0]) {
+ i2s->config.lrck_inv = I2S_L_LEFT;
+ } else {
+ i2s->config.lrck_inv = I2S_L_RIGTH;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "Low for Left" : "Low for Right");
+ }
+ if (!of_property_read_u32(node, "sprd,clk_inv", &val[0])) {
+ if (val[0]) {
+ i2s->config.clk_inv = I2S_CLK_R;
+ } else {
+ i2s->config.clk_inv = I2S_CLK_N;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "CLK INV" : "CLK Normal");
+ }
+ if (i2s->i2s_type){
+ if (!of_property_read_u32
+ (node, "sprd,i2s_compatible", &val[0])) {
+ if (val[0]) {
+ i2s->config.i2s_bus_mode = I2S_COMPATIBLE;//I2S_SHORT_FRAME
+ } else {
+ i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED;//I2S_LONG_FRAME
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "Compatible" : "MSBJustfied");
+ }
+ }
+ if (!of_property_read_u32
+ (node, "sprd,pcm_short_frame", &val[0])) {
+ if (val[0]) {
+ i2s->config.pcm_bus_mode = I2S_SHORT_FRAME;
+ } else {
+ i2s->config.pcm_bus_mode = I2S_LONG_FRAME;
+ }
+ sp_asoc_pr_dbg("Change to %s!\n",
+ val[0] ? "Short Frame" : "Long Frame");
+ }
+ if (!i2s->i2s_type) {
+ if (!of_property_read_u32(node, "sprd,pcm_slot", &val[0])) {
+ i2s->config.pcm_slot = val[0];
+ sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val[0]);
+ }
+ if (!of_property_read_u32(node, "sprd,pcm_cycle", &val[0])) {
+ i2s->config.pcm_cycle = val[0];
+ sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val[0]);
+ }
+ }
+ if (!of_property_read_u32
+ (node, "sprd,tx_watermark", &val[0])) {
+ i2s->config.tx_watermark = val[0];
+ sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val[0]);
+ }
+ if (!of_property_read_u32
+ (node, "sprd,rx_watermark", &val[0])) {
+ i2s->config.rx_watermark = val[0];
+ sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val[0]);
+ }
+ } else {
+ i2s->config =
+ *((struct i2s_config *)(dev_get_platdata(&pdev->dev)));
+ i2s->id = pdev->id;
+ snprintf(i2s->dai_name,DAI_NAME_SIZE,"%s%d","i2s_bt_sco",i2s->id);
+ printk("not bt version i2s->dai_name %s \n",i2s->dai_name);
+ i2s->hw_port = i2s->config.hw_port;
+ sp_asoc_pr_dbg("i2s.%d(%d) default fs is (%d)\n", i2s->id,
+ i2s->hw_port, i2s->config.fs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2s->membase = (void __iomem *)res->start;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ i2s->memphys = (unsigned int *)res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ i2s->tx.dma_no = res->start;
+ i2s->rx.dma_no = res->end;
+ }
+
+ i2s->i2s_dai_driver[0].id = I2S_MAGIC_ID;
+ i2s->i2s_dai_driver[0].name = i2s->dai_name;
+ printk("i2s->i2s_dai_driver[0].name %s \n",i2s->i2s_dai_driver[0].name);
+ i2s->i2s_dai_driver[0].playback.channels_min = 1;
+ i2s->i2s_dai_driver[0].playback.channels_max = 2;
+ i2s->i2s_dai_driver[0].playback.rates = SNDRV_PCM_RATE_CONTINUOUS;
+ i2s->i2s_dai_driver[0].playback.rate_max = 96000;
+ i2s->i2s_dai_driver[0].playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
+
+ i2s->i2s_dai_driver[0].capture.channels_min = 1;
+ i2s->i2s_dai_driver[0].capture.channels_max = 2;
+ i2s->i2s_dai_driver[0].capture.rates = SNDRV_PCM_RATE_CONTINUOUS;
+ i2s->i2s_dai_driver[0].capture.rate_max = 96000;
+ i2s->i2s_dai_driver[0].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ i2s->i2s_dai_driver[0].ops = &sprd_i2s_dai_ops;
+
+ i2s->i2s_dai_driver[1].id = I2S_MAGIC_ID + 1;
+ i2s->i2s_dai_driver[1].name = "i2s_bt_sco_dummy";
+ printk("i2s->i2s_dai_driver[1].name %s \n",i2s->i2s_dai_driver[1].name);
+
+ arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL);
+ sp_asoc_pr_dbg("membase = 0x%x memphys = 0x%x\n", (int)i2s->membase,
+ (int)i2s->memphys);
+ sp_asoc_pr_dbg("DMA Number tx = %d rx = %d\n", i2s->tx.dma_no,
+ i2s->rx.dma_no);
+
+ platform_set_drvdata(pdev, i2s);
+
+ atomic_set(&i2s->open_cnt, 0);
+
+ ret =
+ snd_soc_register_component(&pdev->dev, &sprd_i2s_component,
+ i2s->i2s_dai_driver, ARRAY_SIZE(i2s->i2s_dai_driver));
+ sp_asoc_pr_dbg("return %i\n", ret);
+
+ if (strcmp(i2s->dai_name,"i2s_bt_sco0") == 0) {
+ dup_config = &(i2s->config);
+ membase = i2s->membase;
+ }
+ return ret;
+out:
+ devm_kfree(&pdev->dev,i2s);
+ return -EINVAL;
+}
+
+static int i2s_drv_remove(struct platform_device *pdev)
+{
+ struct i2s_priv *i2s;
+ i2s = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id i2s_of_match[] = {
+ {.compatible = "sprd,i2s",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, i2s_of_match);
+#endif
+
+static struct platform_driver i2s_driver = {
+ .driver = {
+ .name = "i2s",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(i2s_of_match),
+ },
+
+ .probe = i2s_drv_probe,
+ .remove = i2s_drv_remove,
+};
+
+module_platform_driver(i2s_driver);
+
+MODULE_DESCRIPTION("SPRD ASoC I2S CUP-DAI driver");
+MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cpu-dai:i2s");
diff --git a/sound/soc/sprd/dai/sprd-dmaengine-pcm.c b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c
new file mode 100644
index 00000000..548601f8
--- /dev/null
+++ b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c
@@ -0,0 +1,1069 @@
+/*
+ * sound/soc/sprd/dai/sprd-dmaengine-pcm.c
+ *
+ * SpreadTrum DMA for the pcm stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" PCM ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/of.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <soc/sprd/sprd-audio.h>
+#include "sprd-asoc-common.h"
+#include "sprd-dmaengine-pcm.h"
+#include "vaudio.h"
+#include <soc/sprd/i2s.h>
+#include "dfm.h"
+
+#ifndef DMA_LINKLIST_CFG_NODE_SIZE
+#define DMA_LINKLIST_CFG_NODE_SIZE (sizeof(struct sprd_dma_cfg))
+#endif
+
+struct sprd_dma_callback_data {
+ struct snd_pcm_substream *substream;
+ struct dma_chan *dma_chn;
+};
+
+struct sprd_runtime_data {
+ int dma_addr_offset;
+ struct sprd_pcm_dma_params *params;
+ struct dma_chan *dma_chn[2];
+ struct sprd_dma_cfg *dma_cfg_array;
+ struct sprd_dma_callback_data *dma_pdata;
+ dma_addr_t dma_cfg_phy[2];
+ void *dma_cfg_virt[2];
+ struct dma_async_tx_descriptor *dma_tx_des[2];
+ dma_cookie_t cookie[2];
+ int int_pos_update[2];
+ int burst_len;
+ int hw_chan;
+ int dma_pos_pre[2];
+ int interleaved;
+ int cb_called;
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ int buffer_in_iram;
+#endif
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+ struct snd_info_entry *proc_info_entry;
+#endif
+};
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#define SPRD_AUDIO_DMA_NODE_SIZE (1024)
+#endif
+
+static const struct snd_pcm_hardware sprd_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = VBC_FIFO_FRAME_NUM * 4,
+ /* non limit */
+ .period_bytes_max = VBC_FIFO_FRAME_NUM * 4 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = VBC_BUFFER_BYTES_MAX,
+};
+
+static const struct snd_pcm_hardware sprd_i2s_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = 8 * 2,
+ /* non limit */
+ .period_bytes_max = 32 * 2 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = I2S_BUFFER_BYTES_MAX,
+};
+
+atomic_t lightsleep_refcnt;
+
+int sprd_lightsleep_disable(const char *id, int disalbe)
+ __attribute__ ((weak, alias("__sprd_lightsleep_disable")));
+
+static int __sprd_lightsleep_disable(const char *id, int disable)
+{
+ sp_asoc_pr_dbg("NO lightsleep control function %d\n", disable);
+ return 0;
+}
+
+static inline int sprd_is_vaudio(struct snd_soc_dai *cpu_dai)
+{
+ return ((cpu_dai->driver->id == VAUDIO_MAGIC_ID)
+ || (cpu_dai->driver->id == VAUDIO_MAGIC_ID + 1));
+}
+
+static inline int sprd_is_i2s(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == I2S_MAGIC_ID);
+}
+
+static inline int sprd_is_dfm(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == DFM_MAGIC_ID);
+}
+
+static inline const char *sprd_dai_pcm_name(struct snd_soc_dai *cpu_dai)
+{
+ if (sprd_is_i2s(cpu_dai)) {
+ return "I2S";
+ } else if (sprd_is_vaudio(cpu_dai)) {
+ return "VAUDIO";
+ } else if (sprd_is_dfm(cpu_dai)) {
+ return "DFM";
+ }
+ return "VBC";
+}
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+static char *s_mem_for_iram = 0;
+#endif
+static char *s_iram_remap_base = 0;
+
+static int sprd_buffer_iram_backup(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+#endif
+ if (!s_iram_remap_base) {
+ s_iram_remap_base =
+ ioremap_nocache(SPRD_IRAM_ALL_PHYS, SPRD_IRAM_ALL_SIZE);
+ }
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ if (!s_mem_for_iram) {
+ s_mem_for_iram = kzalloc(SPRD_IRAM_ALL_SIZE, GFP_KERNEL);
+ } else {
+ sp_asoc_pr_dbg("IRAM is Backup, Be Careful use IRAM!\n");
+ return 0;
+ }
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM Backup Error!\n");
+ return -ENOMEM;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_fromio(s_mem_for_iram, iram_start, SPRD_IRAM_ALL_SIZE);
+#endif
+ return 0;
+}
+
+static int sprd_buffer_iram_restore(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM not Backup\n");
+ return 0;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_toio(iram_start, s_mem_for_iram, SPRD_IRAM_ALL_SIZE);
+ kfree(s_mem_for_iram);
+ s_mem_for_iram = 0;
+#endif
+ return 0;
+}
+#endif
+
+static inline int sprd_pcm_is_interleaved(struct snd_pcm_runtime *runtime)
+{
+ return (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+}
+
+#define PCM_DIR_NAME(stream) (stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Captrue")
+
+static int sprd_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_runtime_data *rtd;
+ struct i2s_config *config;
+ int burst_len;
+ int hw_chan;
+ int ret;
+
+ sp_asoc_pr_info("%s Open %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ snd_soc_set_runtime_hwparams(substream, &sprd_i2s_pcm_hardware);
+ config = srtd->cpu_dai->ac97_pdata;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ burst_len = I2S_FIFO_DEPTH - config->tx_watermark;
+ } else {
+ burst_len = config->rx_watermark;
+ }
+ burst_len <<= config->byte_per_chan;
+ hw_chan = 1;
+ } else {
+ snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware);
+ burst_len = (VBC_FIFO_FRAME_NUM * 4);
+ hw_chan = 2;
+ }
+
+ /*
+ * For mysterious reasons (and despite what the manual says)
+ * playback samples are lost if the DMA count is not a multiple
+ * of the DMA burst size. Let's add a rule to enforce that.
+ */
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ ret = -ENOMEM;
+ rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
+ if (!rtd)
+ goto out;
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(srtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ rtd->dma_cfg_virt[0] =
+ (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ &rtd->dma_cfg_phy[0], GFP_KERNEL);
+ if (!rtd->dma_cfg_virt[0])
+ goto err2;
+ rtd->dma_cfg_virt[1] =
+ (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ &rtd->dma_cfg_phy[1], GFP_KERNEL);
+ if (!rtd->dma_cfg_virt[1])
+ goto err3;
+ if (atomic_inc_return(&lightsleep_refcnt) == 1)
+ sprd_lightsleep_disable("audio", 1);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ runtime->hw.periods_max =
+ SPRD_AUDIO_DMA_NODE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE;
+ runtime->hw.buffer_bytes_max =
+ SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ rtd->buffer_in_iram = 1;
+ rtd->dma_cfg_virt[0] = s_iram_remap_base + runtime->hw.buffer_bytes_max;
+ rtd->dma_cfg_phy[0] = SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max;
+ rtd->dma_cfg_virt[1] = ((unsigned long)(rtd->dma_cfg_virt[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg))) + 15) & (~0xf);
+ rtd->dma_cfg_phy[1] = ((unsigned int)(rtd->dma_cfg_phy[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg)))+ 15) & (~0xf);
+ }
+#endif
+ rtd->dma_cfg_array =
+ (struct sprd_dma_cfg *)kzalloc(hw_chan * runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),GFP_KERNEL);
+ if (!rtd->dma_cfg_array)
+ goto err4;
+
+ rtd->dma_pdata =
+ (struct sprd_dma_callback_data *)kzalloc(hw_chan * sizeof(struct sprd_dma_callback_data),GFP_KERNEL);
+ if (!rtd->dma_pdata)
+ goto err5;
+
+ rtd->dma_chn[0] = rtd->dma_chn[1] = NULL;
+ rtd->dma_tx_des[0] = rtd->dma_tx_des[1] = NULL;
+ rtd->cookie[0] = rtd->cookie[1] = 0;
+
+ rtd->burst_len = burst_len;
+ rtd->hw_chan = hw_chan;
+
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+
+err5:
+ pr_err("ERR:dma_pdata alloc failed!\n");
+ kfree(rtd->dma_cfg_array);
+err4:
+ pr_err("ERR:dma_cfg_array alloc failed!\n");
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram)
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]);
+err3:
+ pr_err("ERR:dma_cfg_virt[1] alloc failed!\n");
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram)
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]);
+err2:
+ pr_err("ERR:dma_cfg_virt[0] alloc failed!\n");
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+#endif
+ kfree(rtd);
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+out:
+ return ret;
+}
+
+static int sprd_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ sp_asoc_pr_info("%s Close %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ kfree(rtd);
+ return 0;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+ else {
+#endif
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ }
+#endif
+ kfree(rtd->dma_pdata);
+ kfree(rtd->dma_cfg_array);
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (!rtd->buffer_in_iram) {
+#endif
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]);
+ dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),
+ rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ }
+#endif
+ kfree(rtd);
+
+ return 0;
+}
+
+static void sprd_pcm_dma_buf_done(void *data)
+{
+ struct sprd_dma_callback_data *dma_cb_data = (struct sprd_dma_callback_data*)data;
+ struct snd_pcm_runtime *runtime = dma_cb_data->substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ int i = 0;
+
+ if (!rtd->cb_called) {
+ rtd->cb_called = 1;
+ sp_asoc_pr_info("DMA Callback CALL\n");
+ }
+
+ if (rtd->hw_chan == 1)
+ goto irq_fast;
+
+ for (i = 0; i < 2; i++) {
+ if (dma_cb_data->dma_chn == rtd->dma_chn[i]) {
+ rtd->int_pos_update[i] = 1;
+
+ if (rtd->dma_chn[1 - i]) {
+ if (rtd->int_pos_update[1 - i])
+ goto irq_ready;
+ } else {
+ goto irq_ready;
+ }
+ }
+ }
+ goto irq_ret;
+irq_ready:
+ rtd->int_pos_update[0] = 0;
+ rtd->int_pos_update[1] = 0;
+irq_fast:
+ snd_pcm_period_elapsed(dma_cb_data->substream);
+irq_ret:
+ return;
+}
+
+/*
+ * proc interface
+ */
+
+//#ifdef CONFIG_SND_VERBOSE_PROCFS
+#if 0
+static void sprd_pcm_proc_dump_reg(int id, struct snd_info_buffer *buffer)
+{
+ u32 reg, base_reg;
+ u32 offset = sci_dma_dump_reg(id, &base_reg);
+ for (reg = base_reg; reg < base_reg + offset; reg += 0x10) {
+ snd_iprintf(buffer, "0x%04x | 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ (unsigned int)(reg - base_reg),
+ __raw_readl((void __iomem *)(reg + 0x00)),
+ __raw_readl((void __iomem *)(reg + 0x04)),
+ __raw_readl((void __iomem *)(reg + 0x08)),
+ __raw_readl((void __iomem *)(reg + 0x0C))
+ );
+ }
+}
+
+static void sprd_pcm_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_pcm_substream *substream = entry->private_data;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ int i;
+
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ snd_iprintf(buffer, "Channel%d Config\n",
+ rtd->uid_cid_map[i]);
+ sprd_pcm_proc_dump_reg(rtd->uid_cid_map[i], buffer);
+ }
+ }
+}
+
+static void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+ struct snd_info_entry *entry;
+ struct snd_pcm_str *pstr = substream->pstr;
+ struct snd_pcm *pcm = pstr->pcm;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+
+ if ((entry =
+ snd_info_create_card_entry(pcm->card, "DMA",
+ pstr->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, sprd_pcm_proc_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ rtd->proc_info_entry = entry;
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ snd_info_free_entry(rtd->proc_info_entry);
+ rtd->proc_info_entry = NULL;
+}
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+static inline void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+}
+#endif
+
+static int sprd_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_pcm_dma_params *dma_data;
+ struct dma_chan *dma_chn_request;
+ struct sprd_dma_cfg *dma_config_ptr[2];
+ struct sprd_dma_callback_data *dma_pdata_ptr[2];
+ dma_cap_mask_t mask;
+ size_t totsize = params_buffer_bytes(params);
+ size_t period = params_period_bytes(params);
+ dma_addr_t dma_buff_phys[2];
+ struct i2s_config *config = NULL;
+ enum dma_filter_param chn_type;
+ int ret = 0;
+ int i, j = 0;
+ int used_chan_count;
+
+ dma_data = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream);
+ if (!dma_data)
+ goto no_dma;
+
+ used_chan_count = params_channels(params);
+ sp_asoc_pr_info("chan=%d totsize=%d period=%d\n", used_chan_count,
+ totsize, period);
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ config = srtd->cpu_dai->ac97_pdata;
+ used_chan_count = rtd->hw_chan;
+ } else {
+ rtd->interleaved = (used_chan_count == 2)
+ && sprd_pcm_is_interleaved(runtime);
+ if (rtd->interleaved) {
+ sp_asoc_pr_dbg("Interleaved Access\n");
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_data->desc.src_step = 4;
+ } else {
+ dma_data->desc.des_step = 4;
+ }
+ } else {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_data->desc.src_step = 2;
+ } else {
+ dma_data->desc.des_step = 2;
+ }
+ }
+ }
+
+ /* this may get called several times by oss emulation
+ * with different params */
+ if (rtd->params == NULL) {
+ rtd->params = dma_data;
+ for (i = 0; i < used_chan_count; i++) {
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY|DMA_SG,mask);
+ /* request dma channel */
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_USE_AON_DMA
+ chn_type = AP_FULL_DMA;
+#else
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ && (!sprd_is_i2s(srtd->cpu_dai))) {
+ chn_type = AON_FULL_DMA;
+ } else {
+ chn_type = AP_FULL_DMA;
+ }
+#endif
+ dma_chn_request = dma_request_channel(mask, sprd_dma_filter_fn, (void *)&chn_type);
+ if (!dma_chn_request) {
+ pr_err("ERR:PCM Request DMA Error %d\n",
+ dma_data->channels[i]);
+ for (i--; i >= 0; i--) {
+ rtd->dma_chn[i] = NULL;
+ rtd->dma_tx_des[i] = NULL;
+ rtd->cookie[i] = 0;
+ rtd->params = NULL;
+ }
+ goto hw_param_err;
+ }
+ rtd->dma_chn[i] = dma_chn_request;
+ sp_asoc_pr_dbg("Chan%d DMA ID=%d, chn_type:%d \n",
+ rtd->dma_chn[i]->chan_id,
+ rtd->params->channels[i], chn_type);
+ }
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totsize;
+
+ rtd->dma_addr_offset = (totsize / used_chan_count);
+ if (sprd_pcm_is_interleaved(runtime))
+ rtd->dma_addr_offset = 2;
+
+ for (i = 0; i < used_chan_count; i++) {
+ dma_config_ptr[i] = rtd->dma_cfg_array + i * (runtime->hw.periods_max);
+ memset(dma_config_ptr[i],0,sizeof(struct sprd_dma_cfg) * runtime->hw.periods_max);
+ dma_pdata_ptr[i] = rtd->dma_pdata + i;
+ memset(dma_pdata_ptr[i],0,sizeof(struct sprd_dma_callback_data));
+ dma_pdata_ptr[i]->dma_chn = rtd->dma_chn[i];
+ dma_pdata_ptr[i]->substream = substream;
+ dma_buff_phys[i] = runtime->dma_addr + i * rtd->dma_addr_offset;
+ }
+
+ do {
+ for (i = 0; i < used_chan_count; i++) {
+ (dma_config_ptr[i]+j)->datawidth = 1;
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ if (substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK) {
+ (dma_config_ptr[i]+j)->fragmens_len =
+ (I2S_FIFO_DEPTH -
+ config->tx_watermark) *
+ (dma_config_ptr[i]+j)->datawidth;
+ } else {
+ (dma_config_ptr[i]+j)->fragmens_len =
+ config->rx_watermark *
+ (dma_config_ptr[i]+j)->datawidth;
+ }
+ } else {
+ (dma_config_ptr[i]+j)->fragmens_len = dma_data->desc.fragmens_len;
+ }
+ (dma_config_ptr[i]+j)->block_len = period / used_chan_count;
+ (dma_config_ptr[i]+j)->transcation_len = 0;
+ (dma_config_ptr[i]+j)->req_mode = FRAG_REQ_MODE;
+ (dma_config_ptr[i]+j)->irq_mode= BLK_DONE;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step;
+ (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step;
+ (dma_config_ptr[i]+j)->src_addr = dma_buff_phys[i];
+ (dma_config_ptr[i]+j)->des_addr = dma_data->dev_paddr[i];
+ } else {
+ (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step;
+ (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step;
+ (dma_config_ptr[i]+j)->src_addr = dma_data->dev_paddr[i];
+ (dma_config_ptr[i]+j)->des_addr = dma_buff_phys[i];
+ }
+ dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len;
+ if (rtd->interleaved)
+ dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len;
+
+ (dma_config_ptr[i]+j)->link_cfg_v = (unsigned long)(rtd->dma_cfg_virt[i]);
+ (dma_config_ptr[i]+j)->link_cfg_p = (unsigned long)(rtd->dma_cfg_phy[i]);
+ (dma_config_ptr[i]+j)->dev_id = dma_data->channels[i];
+ }
+
+ if (period > totsize)
+ period = totsize;
+ j++;
+ } while (totsize -= period);
+ for (i = 0; i < used_chan_count; i++) {
+ (dma_config_ptr[i]+j-1)->is_end = 2;
+ }
+
+ sp_asoc_pr_dbg("Node Size:%d\n", j);
+
+ /* config dma channel */
+ ret = dmaengine_device_control(rtd->dma_chn[0],
+ DMA_SLAVE_CONFIG,
+ (unsigned long)(dma_config_ptr[0]));
+ if(ret < 0){
+ sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[0]->chan_id);
+ goto hw_param_err;
+ }
+ /* get dma desc from dma config */
+ rtd->dma_tx_des[0] =
+ rtd->dma_chn[0]->device->device_prep_dma_memcpy(rtd->dma_chn[0], 0, 0, 0,
+ DMA_CFG_FLAG|DMA_HARDWARE_FLAG);
+ if(!rtd->dma_tx_des[0]) {
+ sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[0]->chan_id);
+ goto hw_param_err;
+ }
+
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n",
+ __func__,rtd->dma_chn[0]->chan_id);
+ rtd->dma_tx_des[0]->callback = sprd_pcm_dma_buf_done;
+ rtd->dma_tx_des[0]->callback_param = (void *)(dma_pdata_ptr[0]);
+ }
+
+ if (used_chan_count > 1) {
+ /* config dma channel */
+ ret = dmaengine_device_control(rtd->dma_chn[1],
+ DMA_SLAVE_CONFIG,
+ (unsigned long)(dma_config_ptr[1]));
+ if(ret < 0){
+ sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[1]->chan_id);
+ goto hw_param_err;
+ }
+ /* get dma desc from dma config */
+ rtd->dma_tx_des[1] =
+ rtd->dma_chn[1]->device->device_prep_dma_memcpy(rtd->dma_chn[1], 0, 0, 0,
+ DMA_CFG_FLAG|DMA_HARDWARE_FLAG);
+ if(!rtd->dma_tx_des[1]) {
+ sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[1]->chan_id);
+ goto hw_param_err;
+ }
+
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n",
+ __func__,rtd->dma_chn[1]->chan_id);
+ rtd->dma_tx_des[1]->callback = sprd_pcm_dma_buf_done;
+ rtd->dma_tx_des[1]->callback_param = (void *)(dma_pdata_ptr[1]);
+ }
+
+ }
+ sprd_pcm_proc_init(substream);
+
+ goto ok_go_out;
+
+no_dma:
+ sp_asoc_pr_dbg("no dma\n");
+ rtd->params = NULL;
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = totsize;
+ return ret;
+hw_param_err:
+ sp_asoc_pr_dbg("hw_param_err\n");
+ok_go_out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static int sprd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int i;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (dma) {
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_chn[i]) {
+ dma_release_channel(rtd->dma_chn[i]);
+ rtd->dma_chn[i] = NULL;
+ rtd->dma_tx_des[i] = NULL;
+ rtd->cookie[i] = 0;
+ }
+ }
+ rtd->params = NULL;
+ }
+
+ sprd_pcm_proc_done(substream);
+
+ return 0;
+}
+
+static int sprd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int ret = 0;
+ int i;
+ if (!dma) {
+ sp_asoc_pr_dbg("no trigger");
+ return 0;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_tx_des[i]) {
+ rtd->cookie[i] = dmaengine_submit(rtd->dma_tx_des[i]);
+ }
+ }
+ dma_issue_pending_all();
+ sp_asoc_pr_info("S\n");
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->dma_chn[i]) {
+ dmaengine_pause(rtd->dma_chn[i]);
+ }
+ }
+ rtd->cb_called = 0;
+ sp_asoc_pr_info("E\n");
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ snd_pcm_uframes_t x;
+ int now_pointer;
+ int bytes_of_pointer = 0;
+ int shift = 1;
+ int sel_max = 0;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no pointer");
+ return 0;
+ }
+
+ if (rtd->interleaved)
+ shift = 0;
+ if (rtd->dma_chn[0]) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[0],
+ rtd->cookie[0], substream) -
+ runtime->dma_addr;
+ bytes_of_pointer = now_pointer;
+ }
+ if (rtd->dma_chn[1]) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[1],
+ rtd->cookie[1], substream) -
+ runtime->dma_addr - rtd->dma_addr_offset;
+ if (!bytes_of_pointer) {
+ bytes_of_pointer = now_pointer;
+ } else {
+ sel_max = (bytes_of_pointer < rtd->dma_pos_pre[0]);
+ sel_max ^= (now_pointer < rtd->dma_pos_pre[1]);
+ rtd->dma_pos_pre[0] = bytes_of_pointer;
+ rtd->dma_pos_pre[1] = now_pointer;
+ if (sel_max) {
+ bytes_of_pointer =
+ max(bytes_of_pointer, now_pointer) << shift;
+ } else {
+ bytes_of_pointer =
+ min(bytes_of_pointer, now_pointer) << shift;
+ }
+ }
+ }
+ x = bytes_to_frames(runtime, bytes_of_pointer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+
+#if 0
+ sp_asoc_pr_dbg("p=%d f=%d\n", bytes_of_pointer, x);
+#endif
+ return x;
+}
+
+static int sprd_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no mmap");
+ return 0;
+ }
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+#else
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ return remap_pfn_range(vma, vma->vm_start,
+ runtime->dma_addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+#endif
+}
+
+static struct snd_pcm_ops sprd_pcm_ops = {
+ .open = sprd_pcm_open,
+ .close = sprd_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = sprd_pcm_hw_params,
+ .hw_free = sprd_pcm_hw_free,
+ .prepare = sprd_pcm_prepare,
+ .trigger = sprd_pcm_trigger,
+ .pointer = sprd_pcm_pointer,
+ .mmap = sprd_pcm_mmap,
+};
+
+static int sprd_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = AUDIO_BUFFER_BYTES_MAX;
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ if (sprd_is_i2s(rtd->cpu_dai)) {
+ size = I2S_BUFFER_BYTES_MAX;
+ } else {
+ size = VBC_BUFFER_BYTES_MAX;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(rtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ buf->private_data = buf;
+ buf->area = (void *)(s_iram_remap_base);
+ buf->addr = SPRD_IRAM_ALL_PHYS;
+ size = SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ }
+#endif
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static u64 sprd_pcm_dmamask = DMA_BIT_MASK(32);
+static struct snd_dma_buffer *save_p_buf = 0;
+#define VBC_CHAN (2)
+static struct snd_dma_buffer *save_c_buf[VBC_CHAN] = { 0 };
+
+static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_pcm_substream *substream;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s %s\n", __func__, sprd_dai_pcm_name(cpu_dai));
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &sprd_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (substream) {
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_i2s(cpu_dai) || !save_p_buf) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_p_buf = buf;
+ }
+ sp_asoc_pr_dbg("Playback alloc memery\n");
+ } else {
+ memcpy(buf, save_p_buf, sizeof(*buf));
+ sp_asoc_pr_dbg("Playback share memery\n");
+ }
+ }
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ if (substream) {
+ int id = cpu_dai->driver->id;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_vaudio(cpu_dai)) {
+ id -= VAUDIO_MAGIC_ID;
+ }
+ if (sprd_is_i2s(cpu_dai) || !save_c_buf[id]) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_c_buf[id] = buf;
+ }
+ sp_asoc_pr_dbg("Capture alloc memery %d\n", id);
+ } else {
+ memcpy(buf, save_c_buf[id], sizeof(*buf));
+ sp_asoc_pr_dbg("Capture share memery %d\n", id);
+ }
+ }
+out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static void sprd_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (buf->private_data)
+ sprd_buffer_iram_restore();
+ else
+#endif
+ dma_alloc_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ if (buf == save_p_buf) {
+ save_p_buf = 0;
+ }
+ for (i = 0; i < VBC_CHAN; i++) {
+ if (buf == save_c_buf[i]) {
+ save_c_buf[i] = 0;
+ }
+ }
+ }
+}
+
+static struct snd_soc_platform_driver sprd_soc_platform = {
+ .ops = &sprd_pcm_ops,
+ .pcm_new = sprd_pcm_new,
+ .pcm_free = sprd_pcm_free_dma_buffers,
+};
+
+static int sprd_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &sprd_soc_platform);
+}
+
+static int sprd_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sprd_pcm_of_match[] = {
+ {.compatible = "sprd,sprd-pcm",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, sprd_pcm_of_match);
+#endif
+
+static struct platform_driver sprd_pcm_driver = {
+ .driver = {
+ .name = "sprd-pcm-audio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_pcm_of_match),
+ },
+
+ .probe = sprd_soc_platform_probe,
+ .remove = sprd_soc_platform_remove,
+};
+
+module_platform_driver(sprd_pcm_driver);
+
+MODULE_DESCRIPTION("SPRD ASoC PCM DMA");
+MODULE_AUTHOR("Jian chen <jian.chen@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sprd-audio");
diff --git a/sound/soc/sprd/dai/sprd-dmaengine-pcm.h b/sound/soc/sprd/dai/sprd-dmaengine-pcm.h
new file mode 100644
index 00000000..09bf8c3a
--- /dev/null
+++ b/sound/soc/sprd/dai/sprd-dmaengine-pcm.h
@@ -0,0 +1,56 @@
+/*
+ * sound/soc/sprd/dai/sprd-dmaengine-pcm.h
+ *
+ * SpreadTrum DMA for the pcm stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SPRD_DMA_ENGINE_PCM_H
+#define __SPRD_DMA_ENGINE_PCM_H
+
+//#include <mach/dma.h>
+//#include <sprd_dma.h>
+#include <../../../drivers/dma/sprd_dma.h>
+#include <linux/dmaengine.h>
+
+
+#define VBC_PCM_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+#define VBC_FIFO_FRAME_NUM (160)
+#define VBC_BUFFER_BYTES_MAX (128 * 1024)
+
+#define I2S_BUFFER_BYTES_MAX (64 * 1024)
+
+#define AUDIO_BUFFER_BYTES_MAX (VBC_BUFFER_BYTES_MAX + I2S_BUFFER_BYTES_MAX)
+
+struct sprd_pcm_dma_params {
+ char *name; /* stream identifier */
+ int channels[2]; /* channel id */
+ int irq_type; /* dma interrupt type */
+ struct sprd_dma_cfg desc; /* dma description struct */
+ u32 dev_paddr[2]; /* device physical address for DMA */
+};
+
+static inline u32 sprd_pcm_dma_get_addr(struct dma_chan *dma_chn,
+ dma_cookie_t cookie, struct snd_pcm_substream *substream)
+{
+ struct dma_tx_state dma_state;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_state.residue = SPRD_SRC_ADDR;
+ } else {
+ dma_state.residue = SPRD_DST_ADDR;
+ }
+ dmaengine_tx_status(dma_chn, cookie, &dma_state);
+ //printk(KERN_ERR "%s, residue:0x%x \n",__func__,dma_state.residue);
+ return dma_state.residue;
+}
+
+#endif /* __SPRD_DMA_ENGINE_PCM_H */
diff --git a/sound/soc/sprd/dai/sprd-pcm.c b/sound/soc/sprd/dai/sprd-pcm.c
new file mode 100644
index 00000000..517b588c
--- /dev/null
+++ b/sound/soc/sprd/dai/sprd-pcm.c
@@ -0,0 +1,1025 @@
+/*
+ * sound/soc/sprd/dai/sprd-pcm.c
+ *
+ * SpreadTrum DMA for the pcm stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" PCM ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/of.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <soc/sprd/dma.h>
+#include <soc/sprd/dma_reg.h>
+#include <soc/sprd/sprd-audio.h>
+
+#include "sprd-asoc-common.h"
+#include "sprd-pcm.h"
+#include "vaudio.h"
+#include <soc/sprd/i2s.h>
+#include "dfm.h"
+
+#ifndef DMA_LINKLIST_CFG_NODE_SIZE
+#define DMA_LINKLIST_CFG_NODE_SIZE (sizeof(sprd_dma_desc))
+#endif
+
+struct sprd_runtime_data {
+ int dma_addr_offset;
+ struct sprd_pcm_dma_params *params;
+ int uid_cid_map[2];
+ int int_pos_update[2];
+ sprd_dma_desc *dma_cfg_array;
+ dma_addr_t *dma_desc_array_orig;
+ dma_addr_t dma_desc_array_phys_orig;
+ dma_addr_t *dma_desc_array;
+ dma_addr_t dma_desc_array_phys;
+ int burst_len;
+ int hw_chan;
+ int dma_pos_pre[2];
+ int interleaved;
+ int irq_called;
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ int buffer_in_iram;
+#endif
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+ struct snd_info_entry *proc_info_entry;
+#endif
+};
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#define SPRD_AUDIO_DMA_NODE_SIZE (1024)
+#endif
+
+static const struct snd_pcm_hardware sprd_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = VBC_FIFO_FRAME_NUM * 4,
+ /* non limit */
+ .period_bytes_max = VBC_FIFO_FRAME_NUM * 4 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = VBC_BUFFER_BYTES_MAX,
+};
+
+static const struct snd_pcm_hardware sprd_i2s_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ /* 16bits, stereo-2-channels */
+ .period_bytes_min = 8 * 2,
+ /* non limit */
+ .period_bytes_max = 32 * 2 * 100,
+ .periods_min = 1,
+ /* non limit */
+ .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE,
+ .buffer_bytes_max = I2S_BUFFER_BYTES_MAX,
+};
+
+atomic_t lightsleep_refcnt;
+
+int sprd_lightsleep_disable(const char *id, int disalbe)
+ __attribute__ ((weak, alias("__sprd_lightsleep_disable")));
+
+static int __sprd_lightsleep_disable(const char *id, int disable)
+{
+ sp_asoc_pr_dbg("NO lightsleep control function %d\n", disable);
+ return 0;
+}
+
+static inline int sprd_is_vaudio(struct snd_soc_dai *cpu_dai)
+{
+ return ((cpu_dai->driver->id == VAUDIO_MAGIC_ID)
+ || (cpu_dai->driver->id == VAUDIO_MAGIC_ID + 1));
+}
+
+static inline int sprd_is_i2s(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == I2S_MAGIC_ID);
+}
+
+static inline int sprd_is_dfm(struct snd_soc_dai *cpu_dai)
+{
+ return (cpu_dai->driver->id == DFM_MAGIC_ID);
+}
+
+static inline const char *sprd_dai_pcm_name(struct snd_soc_dai *cpu_dai)
+{
+ if (sprd_is_i2s(cpu_dai)) {
+ return "I2S";
+ } else if (sprd_is_vaudio(cpu_dai)) {
+ return "VAUDIO";
+ } else if (sprd_is_dfm(cpu_dai)) {
+ return "DFM";
+ }
+ return "VBC";
+}
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+static char *s_mem_for_iram = 0;
+#endif
+static char *s_iram_remap_base = 0;
+
+static int sprd_buffer_iram_backup(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+#endif
+ if (!s_iram_remap_base) {
+ s_iram_remap_base =
+ ioremap_nocache(SPRD_IRAM_ALL_PHYS, SPRD_IRAM_ALL_SIZE);
+ }
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ if (!s_mem_for_iram) {
+ s_mem_for_iram = kzalloc(SPRD_IRAM_ALL_SIZE, GFP_KERNEL);
+ } else {
+ sp_asoc_pr_dbg("IRAM is Backup, Be Careful use IRAM!\n");
+ return 0;
+ }
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM Backup Error!\n");
+ return -ENOMEM;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_fromio(s_mem_for_iram, iram_start, SPRD_IRAM_ALL_SIZE);
+#endif
+ return 0;
+}
+
+static int sprd_buffer_iram_restore(void)
+{
+#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP
+ void __iomem *iram_start;
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram);
+ if (!s_mem_for_iram) {
+ pr_err("ERR:IRAM not Backup\n");
+ return 0;
+ }
+ iram_start = (void __iomem *)(s_iram_remap_base);
+ memcpy_toio(iram_start, s_mem_for_iram, SPRD_IRAM_ALL_SIZE);
+ kfree(s_mem_for_iram);
+ s_mem_for_iram = 0;
+#endif
+ return 0;
+}
+#endif
+
+static inline int sprd_pcm_is_interleaved(struct snd_pcm_runtime *runtime)
+{
+ return (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+}
+
+#define PCM_DIR_NAME(stream) (stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Captrue")
+
+static int sprd_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_runtime_data *rtd;
+ struct i2s_config *config;
+ int burst_len;
+ int hw_chan;
+ int ret;
+
+ sp_asoc_pr_info("%s Open %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ snd_soc_set_runtime_hwparams(substream, &sprd_i2s_pcm_hardware);
+ config = srtd->cpu_dai->ac97_pdata;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ burst_len = I2S_FIFO_DEPTH - config->tx_watermark;
+ } else {
+ burst_len = config->rx_watermark;
+ }
+ burst_len <<= config->byte_per_chan;
+ hw_chan = 1;
+ } else {
+ snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware);
+ burst_len = (VBC_FIFO_FRAME_NUM * 4);
+ hw_chan = 2;
+ }
+
+ /*
+ * For mysterious reasons (and despite what the manual says)
+ * playback samples are lost if the DMA count is not a multiple
+ * of the DMA burst size. Let's add a rule to enforce that.
+ */
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ burst_len);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ ret = -ENOMEM;
+ rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
+ if (!rtd)
+ goto out;
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(srtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ rtd->dma_desc_array_orig =
+ dma_alloc_coherent(substream->pcm->card->dev,
+ hw_chan * (PAGE_SIZE+32),
+ &rtd->dma_desc_array_phys_orig,
+ GFP_KERNEL);
+
+ rtd->dma_desc_array_phys = (rtd->dma_desc_array_phys_orig+31)&(~31);
+ rtd->dma_desc_array = (unsigned int)rtd->dma_desc_array_orig + (rtd->dma_desc_array_phys - rtd->dma_desc_array_phys_orig);
+
+ if (atomic_inc_return(&lightsleep_refcnt) == 1)
+ sprd_lightsleep_disable("audio", 1);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ runtime->hw.periods_max =
+ SPRD_AUDIO_DMA_NODE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE;
+ runtime->hw.buffer_bytes_max =
+ SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ rtd->dma_desc_array =
+ (void *)(s_iram_remap_base + runtime->hw.buffer_bytes_max);
+ rtd->dma_desc_array_phys =
+ SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max;
+ rtd->dma_desc_array_orig = rtd->dma_desc_array;
+ rtd->dma_desc_array_phys_orig = rtd->dma_desc_array_phys;
+ rtd->buffer_in_iram = 1;
+ /*must clear the dma_desc_array first here */
+ memset(rtd->dma_desc_array, 0, (2 * SPRD_AUDIO_DMA_NODE_SIZE));
+ }
+#endif
+
+ if (!rtd->dma_desc_array_orig)
+ goto err1;
+
+ rtd->dma_cfg_array =
+ kzalloc(hw_chan * runtime->hw.periods_max * sizeof(sprd_dma_desc),
+ GFP_KERNEL);
+ if (!rtd->dma_cfg_array)
+ goto err2;
+
+ rtd->uid_cid_map[0] = rtd->uid_cid_map[1] = -1;
+
+ rtd->burst_len = burst_len;
+ rtd->hw_chan = hw_chan;
+
+ runtime->private_data = rtd;
+ ret = 0;
+ goto out;
+
+err2:
+ pr_err("ERR:dma_cfg_array alloc failed!\n");
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+ else
+#endif
+ dma_free_coherent(substream->pcm->card->dev,
+ hw_chan * (PAGE_SIZE+32),
+ rtd->dma_desc_array_orig,
+ rtd->dma_desc_array_phys_orig);
+err1:
+ pr_err("ERR:dma_desc_array alloc failed!\n");
+ kfree(rtd);
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+out:
+ return ret;
+}
+
+static int sprd_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ sp_asoc_pr_info("%s Close %s\n", sprd_dai_pcm_name(srtd->cpu_dai),
+ PCM_DIR_NAME(substream->stream));
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ kfree(rtd);
+ return 0;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (rtd->buffer_in_iram)
+ sprd_buffer_iram_restore();
+ else {
+#endif
+ dma_free_coherent(substream->pcm->card->dev,
+ rtd->hw_chan * (PAGE_SIZE+32),
+ rtd->dma_desc_array_orig,
+ rtd->dma_desc_array_phys_orig);
+ if (!atomic_dec_return(&lightsleep_refcnt))
+ sprd_lightsleep_disable("audio", 0);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ }
+#endif
+
+ kfree(rtd->dma_cfg_array);
+ kfree(rtd);
+
+ return 0;
+}
+
+static void sprd_pcm_dma_irq_ch(int dma_ch, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ int i = 0;
+
+ if (!rtd->irq_called) {
+ rtd->irq_called = 1;
+ sp_asoc_pr_info("IRQ CALL\n");
+ }
+
+ if (rtd->hw_chan == 1)
+ goto irq_fast;
+
+ for (i = 0; i < 2; i++) {
+ if (dma_ch == rtd->uid_cid_map[i]) {
+ rtd->int_pos_update[i] = 1;
+
+ if (rtd->uid_cid_map[1 - i] >= 0) {
+ if (rtd->int_pos_update[1 - i])
+ goto irq_ready;
+ } else {
+ goto irq_ready;
+ }
+ }
+ }
+ goto irq_ret;
+irq_ready:
+ rtd->int_pos_update[0] = 0;
+ rtd->int_pos_update[1] = 0;
+irq_fast:
+ snd_pcm_period_elapsed(dev_id);
+irq_ret:
+ return;
+}
+
+/*
+ * proc interface
+ */
+
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+static void sprd_pcm_proc_dump_reg(int id, struct snd_info_buffer *buffer)
+{
+ u32 reg, base_reg;
+ u32 offset = sci_dma_dump_reg(id, &base_reg);
+ for (reg = base_reg; reg < base_reg + offset; reg += 0x10) {
+ snd_iprintf(buffer, "0x%04x | 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ (unsigned int)(reg - base_reg),
+ __raw_readl((void __iomem *)(reg + 0x00)),
+ __raw_readl((void __iomem *)(reg + 0x04)),
+ __raw_readl((void __iomem *)(reg + 0x08)),
+ __raw_readl((void __iomem *)(reg + 0x0C))
+ );
+ }
+}
+
+static void sprd_pcm_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_pcm_substream *substream = entry->private_data;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ int i;
+
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ snd_iprintf(buffer, "Channel%d Config\n",
+ rtd->uid_cid_map[i]);
+ sprd_pcm_proc_dump_reg(rtd->uid_cid_map[i], buffer);
+ }
+ }
+}
+
+static void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+ struct snd_info_entry *entry;
+ struct snd_pcm_str *pstr = substream->pstr;
+ struct snd_pcm *pcm = pstr->pcm;
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+
+ if ((entry =
+ snd_info_create_card_entry(pcm->card, "DMA",
+ pstr->proc_root)) != NULL) {
+ snd_info_set_text_ops(entry, substream, sprd_pcm_proc_read);
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ entry = NULL;
+ }
+ }
+ rtd->proc_info_entry = entry;
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ snd_info_free_entry(rtd->proc_info_entry);
+ rtd->proc_info_entry = NULL;
+}
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+static inline void sprd_pcm_proc_init(struct snd_pcm_substream *substream)
+{
+}
+
+static void sprd_pcm_proc_done(struct snd_pcm_substream *substream)
+{
+}
+#endif
+
+static int sprd_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ struct sprd_pcm_dma_params *dma;
+ size_t totsize = params_buffer_bytes(params);
+ size_t period = params_period_bytes(params);
+ sprd_dma_desc *dma_desc[2];
+ dma_addr_t dma_buff_phys[2]; /*, next_desc_phys[2]; */
+ struct i2s_config *config = NULL;
+ int ret = 0;
+ int i, j = 0;
+ int used_chan_count;
+ int chan_id;
+ struct reg_cfg_addr dma_reg_addr[2];
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ dma = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream);
+ if (!dma)
+ goto no_dma;
+
+ used_chan_count = params_channels(params);
+ sp_asoc_pr_info("chan=%d totsize=%d period=%d\n", used_chan_count,
+ totsize, period);
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ config = srtd->cpu_dai->ac97_pdata;
+ used_chan_count = rtd->hw_chan;
+ } else {
+ rtd->interleaved = (used_chan_count == 2)
+ && sprd_pcm_is_interleaved(runtime);
+ if (rtd->interleaved) {
+ sp_asoc_pr_dbg("Interleaved Access\n");
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma->desc.src_step = 4;
+ } else {
+ dma->desc.des_step = 4;
+ }
+ } else {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma->desc.src_step = 2;
+ } else {
+ dma->desc.des_step = 2;
+ }
+ }
+ }
+
+ /* this may get called several times by oss emulation
+ * with different params */
+ if (rtd->params == NULL) {
+ rtd->params = dma;
+ for (i = 0; i < used_chan_count; i++) {
+ chan_id = sci_dma_request(dma->name, FULL_DMA_CHN);
+ if (chan_id < 0) {
+ pr_err("ERR:PCM Request DMA Error %d\n",
+ dma->channels[i]);
+ for (i--; i >= 0; i--) {
+ sci_dma_free(rtd->uid_cid_map[i]);
+ rtd->uid_cid_map[i] = -1;
+ rtd->params = NULL;
+ }
+ goto hw_param_err;
+ }
+ rtd->uid_cid_map[i] = chan_id;
+ sp_asoc_pr_dbg("Chan%d DMA ID=%d\n",
+ rtd->uid_cid_map[i],
+ rtd->params->channels[i]);
+ }
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totsize;
+
+ dma_desc[0] = rtd->dma_cfg_array;
+ dma_desc[1] = rtd->dma_cfg_array + runtime->hw.periods_max;
+
+ dma_buff_phys[0] = runtime->dma_addr;
+ rtd->dma_addr_offset = (totsize / used_chan_count);
+ if (sprd_pcm_is_interleaved(runtime))
+ rtd->dma_addr_offset = 2;
+ dma_buff_phys[1] = runtime->dma_addr + rtd->dma_addr_offset;
+
+ do {
+ for (i = 0; i < used_chan_count; i++) {
+ dma_desc[i]->datawidth = dma->desc.datawidth;
+ if (sprd_is_i2s(srtd->cpu_dai)) {
+ if (substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_desc[i]->fragmens_len =
+ (I2S_FIFO_DEPTH -
+ config->tx_watermark) *
+ dma_desc[i]->datawidth;
+ } else {
+ dma_desc[i]->fragmens_len =
+ config->rx_watermark *
+ dma_desc[i]->datawidth;
+ }
+ } else {
+ dma_desc[i]->fragmens_len =
+ dma->desc.fragmens_len;
+ }
+ dma_desc[i]->block_len = period / used_chan_count;
+ dma_desc[i]->transcation_len = 0;
+ dma_desc[i]->req_mode = FRAG_REQ_MODE;
+ dma_desc[i]->src_step = dma->desc.src_step;
+ dma_desc[i]->des_step = dma->desc.des_step;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_desc[i]->src_addr = dma_buff_phys[i];
+ dma_desc[i]->des_addr = dma->dev_paddr[i];
+ } else {
+ dma_desc[i]->src_addr = dma->dev_paddr[i];
+ dma_desc[i]->des_addr = dma_buff_phys[i];
+ }
+
+ dma_buff_phys[i] += dma_desc[i]->block_len;
+ if (rtd->interleaved)
+ dma_buff_phys[i] += dma_desc[i]->block_len;
+ dma_desc[i]++;
+ }
+
+ if (period > totsize)
+ period = totsize;
+ j++;
+ } while (totsize -= period);
+
+ sp_asoc_pr_dbg("Node Size:%d\n", j);
+
+ dma_reg_addr[0].phys_addr = (u32) (rtd->dma_desc_array_phys);
+ dma_reg_addr[0].virt_addr = rtd->dma_desc_array;
+ sp_asoc_pr_dbg("dma_reg_addr[0].virt_addr:0x%x\n",
+ dma_reg_addr[0].virt_addr);
+ sp_asoc_pr_dbg("dma_reg_addr[0].phys_addr:0x%x\n",
+ dma_reg_addr[0].phys_addr);
+
+ sci_dma_config((u32) (rtd->uid_cid_map[0]),
+ (struct sci_dma_cfg *)(rtd->dma_cfg_array),
+ params_periods(params), &dma_reg_addr[0]);
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("Register IRQ for DMA chan ID %d\n",
+ rtd->uid_cid_map[0]);
+ ret =
+ sci_dma_register_irqhandle(rtd->uid_cid_map[0],
+ rtd->params->irq_type,
+ sprd_pcm_dma_irq_ch, substream);
+ }
+
+ if (used_chan_count > 1) {
+ dma_reg_addr[1].phys_addr =
+ (u32) (dma_reg_addr[0].phys_addr) +
+ runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE;
+ dma_reg_addr[1].virt_addr =
+ (dma_reg_addr[0].virt_addr) +
+ runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE;
+ sp_asoc_pr_dbg("dma_reg_addr[1].virt_addr:0x%x\n",
+ dma_reg_addr[1].virt_addr);
+ sp_asoc_pr_dbg("dma_reg_addr[1].phys_addr:0x%x\n",
+ dma_reg_addr[1].phys_addr);
+
+ sci_dma_config((u32) (rtd->uid_cid_map[1]),
+ (struct sci_dma_cfg *)(rtd->dma_cfg_array +
+ runtime->hw.periods_max),
+ params_periods(params), &dma_reg_addr[1]);
+ if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) {
+ sp_asoc_pr_info("Register IRQ for DMA Chan ID %d\n",
+ rtd->uid_cid_map[1]);
+ ret =
+ sci_dma_register_irqhandle(rtd->uid_cid_map[1],
+ rtd->params->irq_type,
+ sprd_pcm_dma_irq_ch,
+ substream);
+ }
+
+ }
+
+ sprd_pcm_proc_init(substream);
+
+ goto ok_go_out;
+
+no_dma:
+ sp_asoc_pr_dbg("no dma\n");
+ rtd->params = NULL;
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = totsize;
+ return ret;
+hw_param_err:
+ sp_asoc_pr_dbg("hw_param_err\n");
+ok_go_out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static int sprd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int i;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (dma) {
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ sci_dma_free(rtd->uid_cid_map[i]);
+ rtd->uid_cid_map[i] = -1;
+ }
+ }
+ rtd->params = NULL;
+ }
+
+ sprd_pcm_proc_done(substream);
+
+ return 0;
+}
+
+static int sprd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct sprd_runtime_data *rtd = substream->runtime->private_data;
+ struct sprd_pcm_dma_params *dma = rtd->params;
+ int ret = 0;
+ int i;
+
+ if (!dma) {
+ sp_asoc_pr_dbg("no trigger");
+ return 0;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ sci_dma_start(rtd->uid_cid_map[i],
+ dma->channels[i]);
+ }
+ }
+ sp_asoc_pr_info("S\n");
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < rtd->hw_chan; i++) {
+ if (rtd->uid_cid_map[i] >= 0) {
+ sci_dma_stop(rtd->uid_cid_map[i],
+ dma->channels[i]);
+ }
+ }
+ rtd->irq_called = 0;
+ sp_asoc_pr_info("E\n");
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sprd_runtime_data *rtd = runtime->private_data;
+ snd_pcm_uframes_t x;
+ int now_pointer;
+ int bytes_of_pointer = 0;
+ int shift = 1;
+ int sel_max = 0;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no pointer");
+ return 0;
+ }
+
+ if (rtd->interleaved)
+ shift = 0;
+
+ if (rtd->uid_cid_map[0] >= 0) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[0],
+ substream) -
+ runtime->dma_addr;
+ bytes_of_pointer = now_pointer;
+ }
+ if (rtd->uid_cid_map[1] >= 0) {
+ now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[1],
+ substream) -
+ runtime->dma_addr - rtd->dma_addr_offset;
+ if (!bytes_of_pointer) {
+ bytes_of_pointer = now_pointer;
+ } else {
+ sel_max = (bytes_of_pointer < rtd->dma_pos_pre[0]);
+ sel_max ^= (now_pointer < rtd->dma_pos_pre[1]);
+ rtd->dma_pos_pre[0] = bytes_of_pointer;
+ rtd->dma_pos_pre[1] = now_pointer;
+ if (sel_max) {
+ bytes_of_pointer =
+ max(bytes_of_pointer, now_pointer) << shift;
+ } else {
+ bytes_of_pointer =
+ min(bytes_of_pointer, now_pointer) << shift;
+ }
+ }
+ }
+
+ x = bytes_to_frames(runtime, bytes_of_pointer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+
+#if 0
+ sp_asoc_pr_dbg("p=%d f=%d\n", bytes_of_pointer, x);
+#endif
+ return x;
+}
+
+static int sprd_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *srtd = substream->private_data;
+
+ if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) {
+ sp_asoc_pr_dbg("no mmap");
+ return 0;
+ }
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+#else
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ return remap_pfn_range(vma, vma->vm_start,
+ runtime->dma_addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+#endif
+}
+
+static struct snd_pcm_ops sprd_pcm_ops = {
+ .open = sprd_pcm_open,
+ .close = sprd_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = sprd_pcm_hw_params,
+ .hw_free = sprd_pcm_hw_free,
+ .prepare = sprd_pcm_prepare,
+ .trigger = sprd_pcm_trigger,
+ .pointer = sprd_pcm_pointer,
+ .mmap = sprd_pcm_mmap,
+};
+
+static int sprd_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = AUDIO_BUFFER_BYTES_MAX;
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ if (sprd_is_i2s(rtd->cpu_dai)) {
+ size = I2S_BUFFER_BYTES_MAX;
+ } else {
+ size = VBC_BUFFER_BYTES_MAX;
+ }
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (sprd_is_i2s(rtd->cpu_dai)
+ || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ && 0 == sprd_buffer_iram_backup())) {
+#endif
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ } else {
+ buf->private_data = buf;
+ buf->area = (void *)(s_iram_remap_base);
+ buf->addr = SPRD_IRAM_ALL_PHYS;
+ size = SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE);
+ }
+#endif
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static u64 sprd_pcm_dmamask = DMA_BIT_MASK(32);
+static struct snd_dma_buffer *save_p_buf = 0;
+#define VBC_CHAN (2)
+static struct snd_dma_buffer *save_c_buf[VBC_CHAN] = { 0 };
+
+static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_pcm_substream *substream;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s %s\n", __func__, sprd_dai_pcm_name(cpu_dai));
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &sprd_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (substream) {
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_i2s(cpu_dai) || !save_p_buf) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_p_buf = buf;
+ }
+ sp_asoc_pr_dbg("Playback alloc memery\n");
+ } else {
+ memcpy(buf, save_p_buf, sizeof(*buf));
+ sp_asoc_pr_dbg("Playback share memery\n");
+ }
+ }
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ if (substream) {
+ int id = cpu_dai->driver->id;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ if (sprd_is_vaudio(cpu_dai)) {
+ id -= VAUDIO_MAGIC_ID;
+ }
+ if (sprd_is_i2s(cpu_dai) || !save_c_buf[id]) {
+ ret = sprd_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ if (!sprd_is_i2s(cpu_dai)) {
+ save_c_buf[id] = buf;
+ }
+ sp_asoc_pr_dbg("Capture alloc memery %d\n", id);
+ } else {
+ memcpy(buf, save_c_buf[id], sizeof(*buf));
+ sp_asoc_pr_dbg("Capture share memery %d\n", id);
+ }
+ }
+out:
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static void sprd_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ if (buf->private_data)
+ sprd_buffer_iram_restore();
+ else
+#endif
+ dma_free_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ if (buf == save_p_buf) {
+ save_p_buf = 0;
+ }
+ for (i = 0; i < VBC_CHAN; i++) {
+ if (buf == save_c_buf[i]) {
+ save_c_buf[i] = 0;
+ }
+ }
+ }
+}
+
+static struct snd_soc_platform_driver sprd_soc_platform = {
+ .ops = &sprd_pcm_ops,
+ .pcm_new = sprd_pcm_new,
+ .pcm_free = sprd_pcm_free_dma_buffers,
+};
+
+static int sprd_soc_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &sprd_soc_platform);
+}
+
+static int sprd_soc_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sprd_pcm_of_match[] = {
+ {.compatible = "sprd,sprd-pcm",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, sprd_pcm_of_match);
+#endif
+
+static struct platform_driver sprd_pcm_driver = {
+ .driver = {
+ .name = "sprd-pcm-audio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_pcm_of_match),
+ },
+
+ .probe = sprd_soc_platform_probe,
+ .remove = sprd_soc_platform_remove,
+};
+
+static int __init sprd_pcm_driver_init(void)
+{
+ return platform_driver_register(&sprd_pcm_driver);
+}
+
+late_initcall(sprd_pcm_driver_init);
+
+
+MODULE_DESCRIPTION("SPRD ASoC PCM DMA");
+MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sprd-audio");
diff --git a/sound/soc/sprd/dai/sprd-pcm.h b/sound/soc/sprd/dai/sprd-pcm.h
new file mode 100644
index 00000000..149081e1
--- /dev/null
+++ b/sound/soc/sprd/dai/sprd-pcm.h
@@ -0,0 +1,49 @@
+/*
+ * sound/soc/sprd/dai/sprd-pcm.h
+ *
+ * SpreadTrum DMA for the pcm stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SPRD_PCM_H
+#define __SPRD_PCM_H
+
+#include <soc/sprd/dma.h>
+
+#define VBC_PCM_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+#define VBC_FIFO_FRAME_NUM (160)
+#define VBC_BUFFER_BYTES_MAX (128 * 1024)
+
+#define I2S_BUFFER_BYTES_MAX (64 * 1024)
+
+#define AUDIO_BUFFER_BYTES_MAX (VBC_BUFFER_BYTES_MAX + I2S_BUFFER_BYTES_MAX)
+
+struct sprd_pcm_dma_params {
+ char *name; /* stream identifier */
+ int channels[2]; /* channel id */
+ int irq_type; /* dma interrupt type */
+ struct sci_dma_cfg desc; /* dma description struct */
+ u32 dev_paddr[2]; /* device physical address for DMA */
+};
+
+typedef struct sci_dma_cfg sprd_dma_desc;
+
+static inline int sprd_pcm_dma_get_addr(int dma_ch,
+ struct snd_pcm_substream *substream)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return sci_dma_get_src_addr(dma_ch);
+ else
+ return sci_dma_get_dst_addr(dma_ch);
+}
+
+#endif /* __SPRD_PCM_H */
diff --git a/sound/soc/sprd/dai/vaudio/Kconfig b/sound/soc/sprd/dai/vaudio/Kconfig
new file mode 100644
index 00000000..7249ff93
--- /dev/null
+++ b/sound/soc/sprd/dai/vaudio/Kconfig
@@ -0,0 +1,2 @@
+config SND_SOC_SPRD_VAUDIO
+ tristate# "Vaudio"
diff --git a/sound/soc/sprd/dai/vaudio/Makefile b/sound/soc/sprd/dai/vaudio/Makefile
new file mode 100644
index 00000000..35056496
--- /dev/null
+++ b/sound/soc/sprd/dai/vaudio/Makefile
@@ -0,0 +1,6 @@
+# SPRD Vaudio Support
+SPRD_SOUND_TREE := sound/soc/sprd/
+KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai
+
+snd-soc-sprd-vaudio-objs := vaudio.o
+obj-$(CONFIG_SND_SOC_SPRD_VAUDIO) += snd-soc-sprd-vaudio.o
diff --git a/sound/soc/sprd/dai/vaudio/vaudio.c b/sound/soc/sprd/dai/vaudio/vaudio.c
new file mode 100644
index 00000000..5f42cc1b
--- /dev/null
+++ b/sound/soc/sprd/dai/vaudio/vaudio.c
@@ -0,0 +1,157 @@
+/*
+ * sound/soc/sprd/dai/vaudio/vaudio.c
+ *
+ * SpreadTrum Vaudio for the dsp stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" DSP ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#include "vaudio.h"
+
+static int sprd_vaudio_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = dai->card;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ kfree(snd_soc_dai_get_dma_data(dai, substream));
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 1;
+ }
+
+ return 0;
+}
+
+static void sprd_vaudio_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = dai->card;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 0;
+ }
+}
+
+static struct snd_soc_dai_ops sprd_vaudio_dai_ops = {
+ .startup = sprd_vaudio_startup,
+ .shutdown = sprd_vaudio_shutdown,
+};
+
+struct snd_soc_dai_driver sprd_vaudio_dai[] = {
+ {
+ .id = VAUDIO_MAGIC_ID,
+ .name = "vaudio",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &sprd_vaudio_dai_ops,
+ },
+ {
+ .id = VAUDIO_MAGIC_ID + 1,
+ .name = "vaudio-ad23",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2, /*ad23 */
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &sprd_vaudio_dai_ops,
+ },
+};
+
+static const struct snd_soc_component_driver sprd_vaudio_component = {
+ .name = "vaudio",
+};
+
+static int sprd_vaudio_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ ret =
+ snd_soc_register_component(&pdev->dev, &sprd_vaudio_component,
+ sprd_vaudio_dai,
+ ARRAY_SIZE(sprd_vaudio_dai));
+
+ sp_asoc_pr_dbg("return %i\n", ret);
+
+ return ret;
+}
+
+static int sprd_vaudio_drv_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id vaudio_of_match[] = {
+ {.compatible = "sprd,vaudio",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, vaudio_of_match);
+#endif
+
+static struct platform_driver sprd_vaudio_driver = {
+ .driver = {
+ .name = "vaudio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vaudio_of_match),
+ },
+
+ .probe = sprd_vaudio_drv_probe,
+ .remove = sprd_vaudio_drv_remove,
+};
+
+module_platform_driver(sprd_vaudio_driver);
+
+MODULE_DESCRIPTION("SPRD ASoC Vaudio CUP-DAI driver for the DSP");
+MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cpu-dai:sprd-vaudio");
diff --git a/sound/soc/sprd/dai/vaudio/vaudio.h b/sound/soc/sprd/dai/vaudio/vaudio.h
new file mode 100644
index 00000000..05377885
--- /dev/null
+++ b/sound/soc/sprd/dai/vaudio/vaudio.h
@@ -0,0 +1,22 @@
+/*
+ * sound/soc/sprd/dai/vaudio/vaudio.h
+ *
+ * SpreadTrum Vaudio for the dsp stream.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SPRD_VAUDIO_PCM_H
+#define __SPRD_VAUDIO_PCM_H
+
+#define VAUDIO_MAGIC_ID (0x5AD)
+
+#endif /* __SPRD_VAUDIO_PCM_H */
diff --git a/sound/soc/sprd/dai/vbc/Kconfig b/sound/soc/sprd/dai/vbc/Kconfig
new file mode 100644
index 00000000..73af1cb7
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/Kconfig
@@ -0,0 +1,19 @@
+menu "VBC"
+
+source "sound/soc/sprd/dai/vbc/r1p0/Kconfig"
+source "sound/soc/sprd/dai/vbc/r2p0/Kconfig"
+
+config SND_SOC_SPRD_VBC_LR_INVERT
+ bool "VBC Support L/R invert"
+ help
+ say Y then the VBC will support invert the L/R channel.
+
+config SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ bool "VBC SUPPORT DYNAMIC EQ"
+ default n if ARCH_SCX30G
+ help
+ say Y if it is r3p0 vbc, N in r2p0 & r1p0 vbc.
+ Now r2p0 and r3p0 use the same vbc driver, so we need
+ to add it to separate them.
+
+endmenu
diff --git a/sound/soc/sprd/dai/vbc/dfm.h b/sound/soc/sprd/dai/vbc/dfm.h
new file mode 100644
index 00000000..cdebaf8e
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/dfm.h
@@ -0,0 +1,27 @@
+/*
+ * sound/soc/sprd/dai/vbc/dfm.h
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __DFM_H
+#define __DFM_H
+
+#define DFM_MAGIC_ID 0x7BC
+
+struct sprd_dfm_priv {
+ int hw_rate;
+ int sample_rate;
+};
+
+#endif /* __DFM_H */
diff --git a/sound/soc/sprd/dai/vbc/r1p0/Kconfig b/sound/soc/sprd/dai/vbc/r1p0/Kconfig
new file mode 100644
index 00000000..67ad7147
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/Kconfig
@@ -0,0 +1,3 @@
+config SND_SOC_SPRD_VBC_R1P0
+ tristate # "VBC_R1P0"
+ help
diff --git a/sound/soc/sprd/dai/vbc/r1p0/Makefile b/sound/soc/sprd/dai/vbc/r1p0/Makefile
new file mode 100644
index 00000000..3c813974
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/Makefile
@@ -0,0 +1,6 @@
+# SPRD VBC Support
+SPRD_SOUND_TREE := sound/soc/sprd/
+KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai -I$(SPRD_SOUND_TREE)/dai/vbc/
+
+snd-soc-sprd-vbc-r1p0-objs := vbc.o
+obj-$(CONFIG_SND_SOC_SPRD_VBC_R1P0) += snd-soc-sprd-vbc-r1p0.o
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c
new file mode 100644
index 00000000..0f735867
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c
@@ -0,0 +1,1571 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c
+ *
+ * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#include "vbc-codec.h"
+
+#define FUN_REG(f) ((unsigned short)(-((f) + 1)))
+#define SOC_REG(r) ((unsigned short)(r))
+
+struct vbc_fw_header {
+ char magic[VBC_EQ_FIRMWARE_MAGIC_LEN];
+ u32 profile_version;
+ u32 num_profile;
+};
+
+struct vbc_eq_profile {
+ char magic[VBC_EQ_FIRMWARE_MAGIC_LEN];
+ char name[VBC_EQ_PROFILE_NAME_MAX];
+ /* TODO */
+ u32 effect_paras[VBC_EFFECT_PARAS_LEN];
+};
+
+static const u32 vbc_eq_profile_default[VBC_EFFECT_PARAS_LEN] = {
+/* TODO the default register value */
+ 0x00000000, /* DAPATCHCTL */
+ 0x00001818, /* DADGCTL */
+ 0x0000007F, /* DAHPCTL */
+ 0x00000000, /* DAALCCTL0 */
+ 0x00000000, /* DAALCCTL1 */
+ 0x00000000, /* DAALCCTL2 */
+ 0x00000000, /* DAALCCTL3 */
+ 0x00000000, /* DAALCCTL4 */
+ 0x00000000, /* DAALCCTL5 */
+ 0x00000000, /* DAALCCTL6 */
+ 0x00000000, /* DAALCCTL7 */
+ 0x00000000, /* DAALCCTL8 */
+ 0x00000000, /* DAALCCTL9 */
+ 0x00000000, /* DAALCCTL10 */
+ 0x00000183, /* STCTL0 */
+ 0x00000183, /* STCTL1 */
+ 0x00000000, /* ADPATCHCTL */
+ 0x00001818, /* ADDGCTL */
+ 0x00000000, /* HPCOEF0 */
+};
+
+struct vbc_equ {
+ struct device *dev;
+ struct snd_soc_codec *codec;
+ int is_active;
+ int is_loading;
+ int valid;
+ int now_profile;
+ struct vbc_fw_header hdr;
+ struct vbc_eq_profile *data;
+ void (*vbc_eq_apply) (struct snd_soc_codec * codec, void *data,
+ int vbc_idx);
+};
+
+static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx);
+
+#define VBC_DG_VAL_MAX (0x7F)
+
+typedef int (*vbc_dg_set) (int enable, int dg);
+struct vbc_dg {
+ int dg_switch[2];
+ int dg_val[2];
+ vbc_dg_set dg_set[2];
+};
+
+struct st_hpf_dg {
+ int hpf_switch[2];
+ int dg_val[2];
+ int hpf_val[2];
+};
+
+/*vbc mux setting*/
+enum {
+ SPRD_VBC_MUX_START = 0,
+ SPRD_VBC_AD0_INMUX,
+ SPRD_VBC_AD1_INMUX,
+ SPRD_VBC_AD_IISMUX,
+ SPRD_VBC_MUX_MAX
+};
+
+#define IS_SPRD_VBC_MUX_RANG(reg) ((reg) >= SPRD_VBC_MUX_START && (reg) < (SPRD_VBC_MUX_MAX))
+#define SPRD_VBC_MUX_IDX(reg) (reg - SPRD_VBC_MUX_START)
+
+static const char *vbc_mux_debug_str[SPRD_VBC_MUX_MAX] = {
+ "ad0 inmux",
+ "ad1 inmux",
+ "ad iis mux",
+};
+
+typedef int (*sprd_vbc_mux_set) (int sel);
+struct sprd_vbc_mux_op {
+ int val;
+ sprd_vbc_mux_set set;
+};
+
+enum {
+ VBC_LOOP_SWITCH_START = SPRD_VBC_MUX_MAX,
+ VBC_AD_LOOP_SWITCH = VBC_LOOP_SWITCH_START,
+ VBC_LOOP_SWITCH_MAX
+};
+
+#define IS_SPRD_VBC_SWITCH_RANG(reg) ((reg) >= VBC_LOOP_SWITCH_START && (reg) < (VBC_LOOP_SWITCH_MAX))
+#define SPRD_VBC_SWITCH_IDX(reg) (reg - VBC_LOOP_SWITCH_START)
+
+struct vbc_codec_priv {
+ struct snd_soc_codec *codec;
+ struct vbc_equ vbc_eq_setting;
+ struct mutex load_mutex;
+ int vbc_control;
+ int vbc_loop_switch[SPRD_VBC_SWITCH_IDX(VBC_LOOP_SWITCH_MAX)];
+ struct vbc_dg dg[VBC_IDX_MAX];
+ int vbc_da_iis_port;
+ int adc_dgmux_val[ADC_DGMUX_MAX];
+ struct st_hpf_dg st_dg;
+ struct sprd_vbc_mux_op sprd_vbc_mux[SPRD_VBC_MUX_MAX];
+};
+
+static int vbc_ad0_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_INMUX_SHIFT,
+ VBADPATH_AD0_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad1_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_INMUX_SHIFT,
+ VBADPATH_AD1_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad0_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_DGMUX_SHIFT,
+ VBADPATH_AD0_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad1_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_DGMUX_SHIFT,
+ VBADPATH_AD1_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad_iismux_set(int port)
+{
+ vbc_reg_update(VBIISSEL, port << VBIISSEL_AD_PORT_SHIFT,
+ VBIISSEL_AD_PORT_MASK);
+ return 0;
+}
+
+static int vbc_da_iismux_set(int port)
+{
+ vbc_reg_update(VBIISSEL, port << VBIISSEL_DA_PORT_SHIFT,
+ VBIISSEL_DA_PORT_MASK);
+ return 0;
+}
+
+static int vbc_try_ad_iismux_set(int port);
+
+static sprd_vbc_mux_set vbc_mux_cfg[SPRD_VBC_MUX_MAX] = {
+ vbc_ad0_inmux_set,
+ vbc_ad1_inmux_set,
+ vbc_try_ad_iismux_set,
+};
+
+static inline int vbc_da0_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(DADGCTL, 0x80 | (0xFF & dg), 0xFF);
+ } else {
+ vbc_reg_update(DADGCTL, 0, 0x80);
+ }
+ return 0;
+}
+
+static inline int vbc_da1_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(DADGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00);
+ } else {
+ vbc_reg_update(DADGCTL, 0, 0x8000);
+ }
+ return 0;
+}
+
+static inline int vbc_ad0_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDGCTL, 0x80 | (0xFF & dg), 0xFF);
+ } else {
+ vbc_reg_update(ADDGCTL, 0, 0x80);
+ }
+ return 0;
+}
+
+static inline int vbc_ad1_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00);
+ } else {
+ vbc_reg_update(ADDGCTL, 0, 0x8000);
+ }
+ return 0;
+}
+
+static inline int vbc_st0_dg_set(int dg)
+{
+ vbc_reg_update(STCTL0, (0x7F & dg) << 4, 0x7F0);
+ return 0;
+}
+
+static inline int vbc_st1_dg_set(int dg)
+{
+ vbc_reg_update(STCTL1, (0x7F & dg) << 4, 0x7F0);
+ return 0;
+}
+
+static inline int vbc_st0_hpf_set(int enable, int hpf_val)
+{
+ if (enable) {
+ vbc_reg_update(STCTL0, BIT(VBST_HPF_0), BIT(VBST_HPF_0));
+ vbc_reg_update(STCTL0, 0xF & hpf_val, 0xF);
+ } else {
+ vbc_reg_update(STCTL0, 0, BIT(VBST_HPF_0));
+ vbc_reg_update(STCTL0, 3, 0xF);
+ }
+ return 0;
+}
+
+static inline int vbc_st1_hpf_set(int enable, int hpf_val)
+{
+ if (enable) {
+ vbc_reg_update(STCTL1, BIT(VBST_HPF_1), BIT(VBST_HPF_1));
+ vbc_reg_update(STCTL1, 0xF & hpf_val, 0xF);
+ } else {
+ vbc_reg_update(STCTL1, 0, BIT(VBST_HPF_1));
+ vbc_reg_update(STCTL1, 3, 0xF);
+ }
+ return 0;
+}
+
+static int vbc_try_dg_set(struct vbc_codec_priv *vbc_codec, int vbc_idx, int id)
+{
+ struct vbc_dg *p_dg = &vbc_codec->dg[vbc_idx];
+ int dg = p_dg->dg_val[id];
+ if (p_dg->dg_switch[id]) {
+ p_dg->dg_set[id] (1, dg);
+ } else {
+ p_dg->dg_set[id] (0, dg);
+ }
+ return 0;
+}
+
+static int vbc_try_st_dg_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg;
+ if (id == VBC_LEFT) {
+ vbc_st0_dg_set(p_st_dg->dg_val[id]);
+ } else {
+ vbc_st1_dg_set(p_st_dg->dg_val[id]);
+ }
+ return 0;
+}
+
+static int vbc_try_st_hpf_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg;
+ if (id == VBC_LEFT) {
+ vbc_st0_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]);
+ } else {
+ vbc_st1_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]);
+ }
+ return 0;
+}
+
+static int vbc_try_da_iismux_set(int port)
+{
+ return vbc_da_iismux_set(port ? (port + 1) : 0);
+}
+
+static int vbc_try_ad_iismux_set(int port)
+{
+ return vbc_ad_iismux_set(port);
+}
+
+static int vbc_try_ad_dgmux_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ switch (id) {
+ case ADC0_DGMUX:
+ vbc_ad0_dgmux_set(vbc_codec->adc_dgmux_val[ADC0_DGMUX]);
+ break;
+ case ADC1_DGMUX:
+ vbc_ad1_dgmux_set(vbc_codec->adc_dgmux_val[ADC1_DGMUX]);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DEBUG
+static const char *get_event_name(int event)
+{
+ const char *ev_name;
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ev_name = "PRE_PMU";
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ ev_name = "POST_PMU";
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ev_name = "PRE_PMD";
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ev_name = "POST_PMD";
+ break;
+ default:
+ BUG();
+ return 0;
+ }
+ return ev_name;
+}
+#endif
+
+static const char *ad0_inmux_txt[] = {
+ "IIS0AD0", "IIS1AD0", "NOINPUT",
+};
+
+static const char *ad1_inmux_txt[] = {
+ "IIS1AD1", "IIS0AD1", "NOINPUT",
+};
+
+static const char *ad_iis_txt[] = {
+ "AUDIIS0", "DIGFM",
+};
+
+#define SPRD_VBC_ENUM(xreg, xmax, xtexts)\
+ SOC_ENUM_SINGLE(FUN_REG(xreg), 0, xmax, xtexts)
+
+static const struct soc_enum vbc_mux_sel_enum[SPRD_VBC_MUX_MAX] = {
+ /*AD INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_AD0_INMUX, 4, ad0_inmux_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_AD1_INMUX, 4, ad1_inmux_txt),
+ /*IIS INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_AD_IISMUX, 4, ad_iis_txt),
+};
+
+static int vbc_chan_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int vbc_idx = FUN_REG(w->reg);
+ int chan = w->shift;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s(%s%d) Event is %s\n", __func__,
+ vbc_get_name(vbc_idx), chan, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ vbc_chan_enable(1, vbc_idx, chan);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ vbc_chan_enable(0, vbc_idx, chan);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int vbc_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ vbc_power(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ vbc_power(0);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int dfm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event));
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ fm_set_vbc_buffer_size(); /*No use in FM function, just for debug VBC */
+ vbc_try_st_dg_set(vbc_codec, VBC_LEFT);
+ vbc_try_st_dg_set(vbc_codec, VBC_RIGHT);
+ if (!p_eq_setting->codec)
+ p_eq_setting->codec = codec;
+ /*eq setting */
+ if (p_eq_setting->is_active && p_eq_setting->data)
+ vbc_eq_try_apply(codec, VBC_PLAYBACK);
+ vbc_enable(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ vbc_enable(0);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int aud_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ unsigned int vbc_idx = FUN_REG(w->reg);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n Chan is %s", __func__,
+ get_event_name(event), vbc_get_name(vbc_idx));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /*eq setting */
+ if (!p_eq_setting->codec)
+ p_eq_setting->codec = codec;
+ if (p_eq_setting->is_active && p_eq_setting->data)
+ vbc_eq_try_apply(codec, vbc_idx);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mux_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ unsigned int id = SPRD_VBC_MUX_IDX(FUN_REG(w->reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]);
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__,
+ vbc_mux_debug_str[id], mux->val, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mux->set = vbc_mux_cfg[id];
+ ret = mux->set(mux->val);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ mux->set = 0;
+ ret = vbc_mux_cfg[id] (0);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sprd_vbc_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]);
+
+ ucontrol->value.enumerated.item[0] = mux->val;
+
+ return 0;
+}
+
+static int sprd_vbc_mux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg));
+ unsigned int max = e->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]);
+ int ret = 0;
+
+ if (mux->val == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sp_asoc_pr_info("Set MUX[%s] to %d\n", vbc_mux_debug_str[reg],
+ ucontrol->value.enumerated.item[0]);
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+
+ /*notice the sequence */
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+ /*update reg: must be set after snd_soc_dapm_put_enum_double->change = snd_soc_test_bits(widget->codec, e->reg, mask, val); */
+ mux->val = (ucontrol->value.enumerated.item[0] & mask);
+ if (mux->set) {
+ ret = mux->set(mux->val);
+ }
+
+ return ret;
+}
+
+#define SPRD_VBC_MUX(xname, xenum) \
+ SOC_DAPM_ENUM_EXT(xname, xenum, sprd_vbc_mux_get, sprd_vbc_mux_put)
+
+static const struct snd_kcontrol_new vbc_mux[SPRD_VBC_MUX_MAX] = {
+ SPRD_VBC_MUX("AD0 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD0_INMUX]),
+ SPRD_VBC_MUX("AD1 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD1_INMUX]),
+ SPRD_VBC_MUX("AD IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD_IISMUX]),
+};
+
+#define VBC_DAPM_MUX_E(wname, wreg) \
+ SND_SOC_DAPM_MUX_E(wname, FUN_REG(wreg), 0, 0, &vbc_mux[wreg], mux_event, \
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)
+
+static int vbc_loop_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg));
+ ucontrol->value.integer.value[0] = vbc_codec->vbc_loop_switch[id];
+ return 0;
+}
+
+static int vbc_loop_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg));
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->vbc_loop_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC AD LOOP Switch Set %x\n",
+ (int)ucontrol->value.integer.value[0]);
+
+ snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ vbc_codec->vbc_loop_switch[id] = ret;
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new vbc_loop_control[] = {
+ SOC_SINGLE_EXT("Switch",
+ FUN_REG(VBC_AD_LOOP_SWITCH),
+ 0, 1, 0,
+ vbc_loop_switch_get,
+ vbc_loop_switch_put),
+};
+
+static const struct snd_soc_dapm_widget vbc_codec_dapm_widgets[] = {
+ /*power */
+ SND_SOC_DAPM_SUPPLY("VBC Power", SND_SOC_NOPM, 0, 0,
+ vbc_power_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /*ADC inmux */
+ VBC_DAPM_MUX_E("AD0 INMUX", SPRD_VBC_AD0_INMUX),
+ VBC_DAPM_MUX_E("AD1 INMUX", SPRD_VBC_AD1_INMUX),
+
+ /*IIS MUX */
+ VBC_DAPM_MUX_E("AD IISMUX", SPRD_VBC_AD_IISMUX),
+
+ /*ST Switch */
+ SND_SOC_DAPM_PGA_S("ST0 Switch", 4, SOC_REG(STCTL0), VBST_EN_0, 0, NULL,
+ 0),
+ SND_SOC_DAPM_PGA_S("ST1 Switch", 4, SOC_REG(STCTL1), VBST_EN_1, 0, NULL,
+ 0),
+
+ /*FM Mixer */
+ SND_SOC_DAPM_PGA_S("DA0 FM Mixer", 4, SOC_REG(DAPATCHCTL),
+ VBDAPATH_DA0_ADDFM_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA_S("DA1 FM Mixer", 4, SOC_REG(DAPATCHCTL),
+ VBDAPATH_DA1_ADDFM_SHIFT, 0, NULL, 0),
+
+ SND_SOC_DAPM_LINE("DFM", dfm_event),
+
+ /*VBC Chan Switch */
+ SND_SOC_DAPM_PGA_S("DA0 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_LEFT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("DA1 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_RIGHT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD0 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_LEFT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD1 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_RIGHT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /*AUD loop var vbc switch */
+ SND_SOC_DAPM_PGA_S("Aud input", 4, FUN_REG(VBC_CAPTRUE), 0, 0,
+ aud_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SWITCH("Aud Loop in VBC", SND_SOC_NOPM, 0, 0,
+ &vbc_loop_control[SPRD_VBC_SWITCH_IDX
+ (VBC_AD_LOOP_SWITCH)]),
+};
+
+/* sprd_vbc supported interconnection*/
+static const struct snd_soc_dapm_route vbc_codec_intercon[] = {
+ /************************power********************************/
+ /* digital fm playback need to open DA Clk and DA power */
+ {"DFM", NULL, "VBC Power"},
+
+ /********************** capture in path in vbc ********************/
+ /* AD input route */
+ {"Aud Loop in VBC", "Switch", "Aud input"},
+
+ /* AD */
+ {"AD IISMUX", "DIGFM", "Dig FM Jack"},
+ {"AD IISMUX", "AUDIIS0", "Aud Loop in VBC"},
+
+ {"AD0 INMUX", "IIS0AD0", "AD IISMUX"},
+ {"AD0 INMUX", "IIS1AD0", "AD IISMUX"},
+ {"AD1 INMUX", "IIS1AD1", "AD IISMUX"},
+ {"AD1 INMUX", "IIS0AD1", "AD IISMUX"},
+
+ {"AD0 Switch", NULL, "AD0 INMUX"},
+ {"AD1 Switch", NULL, "AD1 INMUX"},
+
+ /********************** fm playback route ********************/
+ /*ST route */
+ {"ST0 INMUX", NULL, "AD0 Switch"},
+ {"ST1 INMUX", NULL, "AD1 Switch"},
+
+ {"ST0 Switch", NULL, "ST0 INMUX"},
+ {"ST1 Switch", NULL, "ST1 INMUX"},
+ {"DA0 Switch", NULL, "ST0 Switch"},
+ {"DA1 Switch", NULL, "ST1 Switch"},
+ {"DA0 FM Mixer", NULL, "DA0 Switch"},
+ {"DA1 FM Mixer", NULL, "DA1 Switch"},
+ {"DFM", NULL, "DA0 FM Mixer"},
+ {"DFM", NULL, "DA1 FM Mixer"},
+};
+
+int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai)
+{
+ struct vbc_codec_priv *vbc_codec = dev_get_drvdata(dai->dev);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ sp_asoc_pr_dbg("%s %s\n", __func__, vbc_get_name(vbc_idx));
+
+ switch (vbc_idx) {
+ case VBC_PLAYBACK:
+ vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port);
+ break;
+ case VBC_CAPTRUE:
+ vbc_try_ad_iismux_set(vbc_codec->sprd_vbc_mux
+ [SPRD_VBC_AD_IISMUX].val);
+ vbc_try_ad_dgmux_set(vbc_codec, 0);
+ vbc_try_ad_dgmux_set(vbc_codec, 1);
+ break;
+ }
+
+ if (p_eq_setting->is_active && p_eq_setting->data)
+ vbc_eq_try_apply(vbc_codec->codec, vbc_idx);
+
+ vbc_try_dg_set(vbc_codec, vbc_idx, VBC_LEFT);
+ vbc_try_dg_set(vbc_codec, vbc_idx, VBC_RIGHT);
+
+ return 0;
+}
+
+/*
+ * proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+static void vbc_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ int reg;
+
+ snd_iprintf(buffer, "vbc register dump\n");
+ for (reg = ARM_VB_BASE + 0x10; reg < ARM_VB_END; reg += 0x10) {
+ snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n",
+ (reg - ARM_VB_BASE)
+ , vbc_reg_read(reg + 0x00)
+ , vbc_reg_read(reg + 0x04)
+ , vbc_reg_read(reg + 0x08)
+ , vbc_reg_read(reg + 0x0C)
+ );
+ }
+}
+
+static void vbc_proc_init(struct snd_soc_codec *codec)
+{
+ struct snd_info_entry *entry;
+
+ if (!snd_card_proc_new(codec->card->snd_card, "vbc", &entry))
+ snd_info_set_text_ops(entry, NULL, vbc_proc_read);
+}
+#else /* !CONFIG_PROC_FS */
+static inline void vbc_proc_init(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static int vbc_eq_reg_offset(u32 reg)
+{
+ int i = 0;
+ if ((reg >= DAPATCHCTL) && (reg <= ADDGCTL)) {
+ i = (reg - DAPATCHCTL) >> 2;
+ } else if ((reg >= HPCOEF0) && (reg <= HPCOEF42)) {
+ i = ((reg - HPCOEF0) >> 2) + ((ADDGCTL - DAPATCHCTL) >> 2) + 1;
+ }
+ BUG_ON(i >= VBC_EFFECT_PARAS_LEN);
+ return i;
+}
+
+static inline void vbc_eq_reg_set(u32 reg, void *data)
+{
+ u32 *effect_paras = (u32 *) data;
+ vbc_reg_write(reg, effect_paras[vbc_eq_reg_offset(reg)]);
+}
+
+static inline void vbc_eq_reg_set_range(u32 reg_start, u32 reg_end, void *data)
+{
+ u32 reg_addr;
+ for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) {
+ vbc_eq_reg_set(reg_addr, data);
+ }
+}
+
+static void vbc_eq_reg_apply(struct snd_soc_codec *codec, void *data,
+ int vbc_idx)
+{
+ if (vbc_idx == VBC_PLAYBACK) {
+ vbc_eq_reg_set_range(DAALCCTL0, DAALCCTL10, data);
+ vbc_eq_reg_set_range(HPCOEF0, HPCOEF42, data);
+
+ vbc_eq_reg_set(DAHPCTL, data);
+ /*FM function maybe use this regs,and EQ setting don't use them */
+ /*vbc_da_eq_reg_set(DAPATCHCTL, data); */
+
+ /*vbc_da_eq_reg_set(STCTL0, data); */
+ /*vbc_da_eq_reg_set(STCTL1, data); */
+
+ } else {
+ vbc_eq_reg_set(ADPATCHCTL, data);
+ }
+}
+
+static void vbc_eq_profile_apply(struct snd_soc_codec *codec, void *data,
+ int vbc_idx)
+{
+ if (codec) {
+ vbc_eq_reg_apply(codec, data, vbc_idx);
+ }
+}
+
+static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx)
+{
+ vbc_eq_profile_apply(codec, &vbc_eq_profile_default, vbc_idx);
+}
+
+static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ u32 *data;
+
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)p_eq_setting->vbc_eq_apply);
+ if (p_eq_setting->vbc_eq_apply) {
+ struct vbc_eq_profile *now;
+ mutex_lock(&vbc_codec->load_mutex);
+ now = &p_eq_setting->data[p_eq_setting->now_profile];
+ data = now->effect_paras;
+ sp_asoc_pr_info("VBC %s EQ Apply '%s'\n",
+ vbc_get_name(vbc_idx), now->name);
+ p_eq_setting->vbc_eq_apply(codec, data, vbc_idx);
+ mutex_unlock(&vbc_codec->load_mutex);
+ }
+}
+
+static int vbc_eq_profile_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ ucontrol->value.integer.value[0] = p_eq_setting->now_profile;
+ return 0;
+}
+
+static int vbc_eq_profile_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int vbc_idx = FUN_REG(mc->reg);
+ int profile_max;
+
+ profile_max = p_eq_setting->hdr.num_profile;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == p_eq_setting->now_profile) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s EQ Select %ld max %d\n",
+ vbc_get_name(vbc_idx),
+ ucontrol->value.integer.value[0], profile_max);
+
+ if (ret < profile_max) {
+ p_eq_setting->now_profile = ret;
+ }
+
+ if (p_eq_setting->is_active && p_eq_setting->data)
+ vbc_eq_try_apply(codec, vbc_idx);
+
+ return ret;
+}
+
+static int vbc_eq_loading(struct snd_soc_codec *codec)
+{
+ int ret;
+ const u8 *fw_data;
+ const struct firmware *fw;
+ int i;
+ int offset = 0;
+ int len = 0;
+ int old_num_profile;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+ mutex_lock(&vbc_codec->load_mutex);
+ p_eq_setting->is_loading = 1;
+
+ /* request firmware for VBC EQ */
+ ret = request_firmware(&fw, "vbc_eq", p_eq_setting->dev);
+ if (ret != 0) {
+ pr_err("ERR:Failed to load firmware: %d\n", ret);
+ goto req_fw_err;
+ }
+ fw_data = fw->data;
+ old_num_profile = p_eq_setting->hdr.num_profile;
+
+ memcpy(&p_eq_setting->hdr, fw_data, sizeof(p_eq_setting->hdr));
+
+ if (strncmp
+ (p_eq_setting->hdr.magic, VBC_EQ_FIRMWARE_MAGIC_ID,
+ VBC_EQ_FIRMWARE_MAGIC_LEN)) {
+ pr_err("ERR:Firmware magic error!\n");
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ if (p_eq_setting->hdr.profile_version != VBC_EQ_PROFILE_VERSION) {
+ pr_err("ERR:Firmware support version is 0x%x!\n",
+ VBC_EQ_PROFILE_VERSION);
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ if (p_eq_setting->hdr.num_profile > VBC_EQ_PROFILE_CNT_MAX) {
+ pr_err
+ ("ERR:Firmware profile to large at %d, max count is %d!\n",
+ p_eq_setting->hdr.num_profile, VBC_EQ_PROFILE_CNT_MAX);
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ if (old_num_profile != p_eq_setting->hdr.num_profile) {
+ vbc_safe_kfree(&p_eq_setting->data);
+ }
+
+ len = p_eq_setting->hdr.num_profile * sizeof(struct vbc_eq_profile);
+ if (p_eq_setting->data == NULL) {
+ p_eq_setting->data = kzalloc(len, GFP_KERNEL);
+ if (p_eq_setting->data == NULL) {
+ ret = -ENOMEM;
+ goto eq_out;
+ }
+ }
+ offset = sizeof(struct vbc_fw_header);
+ memcpy(p_eq_setting->data, fw_data + offset, len);
+
+ for (i = 0; i < p_eq_setting->hdr.num_profile; i++) {
+ const char *magic = (char *)p_eq_setting->data[i].magic;
+ if (strncmp
+ (magic, VBC_EQ_FIRMWARE_MAGIC_ID,
+ VBC_EQ_FIRMWARE_MAGIC_LEN)) {
+ pr_err
+ ("ERR:Firmware profile[%d] magic error!magic: %s \n",
+ i, magic);
+ ret = -EINVAL;
+ goto eq_out;
+ }
+ }
+
+ ret = 0;
+ goto eq_out;
+
+eq_out:
+ release_firmware(fw);
+req_fw_err:
+ p_eq_setting->is_loading = 0;
+ mutex_unlock(&vbc_codec->load_mutex);
+ if (ret >= 0) {
+ if (p_eq_setting->is_active && p_eq_setting->data)
+ vbc_eq_try_apply(codec, 0);
+ }
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static int vbc_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE);
+ if (ret >= 0)
+ ucontrol->value.integer.value[0] =
+ (ret == AUDIO_TO_DSP_CTRL ? 0 : 1);
+ return ret;
+}
+
+static int vbc_switch_reg_val[];
+static int vbc_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+
+ sp_asoc_pr_info("VBC Switch to %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ ret = ucontrol->value.integer.value[0];
+ ret = arch_audio_vbc_switch(ret == 0 ?
+ AUDIO_TO_DSP_CTRL : AUDIO_TO_ARM_CTRL);
+
+ return ret;
+}
+
+static int vbc_eq_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ ucontrol->value.integer.value[0] = p_eq_setting->is_active;
+ return 0;
+}
+
+static int vbc_eq_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int ret;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == p_eq_setting->is_active) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC EQ Switch %s\n",
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ if ((ret == 0) || (ret == 1)) {
+ p_eq_setting->is_active = ret;
+ if (p_eq_setting->is_active && p_eq_setting->data) {
+ p_eq_setting->vbc_eq_apply = vbc_eq_profile_apply;
+ vbc_eq_try_apply(codec, id);
+ } else {
+ p_eq_setting->vbc_eq_apply = 0;
+ vbc_eq_profile_close(codec, id);
+ }
+ }
+
+ return ret;
+}
+
+static int vbc_eq_load_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ ucontrol->value.integer.value[0] = p_eq_setting->is_loading;
+ return 0;
+}
+
+static int vbc_eq_load_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int ret;
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+
+ sp_asoc_pr_info("VBC EQ %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == 1) {
+ ret = vbc_eq_loading(codec);
+ }
+
+ return ret;
+}
+
+static int vbc_dg_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_val[id];
+ return 0;
+}
+
+static int vbc_dg_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->dg[vbc_idx].dg_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s%d DG set 0x%02x\n",
+ vbc_get_name(vbc_idx), id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->dg[vbc_idx].dg_val[id] = ret;
+ }
+
+ vbc_try_dg_set(vbc_codec, vbc_idx, id);
+
+ return ret;
+}
+
+static int vbc_st_dg_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.dg_val[id];
+ return 0;
+}
+
+static int vbc_st_dg_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.dg_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d DG set 0x%02x\n", id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->st_dg.dg_val[id] = ret;
+ }
+
+ vbc_try_st_dg_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int vbc_dg_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_switch[id];
+ return 0;
+}
+
+static int vbc_dg_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->dg[vbc_idx].dg_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s%d DG Switch %s\n",
+ vbc_get_name(vbc_idx), id,
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ vbc_codec->dg[vbc_idx].dg_switch[id] = ret;
+
+ vbc_try_dg_set(vbc_codec, vbc_idx, id);
+
+ return ret;
+}
+
+static int vbc_st_hpf_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_switch[id];
+ return 0;
+}
+
+static int vbc_st_hpf_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.hpf_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d HPF Switch %s\n", id,
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ vbc_codec->st_dg.hpf_switch[id] = ret;
+
+ vbc_try_st_hpf_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int vbc_st_hpf_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_val[id];
+ return 0;
+}
+
+static int vbc_st_hpf_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.hpf_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d HPF set 0x%02x\n", id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->st_dg.hpf_val[id] = ret;
+ }
+
+ vbc_try_st_hpf_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int adc_dgmux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->adc_dgmux_val[id];
+ return 0;
+}
+
+static int adc_dgmux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->adc_dgmux_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC AD%d DG mux : %ld\n", id,
+ ucontrol->value.integer.value[0]);
+
+ vbc_codec->adc_dgmux_val[id] = ret;
+ vbc_try_ad_dgmux_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int dac_iismux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ ucontrol->value.integer.value[0] = vbc_codec->vbc_da_iis_port;
+ return 0;
+}
+
+static int dac_iismux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+
+ if (vbc_codec->vbc_da_iis_port == ucontrol->value.integer.value[0])
+ return 0;
+
+ sp_asoc_pr_info("VBC DA output to %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ vbc_codec->vbc_da_iis_port = ucontrol->value.integer.value[0];
+
+ vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port);
+
+ return 1;
+}
+
+static const char *switch_function[] = { "dsp", "arm" };
+static const char *eq_load_function[] = { "idle", "loading" };
+static const char *da_iis_mux_function[] =
+ { "sprd-codec", "ext-codec-4", "ext-codec-6" };
+static const char *fm_sample_rate_function[] = { "32000", "48000" };
+
+static const struct soc_enum vbc_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, switch_function),
+ SOC_ENUM_SINGLE_EXT(2, eq_load_function),
+ SOC_ENUM_SINGLE_EXT(3, da_iis_mux_function),
+ SOC_ENUM_SINGLE_EXT(2, fm_sample_rate_function),
+};
+
+static const struct snd_kcontrol_new vbc_codec_snd_controls[] = {
+ SOC_ENUM_EXT("VBC Switch", vbc_enum[0], vbc_switch_get,
+ vbc_switch_put),
+ SOC_SINGLE_EXT("VBC EQ Switch", FUN_REG(VBC_PLAYBACK), 0, 1, 0,
+ vbc_eq_switch_get,
+ vbc_eq_switch_put),
+ SOC_ENUM_EXT("VBC EQ Update", vbc_enum[1], vbc_eq_load_get,
+ vbc_eq_load_put),
+
+ SOC_SINGLE_EXT("VBC DACL DG Set", FUN_REG(VBC_LEFT),
+ VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC DACR DG Set", FUN_REG(VBC_RIGHT),
+ VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADCL DG Set", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADCR DG Set", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC STL DG Set", FUN_REG(VBC_LEFT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put),
+ SOC_SINGLE_EXT("VBC STR DG Set", FUN_REG(VBC_RIGHT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put),
+
+ SOC_SINGLE_EXT("VBC DACL DG Switch", FUN_REG(VBC_LEFT),
+ VBC_PLAYBACK, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC DACR DG Switch", FUN_REG(VBC_RIGHT),
+ VBC_PLAYBACK, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADCL DG Switch", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADCR DG Switch", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC STL HPF Switch", FUN_REG(VBC_LEFT),
+ 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put),
+ SOC_SINGLE_EXT("VBC STR HPF Switch", FUN_REG(VBC_RIGHT),
+ 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put),
+ SOC_SINGLE_EXT("VBC STL HPF Set", FUN_REG(VBC_LEFT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put),
+ SOC_SINGLE_EXT("VBC STR HPF Set", FUN_REG(VBC_RIGHT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put),
+
+ SOC_SINGLE_EXT("VBC AD0 DG Mux", FUN_REG(ADC0_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_SINGLE_EXT("VBC AD1 DG Mux", FUN_REG(ADC1_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_ENUM_EXT("VBC DA IIS Mux", vbc_enum[2], dac_iismux_get,
+ dac_iismux_put),
+ SOC_SINGLE_EXT("VBC DA EQ Profile Select", FUN_REG(VBC_PLAYBACK), 0,
+ VBC_EQ_PROFILE_CNT_MAX, 0,
+ vbc_eq_profile_get, vbc_eq_profile_put),
+
+};
+
+static unsigned int vbc_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ /* Because snd_soc_update_bits reg is 16 bits short type,
+ so muse do following convert
+ */
+ if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) {
+ reg |= SPRD_VBC_BASE_HI;
+ return vbc_reg_read(reg);
+ } else if (IS_SPRD_VBC_MUX_RANG(FUN_REG(reg))) {
+ int id = SPRD_VBC_MUX_IDX(FUN_REG(reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]);
+ return mux->val;
+ } else if (IS_SPRD_VBC_SWITCH_RANG(FUN_REG(reg))) {
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(reg));
+ return vbc_codec->vbc_loop_switch[id];
+ }
+
+ sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg);
+ return 0;
+}
+
+static int vbc_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) {
+ reg |= SPRD_VBC_BASE_HI;
+ return vbc_reg_write(reg, val);
+ }
+ sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg);
+ return 0;
+}
+
+static int vbc_codec_soc_probe(struct snd_soc_codec *codec)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ codec->dapm.idle_bias_off = 1;
+
+ vbc_codec->codec = codec;
+ p_eq_setting->codec = codec;
+
+ vbc_proc_init(codec);
+
+ return ret;
+}
+
+/* power down chip */
+static int vbc_codec_soc_remove(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_vbc_codec = {
+ .probe = vbc_codec_soc_probe,
+ .remove = vbc_codec_soc_remove,
+ .read = vbc_codec_read,
+ .write = vbc_codec_write,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .dapm_widgets = vbc_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(vbc_codec_dapm_widgets),
+ .dapm_routes = vbc_codec_intercon,
+ .num_dapm_routes = ARRAY_SIZE(vbc_codec_intercon),
+ .controls = vbc_codec_snd_controls,
+ .num_controls = ARRAY_SIZE(vbc_codec_snd_controls),
+};
+
+static int sprd_vbc_codec_probe(struct platform_device *pdev)
+{
+ struct vbc_codec_priv *vbc_codec;
+ int ret;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ vbc_codec = devm_kzalloc(&pdev->dev, sizeof(struct vbc_codec_priv),
+ GFP_KERNEL);
+ if (vbc_codec == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, vbc_codec);
+
+ ret = snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_vbc_codec, NULL, 0);
+ if (ret != 0) {
+ pr_err("ERR:Failed to register VBC-CODEC: %d\n", ret);
+ }
+
+ /* AP refer to array vbc_switch_reg_val */
+ vbc_codec->vbc_control = 2;
+ vbc_codec->vbc_eq_setting.dev = &pdev->dev;
+ vbc_codec->st_dg.dg_val[0] = 0x18;
+ vbc_codec->st_dg.dg_val[1] = 0x18;
+ vbc_codec->st_dg.hpf_val[0] = 0x3;
+ vbc_codec->st_dg.hpf_val[1] = 0x3;
+ vbc_codec->dg[0].dg_val[0] = 0x18;
+ vbc_codec->dg[0].dg_val[1] = 0x18;
+ vbc_codec->dg[1].dg_val[0] = 0x18;
+ vbc_codec->dg[1].dg_val[1] = 0x18;
+ vbc_codec->dg[0].dg_set[0] = vbc_da0_dg_set;
+ vbc_codec->dg[0].dg_set[1] = vbc_da1_dg_set;
+ vbc_codec->dg[1].dg_set[0] = vbc_ad0_dg_set;
+ vbc_codec->dg[1].dg_set[1] = vbc_ad1_dg_set;
+ mutex_init(&vbc_codec->load_mutex);
+
+ return ret;
+}
+
+static int sprd_vbc_codec_remove(struct platform_device *pdev)
+{
+ struct vbc_codec_priv *vbc_codec = platform_get_drvdata(pdev);
+ vbc_codec->vbc_eq_setting.dev = 0;
+
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_DESCRIPTION("SPRD ASoC VBC Codec Driver");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_AUTHOR("Ken.Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("codec:VBC Codec");
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h
new file mode 100644
index 00000000..277445ee
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h
@@ -0,0 +1,29 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h
+ *
+ * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_CODEC_H
+#define __VBC_CODEC_H
+
+#include "vbc-comm.h"
+
+#define VBC_EQ_FIRMWARE_MAGIC_LEN (4)
+#define VBC_EQ_FIRMWARE_MAGIC_ID ("VBEQ")
+#define VBC_EQ_PROFILE_VERSION (0x00000001)
+#define VBC_EQ_PROFILE_CNT_MAX (100)
+#define VBC_EQ_PROFILE_NAME_MAX (32)
+#define VBC_EFFECT_PARAS_LEN (61)
+
+#endif /* __VBC_CODEC_H */
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c
new file mode 100644
index 00000000..6123ded2
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c
@@ -0,0 +1,229 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "sprd-asoc-common.h"
+#include "vbc-comm.h"
+
+static DEFINE_SPINLOCK(vbc_lock);
+
+/* vbc local power suppliy and chan on */
+static struct vbc_refcount {
+ atomic_t vbc_power_on; /*vbc reg power enable */
+ atomic_t vbc_on; /*vbc enable */
+ atomic_t chan_on[VBC_IDX_MAX][2];
+} vbc_refcnt;
+
+static const char *sprd_vbc_name[VBC_IDX_MAX] = {
+ "DAC",
+ "ADC",
+};
+
+static inline const char *vbc_get_name(int vbc_idx)
+{
+ return sprd_vbc_name[vbc_idx];
+}
+
+static inline int vbc_reg_read(unsigned int reg)
+{
+ return __raw_readl((void *__iomem)reg);
+}
+
+static inline void vbc_reg_raw_write(unsigned int reg, int val)
+{
+ __raw_writel(val, (void *__iomem)reg);
+}
+
+static int vbc_reg_write(unsigned int reg, int val)
+{
+ spin_lock(&vbc_lock);
+ vbc_reg_raw_write(reg, val);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val,
+ vbc_reg_read(reg));
+ return 0;
+}
+
+/*
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static int vbc_reg_update(unsigned int reg, int val, int mask)
+{
+ int new, old;
+ spin_lock(&vbc_lock);
+ old = vbc_reg_read(reg);
+ new = (old & ~mask) | (val & mask);
+ vbc_reg_raw_write(reg, new);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new,
+ vbc_reg_read(reg));
+ return old != new;
+}
+
+static struct clk *s_vbc_clk = 0;
+static void vbc_clk_set(struct clk *clk)
+{
+ s_vbc_clk = clk;
+}
+
+static struct clk *vbc_clk_get(void)
+{
+ return s_vbc_clk;
+}
+
+static inline void vbc_reg_enable(void)
+{
+ if (s_vbc_clk) {
+ clk_enable(s_vbc_clk);
+ } else {
+ arch_audio_vbc_reg_enable();
+ }
+}
+
+static inline void vbc_reg_disable(void)
+{
+ if (s_vbc_clk) {
+ clk_disable(s_vbc_clk);
+ } else {
+ arch_audio_vbc_reg_disable();
+ }
+}
+
+static int vbc_power(int enable)
+{
+ int i;
+ atomic_t *vbc_power_on = &vbc_refcnt.vbc_power_on;
+ if (enable) {
+ atomic_inc(vbc_power_on);
+ if (atomic_read(vbc_power_on) == 1) {
+ arch_audio_vbc_enable();
+ vbc_reg_enable();
+ for (i = 0; i < SPRD_VBC_PLAYBACK_COUNT; i++) {
+ vbc_da_buffer_clear_all(i);
+ }
+ sp_asoc_pr_dbg("VBC Power On\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_power_on)) {
+ arch_audio_vbc_reset();
+ arch_audio_vbc_disable();
+ vbc_reg_disable();
+ sp_asoc_pr_dbg("VBC Power Off\n");
+ }
+ if (atomic_read(vbc_power_on) < 0) {
+ atomic_set(vbc_power_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("VBC Power REF: %d", atomic_read(vbc_power_on));
+ return 0;
+}
+
+static inline int vbc_da_enable_raw(int enable, int chan)
+{
+ int ret;
+ if (enable) {
+ ret = arch_audio_vbc_da_enable(chan);
+ } else {
+ ret = arch_audio_vbc_da_disable(chan);
+ }
+ return ret;
+}
+
+static inline int vbc_ad_enable_raw(int enable, int chan)
+{
+ int ret;
+ if (enable) {
+ ret = arch_audio_vbc_ad_enable(chan);
+ } else {
+ ret = arch_audio_vbc_ad_disable(chan);
+ }
+ return ret;
+}
+
+static inline int vbc_enable_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBENABLE),
+ (1 << VBENABLE));
+ return 0;
+}
+
+typedef int (*vbc_chan_enable_raw) (int enable, int chan);
+static vbc_chan_enable_raw vbc_chan_enable_fun[VBC_IDX_MAX] = {
+ vbc_da_enable_raw,
+ vbc_ad_enable_raw,
+};
+
+static int vbc_chan_enable(int enable, int vbc_idx, int chan)
+{
+ atomic_t *chan_on = &vbc_refcnt.chan_on[vbc_idx][chan];
+ if (enable) {
+ atomic_inc(chan_on);
+ if (atomic_read(chan_on) == 1) {
+ vbc_chan_enable_fun[vbc_idx] (1, chan);
+ sp_asoc_pr_dbg("VBC %s%d On\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ } else {
+ if (atomic_dec_and_test(chan_on)) {
+ vbc_chan_enable_fun[vbc_idx] (0, chan);
+ sp_asoc_pr_dbg("VBC %s%d Off\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ if (atomic_read(chan_on) < 0) {
+ atomic_set(chan_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("%s%d REF: %d", vbc_get_name(vbc_idx), chan,
+ atomic_read(chan_on));
+ return 0;
+}
+
+static int vbc_enable(int enable)
+{
+ atomic_t *vbc_on = &vbc_refcnt.vbc_on;
+ if (enable) {
+ atomic_inc(vbc_on);
+ if (atomic_read(vbc_on) == 1) {
+ vbc_enable_set(1);
+ sp_asoc_pr_dbg("VBC Enable\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_on)) {
+ vbc_enable_set(0);
+ sp_asoc_pr_dbg("VBC Disable");
+ }
+ if (atomic_read(vbc_on) < 0) {
+ atomic_set(vbc_on, 0);
+ }
+ }
+
+ sp_asoc_pr_dbg("VBC EN REF: %d", atomic_read(vbc_on));
+ return 0;
+}
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h
new file mode 100644
index 00000000..47299ea6
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h
@@ -0,0 +1,219 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_COMM_H
+#define __VBC_COMM_H
+
+#include <mach/sprd-audio.h>
+
+#define VBC_VERSION "vbc.r1p0"
+
+/* VBADBUFFDTA */
+enum {
+ VBISADCK_INV = 9,
+ VBISDACK_INV,
+ VBLSB_EB,
+ VBIIS_DLOOP = 13,
+ VBPCM_MODE,
+ VBIIS_LRCK,
+};
+/* VBDABUFFDTA */
+enum {
+ RAMSW_NUMB = 9,
+ RAMSW_EN,
+ VBAD0DMA_EN,
+ VBAD1DMA_EN,
+ VBDA0DMA_EN,
+ VBDA1DMA_EN,
+ VBENABLE,
+};
+
+/* VBDAPATH FM MIXER*/
+enum {
+ DAPATH_NO_MIX = 0,
+ DAPATH_ADD_FM,
+ DAPATH_SUB_FM,
+};
+
+/* AD DGMUX NUM*/
+enum {
+ ADC0_DGMUX = 0,
+ ADC1_DGMUX,
+ ADC_DGMUX_MAX
+};
+
+/* -------------------------- */
+
+#define PHYS_VBDA0 (VBC_PHY_BASE + 0x0000) /* 0x0000 Voice band DAC0 data buffer */
+#define PHYS_VBDA1 (VBC_PHY_BASE + 0x0004) /* 0x0004 Voice band DAC1 data buffer */
+#define PHYS_VBAD0 (VBC_PHY_BASE + 0x0008) /* 0x0008 Voice band ADC0 data buffer */
+#define PHYS_VBAD1 (VBC_PHY_BASE + 0x000C) /* 0x000C Voice band ADC1 data buffer */
+
+#define ARM_VB_BASE VBC_BASE
+#define VBDA0 (ARM_VB_BASE + 0x0000) /* Voice band DAC0 data buffer */
+#define VBDA1 (ARM_VB_BASE + 0x0004) /* Voice band DAC1 data buffer */
+#define VBAD0 (ARM_VB_BASE + 0x0008) /* Voice band ADC0 data buffer */
+#define VBAD1 (ARM_VB_BASE + 0x000C) /* Voice band ADC1 data buffer */
+#define VBBUFFSIZE (ARM_VB_BASE + 0x0010) /* Voice band buffer size */
+#define VBADBUFFDTA (ARM_VB_BASE + 0x0014) /* Voice band AD buffer control */
+#define VBDABUFFDTA (ARM_VB_BASE + 0x0018) /* Voice band DA buffer control */
+#define VBADCNT (ARM_VB_BASE + 0x001C) /* Voice band AD buffer counter */
+#define VBDACNT (ARM_VB_BASE + 0x0020) /* Voice band DA buffer counter */
+#define VBINTTYPE (ARM_VB_BASE + 0x0034)
+#define VBDATASWT (ARM_VB_BASE + 0x0038)
+#define VBIISSEL (ARM_VB_BASE + 0x003C)
+
+#define DAPATCHCTL (ARM_VB_BASE + 0x0040)
+#define DADGCTL (ARM_VB_BASE + 0x0044)
+#define DAHPCTL (ARM_VB_BASE + 0x0048)
+#define DAALCCTL0 (ARM_VB_BASE + 0x004C)
+#define DAALCCTL1 (ARM_VB_BASE + 0x0050)
+#define DAALCCTL2 (ARM_VB_BASE + 0x0054)
+#define DAALCCTL3 (ARM_VB_BASE + 0x0058)
+#define DAALCCTL4 (ARM_VB_BASE + 0x005C)
+#define DAALCCTL5 (ARM_VB_BASE + 0x0060)
+#define DAALCCTL6 (ARM_VB_BASE + 0x0064)
+#define DAALCCTL7 (ARM_VB_BASE + 0x0068)
+#define DAALCCTL8 (ARM_VB_BASE + 0x006C)
+#define DAALCCTL9 (ARM_VB_BASE + 0x0070)
+#define DAALCCTL10 (ARM_VB_BASE + 0x0074)
+#define STCTL0 (ARM_VB_BASE + 0x0078)
+#define STCTL1 (ARM_VB_BASE + 0x007C)
+#define ADPATCHCTL (ARM_VB_BASE + 0x0080)
+#define ADDGCTL (ARM_VB_BASE + 0x0084)
+#define HPCOEF0 (ARM_VB_BASE + 0x0100)
+#define HPCOEF1 (ARM_VB_BASE + 0x0104)
+#define HPCOEF2 (ARM_VB_BASE + 0x0108)
+#define HPCOEF3 (ARM_VB_BASE + 0x010C)
+#define HPCOEF4 (ARM_VB_BASE + 0x0110)
+#define HPCOEF5 (ARM_VB_BASE + 0x0114)
+#define HPCOEF6 (ARM_VB_BASE + 0x0118)
+#define HPCOEF7 (ARM_VB_BASE + 0x011C)
+#define HPCOEF8 (ARM_VB_BASE + 0x0120)
+#define HPCOEF9 (ARM_VB_BASE + 0x0124)
+#define HPCOEF10 (ARM_VB_BASE + 0x0128)
+#define HPCOEF11 (ARM_VB_BASE + 0x012C)
+#define HPCOEF12 (ARM_VB_BASE + 0x0130)
+#define HPCOEF13 (ARM_VB_BASE + 0x0134)
+#define HPCOEF14 (ARM_VB_BASE + 0x0138)
+#define HPCOEF15 (ARM_VB_BASE + 0x013C)
+#define HPCOEF16 (ARM_VB_BASE + 0x0140)
+#define HPCOEF17 (ARM_VB_BASE + 0x0144)
+#define HPCOEF18 (ARM_VB_BASE + 0x0148)
+#define HPCOEF19 (ARM_VB_BASE + 0x014C)
+#define HPCOEF20 (ARM_VB_BASE + 0x0150)
+#define HPCOEF21 (ARM_VB_BASE + 0x0154)
+#define HPCOEF22 (ARM_VB_BASE + 0x0158)
+#define HPCOEF23 (ARM_VB_BASE + 0x015C)
+#define HPCOEF24 (ARM_VB_BASE + 0x0160)
+#define HPCOEF25 (ARM_VB_BASE + 0x0164)
+#define HPCOEF26 (ARM_VB_BASE + 0x0168)
+#define HPCOEF27 (ARM_VB_BASE + 0x016C)
+#define HPCOEF28 (ARM_VB_BASE + 0x0170)
+#define HPCOEF29 (ARM_VB_BASE + 0x0174)
+#define HPCOEF30 (ARM_VB_BASE + 0x0178)
+#define HPCOEF31 (ARM_VB_BASE + 0x017C)
+#define HPCOEF32 (ARM_VB_BASE + 0x0180)
+#define HPCOEF33 (ARM_VB_BASE + 0x0184)
+#define HPCOEF34 (ARM_VB_BASE + 0x0188)
+#define HPCOEF35 (ARM_VB_BASE + 0x018C)
+#define HPCOEF36 (ARM_VB_BASE + 0x0190)
+#define HPCOEF37 (ARM_VB_BASE + 0x0194)
+#define HPCOEF38 (ARM_VB_BASE + 0x0198)
+#define HPCOEF39 (ARM_VB_BASE + 0x019C)
+#define HPCOEF40 (ARM_VB_BASE + 0x01A0)
+#define HPCOEF41 (ARM_VB_BASE + 0x01A4)
+#define HPCOEF42 (ARM_VB_BASE + 0x01A8)
+
+#define ARM_VB_END (ARM_VB_BASE + 0x01AC)
+#define IS_SPRD_VBC_RANG(reg) (((reg) >= ARM_VB_BASE) && ((reg) < ARM_VB_END))
+#define SPRD_VBC_BASE_HI (ARM_VB_BASE & 0xFFFF0000)
+
+#define VBADBUFFERSIZE_SHIFT (0)
+#define VBADBUFFERSIZE_MASK (0xFF<<VBADBUFFERSIZE_SHIFT)
+#define VBDABUFFERSIZE_SHIFT (8)
+#define VBDABUFFERSIZE_MASK (0xFF<<VBDABUFFERSIZE_SHIFT)
+
+#define VBDACHEN_SHIFT (0)
+#define VBADCHEN_SHIFT (2)
+
+ /*VBIISSEL*/
+#define VBIISSEL_AD_PORT_SHIFT (0)
+#define VBIISSEL_AD_PORT_MASK (0x7)
+#define VBIISSEL_DA_PORT_SHIFT (3)
+#define VBIISSEL_DA_PORT_MASK (0x7<<VBIISSEL_DA_PORT_SHIFT)
+#define VBIISSEL_DA_LRCK (9)
+#define VBIISSEL_AD_LRCK (8)
+ /*DAPATHCTL*/
+#define VBDAPATH_DA0_ADDFM_SHIFT (0)
+#define VBDAPATH_DA0_ADDFM_MASK (0x3<<VBDAPATH_DA0_ADDFM_SHIFT)
+#define VBDAPATH_DA1_ADDFM_SHIFT (2)
+#define VBDAPATH_DA1_ADDFM_MASK (0x3<<VBDAPATH_DA1_ADDFM_SHIFT)
+ /*ADPATHCTL*/
+#define VBADPATH_AD0_INMUX_SHIFT (0)
+#define VBADPATH_AD0_INMUX_MASK (0x3<<VBADPATH_AD0_INMUX_SHIFT)
+#define VBADPATH_AD1_INMUX_SHIFT (2)
+#define VBADPATH_AD1_INMUX_MASK (0x3<<VBADPATH_AD1_INMUX_SHIFT)
+#define VBADPATH_AD0_DGMUX_SHIFT (4)
+#define VBADPATH_AD0_DGMUX_MASK (0x1<<VBADPATH_AD0_DGMUX_SHIFT)
+#define VBADPATH_AD1_DGMUX_SHIFT (5)
+#define VBADPATH_AD1_DGMUX_MASK (0x1<<VBADPATH_AD1_DGMUX_SHIFT)
+/*STCTL0 */
+#define VBST_EN_0 (12)
+#define VBST_HPF_0 (11)
+/*STCTL1 */
+#define VBST_HPF_1 (11)
+#define VBST_EN_1 (12)
+#define VBST0_SEL_CHN (13)
+#define VBST1_SEL_CHN (14)
+ enum {
+ VBC_LEFT = 0,
+ VBC_RIGHT = 1,
+};
+
+enum {
+ VBC_PLAYBACK = 0,
+ SPRD_VBC_PLAYBACK_COUNT = 1,
+ VBC_CAPTRUE = 1,
+ VBC_IDX_MAX
+};
+
+static const char *vbc_get_name(int vbc_idx);
+
+static int vbc_reg_read(unsigned int reg);
+static void vbc_reg_raw_write(unsigned int reg, int val);
+static int vbc_reg_write(unsigned int reg, int val);
+static int vbc_reg_update(unsigned int reg, int val, int mask);
+static void vbc_da_buffer_clear_all(int vbc_idx);
+static int vbc_enable(int enable);
+static void vbc_clk_set(struct clk *clk);
+static struct clk *vbc_clk_get(void);
+static int vbc_power(int enable);
+static int vbc_chan_enable(int enable, int vbc_idx, int chan);
+static int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai);
+static int fm_set_vbc_buffer_size(void);
+
+static inline void vbc_safe_mem_release(void **free)
+{
+ if (*free) {
+ kfree(*free);
+ *free = NULL;
+ }
+}
+
+#define vbc_safe_kfree(p) vbc_safe_mem_release((void**)p)
+
+#endif /* __VBC_COMM_H */
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc.c b/sound/soc/sprd/dai/vbc/r1p0/vbc.c
new file mode 100644
index 00000000..5c857b90
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc.c
@@ -0,0 +1,637 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" VBC ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#include "sprd-pcm.h"
+#include "vbc.h"
+#include "dfm.h"
+
+typedef int (*vbc_dma_set) (int enable);
+
+struct sprd_vbc_buffer_info {
+ int reg;
+ int shift;
+ int mask;
+ int max;
+};
+
+struct sprd_vbc_priv {
+ vbc_dma_set dma_set[2];
+ int (*arch_enable) (int chan);
+ int (*arch_disable) (int chan);
+ int used_chan_count;
+ struct sprd_vbc_buffer_info buf_info;
+ int rate;
+};
+
+struct sprd_dfm_priv dfm = { 0, 0 };
+
+EXPORT_SYMBOL_GPL(dfm);
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_out = {
+ .name = "VBC PCM Stereo out",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 2,
+ .des_step = 0,
+ },
+ .dev_paddr = {PHYS_VBDA0, PHYS_VBDA1},
+};
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_in = {
+ .name = "VBC PCM Stereo in",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 0,
+ .des_step = 2,
+ },
+ .dev_paddr = {PHYS_VBAD0, PHYS_VBAD1},
+};
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX];
+
+static int vbc_iis_high_for_da1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_DA_LRCK,
+ 1 << VBIISSEL_DA_LRCK);
+ return 0;
+}
+
+#if 0
+static int vbc_iis_high_for_ad1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_AD_LRCK,
+ 1 << VBIISSEL_AD_LRCK);
+ return 0;
+}
+#endif
+
+static int vbc_set_buffer_size(int vbc_idx, int buffer_size)
+{
+ int val = vbc_reg_read(vbc[vbc_idx].buf_info.reg);
+ WARN_ON(buffer_size > vbc[vbc_idx].buf_info.max);
+ if ((buffer_size > 0)
+ && (buffer_size <= vbc[vbc_idx].buf_info.max)) {
+ val &= ~(vbc[vbc_idx].buf_info.mask);
+ val |= (((buffer_size - 1) << vbc[vbc_idx].buf_info.shift)
+ & vbc[vbc_idx].buf_info.mask);
+ vbc_reg_update(vbc[vbc_idx].buf_info.reg, val,
+ vbc[vbc_idx].buf_info.mask);
+ } else {
+ pr_err("ERR:buffer_size error! %d\n", buffer_size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fm_set_vbc_buffer_size(void)
+{
+ int i;
+ for (i = SPRD_VBC_PLAYBACK_COUNT; i < VBC_IDX_MAX; i++) {
+ vbc_set_buffer_size(i, VBC_FIFO_FRAME_NUM);
+ }
+ return 0;
+}
+
+static inline int vbc_sw_write_buffer(int enable)
+{
+ /* Software access ping-pong buffer enable when VBENABE bit low */
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << RAMSW_EN),
+ (1 << RAMSW_EN));
+ return 0;
+}
+
+static inline int vbc_ad0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD0DMA_EN),
+ (1 << VBAD0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_ad1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD1DMA_EN),
+ (1 << VBAD1DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA0DMA_EN),
+ (1 << VBDA0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA1DMA_EN),
+ (1 << VBDA1DMA_EN));
+ return 0;
+}
+
+static void vbc_da_buffer_clear(int id)
+{
+ int i;
+ vbc_reg_update(VBDABUFFDTA, ((id ? 1 : 0) << RAMSW_NUMB),
+ (1 << RAMSW_NUMB));
+ for (i = 0; i < VBC_FIFO_FRAME_NUM; i++) {
+ vbc_reg_raw_write(VBDA0, 0);
+ vbc_reg_raw_write(VBDA1, 0);
+ }
+}
+
+static void vbc_da_buffer_clear_all(int vbc_idx)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ }
+
+ vbc_sw_write_buffer(true);
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_da_buffer_clear(1); /* clear data buffer 1 */
+ vbc_da_buffer_clear(0); /* clear data buffer 0 */
+ vbc_sw_write_buffer(false);
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ }
+}
+
+static int vbc_da_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_PLAYBACK, chan);
+}
+
+static int vbc_da_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_PLAYBACK, chan);
+}
+
+static int vbc_ad_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_CAPTRUE, chan);
+}
+
+static int vbc_ad_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_CAPTRUE, chan);
+}
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX] = {
+ /* All Playback */
+ { /*PlayBack */
+ .dma_set = {vbc_da0_dma_set, vbc_da1_dma_set},
+ .arch_enable = vbc_da_arch_enable,
+ .arch_disable = vbc_da_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBDABUFFERSIZE_SHIFT, VBDABUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+
+ /* All Capture */
+ { /*Capture for ad01 */
+ .dma_set = {vbc_ad0_dma_set, vbc_ad1_dma_set},
+ .arch_enable = vbc_ad_arch_enable,
+ .arch_disable = vbc_ad_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBADBUFFERSIZE_SHIFT, VBADBUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+};
+
+/* NOTE:
+ this index need use for the [struct sprd_vbc_priv] vbc[2] index
+ default MUST return 0.
+ */
+static inline int vbc_str_2_index(int stream, int id)
+{
+ if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+ return id + SPRD_VBC_PLAYBACK_COUNT;
+ }
+ return id;
+}
+
+static int vbc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+ sp_asoc_pr_dbg("dfm.hw_rate:%d, dfm.sample_rate:%d", dfm.hw_rate,
+ dfm.sample_rate);
+
+ if (dfm.sample_rate != 0) {
+ if ((vbc_idx == VBC_PLAYBACK) || (vbc_idx == VBC_CAPTRUE)) {
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ dfm.sample_rate,
+ dfm.sample_rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+ }
+ }
+
+ vbc_power(1);
+
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_codec_startup(vbc_idx, dai);
+
+ WARN_ON(!vbc[vbc_idx].arch_enable);
+ WARN_ON(!vbc[vbc_idx].arch_disable);
+ WARN_ON(!vbc[vbc_idx].dma_set[0]);
+ WARN_ON(!vbc[vbc_idx].dma_set[1]);
+
+ return 0;
+}
+
+static void vbc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ /* vbc da close MUST clear da buffer */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ vbc_da_buffer_clear_all(vbc_idx);
+ }
+
+ vbc_power(0);
+}
+
+static int vbc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ struct sprd_pcm_dma_params *dma_data[VBC_IDX_MAX] = {
+ &vbc_pcm_stereo_out,
+ &vbc_pcm_stereo_in,
+ };
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data[vbc_idx]);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ vbc[vbc_idx].used_chan_count = params_channels(params);
+ if (vbc[vbc_idx].used_chan_count > 2) {
+ pr_err("ERR:VBC Can NOT Supports Grate 2 Channels\n");
+ }
+
+ vbc_iis_high_for_da1(1);
+#ifdef CONFIG_SND_SOC_SPRD_VBC_LR_INVERT
+ if (vbc[vbc_idx].used_chan_count == 2) {
+ vbc_iis_high_for_da1(0);
+ }
+#endif
+ vbc[vbc_idx].rate = params_rate(params);
+ return 0;
+}
+
+static int vbc_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ vbc[vbc_idx].rate = 0;
+
+ return 0;
+}
+
+static int vbc_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret = 0;
+ int i;
+
+#if 0
+ sp_asoc_pr_dbg("%s\n", __func__);
+#endif
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ vbc[vbc_idx].dma_set[i] (1);
+ }
+ vbc_enable(1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ vbc[vbc_idx].dma_set[i] (0);
+ }
+ vbc_enable(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops vbc_dai_ops = {
+ .startup = vbc_startup,
+ .shutdown = vbc_shutdown,
+ .hw_params = vbc_hw_params,
+ .trigger = vbc_trigger,
+ .hw_free = vbc_hw_free,
+};
+
+static int dfm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ static const unsigned int dfm_all_rates[] = { 32000, 44100, 48000 };
+ static const struct snd_pcm_hw_constraint_list dfm_rates_constraint = {
+ .count = ARRAY_SIZE(dfm_all_rates),
+ .list = dfm_all_rates,
+ };
+ int ret;
+ int vbc_idx;
+ struct snd_soc_card *card = dai->card;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n, vbc[vbc_idx].rate:da:%d, ad:%d, ad23:%d",
+ __func__, vbc[0].rate, vbc[1].rate, vbc[2].rate);
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &dfm_rates_constraint);
+ if (ret < 0)
+ return ret;
+
+ for (vbc_idx = VBC_PLAYBACK; vbc_idx < VBC_CAPTRUE1; vbc_idx++) {
+ if (vbc[vbc_idx].rate != 0) {
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ vbc[vbc_idx].rate,
+ vbc[vbc_idx].rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+ }
+ }
+
+ kfree(snd_soc_dai_get_dma_data(dai, substream));
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+
+ snd_soc_dapm_enable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 1;
+ }
+ snd_soc_dapm_ignore_suspend(&dai->codec->dapm, "DFM");
+
+ return 0;
+}
+
+static void dfm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = dai->card;
+ int i;
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ snd_soc_dapm_disable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 0;
+ }
+}
+
+static int dfm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int dfm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+ dfm.sample_rate = 0;
+ dfm.hw_rate = 0;
+ return 0;
+}
+
+static struct snd_soc_dai_ops dfm_dai_ops = {
+ .startup = dfm_startup,
+ .shutdown = dfm_shutdown,
+ .hw_params = dfm_hw_params,
+ .hw_free = dfm_hw_free,
+};
+
+static struct snd_soc_dai_driver vbc_dai[] = {
+ {
+ .name = "vbc-r1p0",
+ .id = 0,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2, /* AD01 */
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &vbc_dai_ops,
+ },
+ {
+ .name = "vbc-dfm",
+ .id = DFM_MAGIC_ID,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &dfm_dai_ops,
+ }
+};
+
+static const struct snd_soc_component_driver sprd_vbc_component = {
+ .name = "vbc",
+};
+
+static int sprd_vbc_codec_probe(struct platform_device *pdev);
+static int vbc_drv_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ struct clk *vbc_clk;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ arch_audio_vbc_switch(AUDIO_TO_ARM_CTRL);
+ ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE);
+ if (ret != AUDIO_TO_ARM_CTRL) {
+ pr_err("Failed to Switch VBC to AP\n");
+ return -1;
+ }
+
+ for (i = 0; i < 2; i++) {
+ vbc_pcm_stereo_out.channels[i] = arch_audio_vbc_da_dma_info(i);
+ vbc_pcm_stereo_in.channels[i] = arch_audio_vbc_ad_dma_info(i);
+ }
+
+ /* 1. probe CODEC */
+ ret = sprd_vbc_codec_probe(pdev);
+
+ if (ret < 0) {
+ goto probe_err;
+ }
+
+ /* 2. probe DAIS */
+ ret =
+ snd_soc_register_component(&pdev->dev, &sprd_vbc_component, vbc_dai,
+ ARRAY_SIZE(vbc_dai));
+
+ if (ret < 0) {
+ pr_err("ERR:Register VBC to DAIS Failed!\n");
+ goto probe_err;
+ }
+
+ vbc_clk = clk_get(&pdev->dev, "clk_vbc");
+ if (IS_ERR(vbc_clk)) {
+ pr_err("Cannot request clk_vbc\n");
+ } else {
+ vbc_clk_set(vbc_clk);
+ }
+
+probe_err:
+ sp_asoc_pr_dbg("return %i\n", ret);
+
+ return ret;
+}
+
+static int sprd_vbc_codec_remove(struct platform_device *pdev);
+static int vbc_drv_remove(struct platform_device *pdev)
+{
+ struct clk *vbc_clk = vbc_clk_get();
+
+ if (vbc_clk) {
+ clk_put(vbc_clk);
+ vbc_clk_set(0);
+ }
+
+ snd_soc_unregister_component(&pdev->dev);
+
+ sprd_vbc_codec_remove(pdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id vbc_of_match[] = {
+ {.compatible = "sprd,vbc-r1p0",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, vbc_of_match);
+#endif
+
+static struct platform_driver vbc_driver = {
+ .driver = {
+ .name = "vbc-r1p0",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vbc_of_match),
+ },
+
+ .probe = vbc_drv_probe,
+ .remove = vbc_drv_remove,
+};
+
+module_platform_driver(vbc_driver);
+
+/* include the other module of VBC */
+#include "vbc-comm.c"
+#include "vbc-codec.c"
+
+MODULE_DESCRIPTION("SPRD ASoC VBC CUP-DAI driver");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cpu-dai:vbc-r1p0");
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc.h b/sound/soc/sprd/dai/vbc/r1p0/vbc.h
new file mode 100644
index 00000000..56dc1989
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc.h
@@ -0,0 +1,22 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc.h
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_H
+#define __VBC_H
+
+#include "vbc-comm.h"
+
+#endif /* __VBC_H */
diff --git a/sound/soc/sprd/dai/vbc/r2p0/Kconfig b/sound/soc/sprd/dai/vbc/r2p0/Kconfig
new file mode 100644
index 00000000..68306682
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/Kconfig
@@ -0,0 +1,20 @@
+config SND_SOC_SPRD_VBC_R2P0
+ tristate # "VBC_R2P0"
+ help
+
+config SND_SOC_SPRD_VBC_SRC_OPEN
+ bool "VBC ADC/ADC23 SRC open"
+ depends on SND_SOC_SPRD_VBC_R2P0
+ help
+ say Y then the VBC will open adc SRC for FM function.
+
+config SND_SOC_VBC_SRC_SAMPLE_RATE
+ int "VBC SRC Source Rate (32000 or 48000) unit(HZ)"
+ range 32000 48000
+ default 32000
+ depends on SND_SOC_SPRD_VBC_R2P0
+ help
+ when capture sample rate is 44100HZ,
+ we cann't set 44100 HZ for CODEC adc sample rate,
+ so need to set CODEC sample to 32000/48000,
+ then open VBC SRC changing to 44100HZ..
diff --git a/sound/soc/sprd/dai/vbc/r2p0/Makefile b/sound/soc/sprd/dai/vbc/r2p0/Makefile
new file mode 100644
index 00000000..a269a3c5
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/Makefile
@@ -0,0 +1,6 @@
+# SPRD VBC Support
+SPRD_SOUND_TREE := sound/soc/sprd/
+KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai -I$(SPRD_SOUND_TREE)/dai/vbc/
+
+snd-soc-sprd-vbc-r2p0-objs := vbc.o
+obj-$(CONFIG_SND_SOC_SPRD_VBC_R2P0) += snd-soc-sprd-vbc-r2p0.o
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c
new file mode 100644
index 00000000..a029523a
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c
@@ -0,0 +1,2596 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c
+ *
+ * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#include "vbc-codec.h"
+
+#define FUN_REG(f) ((unsigned short)(-((f) + 1)))
+#define SOC_REG(r) ((unsigned short)(r))
+
+struct vbc_fw_header {
+ char magic[VBC_EQ_FIRMWARE_MAGIC_LEN];
+ u32 profile_version;
+ u32 num_profile; /*total num */
+ u32 num_da[VBC_IDX_MAX];
+};
+
+struct vbc_da_eq_profile {
+ char magic[VBC_EQ_FIRMWARE_MAGIC_LEN];
+ char name[VBC_EQ_PROFILE_NAME_MAX];
+ /* TODO */
+ u32 effect_paras[VBC_DA_EFFECT_PARAS_LEN];
+};
+
+struct vbc_ad_eq_profile {
+ char magic[VBC_EQ_FIRMWARE_MAGIC_LEN];
+ char name[VBC_EQ_PROFILE_NAME_MAX];
+ /* TODO */
+ u32 effect_paras[VBC_AD_EFFECT_PARAS_LEN];
+};
+
+static const u32 vbc_da_eq_profile_default[VBC_DA_EFFECT_PARAS_LEN] = {
+/* TODO the default register value */
+ 0x00000000, /* DAPATCHCTL */
+ 0x00000d7F, /* DAHPCTL */
+ /*ALC default para */
+ 0x000001e0, /* DAALCCTL0 */
+ 0x00002000, /* DAALCCTL1 */
+ 0x000004fe, /* DAALCCTL2 */
+ 0x0000001f, /* DAALCCTL3 */
+ 0x0000ffe2, /* DAALCCTL4 */
+ 0x00007fff, /* DAALCCTL5 */
+ 0x0000028c, /* DAALCCTL6 */
+ 0x00000010, /* DAALCCTL7 */
+ 0x000004dd, /* DAALCCTL8 */
+ 0x00000000, /* DAALCCTL9 */
+ 0x00000062, /* DAALCCTL10 */
+
+ 0x00000183, /* STCTL0 */
+ 0x00000183, /* STCTL1 */
+ 0x00000000, /* DACSRCCTL */
+ 0x00000000, /* MIXERCTL */
+ 0x00000000, /* VBNGCVTHD */
+ 0x00000000, /* VBNGCTTHD */
+ 0x00000000, /* VBNGCTL */
+
+ /*DA eq6 */
+ /*0x100 -- 0x134 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x138 -- 0x16c */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x170 -- 0x1a4 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x1a8 -- 0x1dc */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x1e0 -- 0x214 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x218 -- 0x24c */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x250 -- 0x254 */
+ 0x00001000,
+ 0x00000000,
+
+ /*DA eq4 */
+ /*0x258 -- 0x28c */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x290 -- 0x2c4 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x2c8 -- 0x2fc */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x300 -- 0x334 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x338 -- 0x33c */
+ 0x00001000,
+ 0x00000000,
+};
+
+static const u32 vbc_ad_eq_profile_default[VBC_AD_EFFECT_PARAS_LEN] = {
+/* TODO the default register value */
+ 0x00000000, /* ADPATCHCTL */
+ 0x00000000, /* ADHPCTL */
+ /*AD01/23 eq6 */
+ /*0x400 -- 0x434 */
+ 0x00001000, /* ADC01_HPCOEF0_H or ADC23_HPCOEF0_H */
+ 0x00000000, /* ADC01_HPCOEF0_L or ADC23_HPCOEF0_L */
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x438 -- 0x46c */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x470 -- 0x4a4 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x4a8 -- 0x4dc */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x4e0 -- 0x514 */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x518 -- 0x54c */
+ 0x00001000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00004000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ /*0x550 -- 0x554 */
+ 0x00001000,
+ 0x00000000,
+};
+
+struct vbc_equ {
+ struct device *dev;
+ struct snd_soc_codec *codec;
+ int is_active[VBC_IDX_MAX];
+ int is_loading;
+ int valid;
+ int now_profile[VBC_IDX_MAX];
+ struct vbc_fw_header hdr;
+ void *data[VBC_IDX_MAX];
+ void (*vbc_eq_apply) (struct snd_soc_codec * codec, void *data,
+ int vbc_idx);
+};
+
+static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx);
+static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx);
+
+#define VBC_DG_VAL_MAX (0x7F)
+
+typedef int (*vbc_dg_set) (int enable, int dg);
+struct vbc_dg {
+ int dg_switch[2];
+ int dg_val[2];
+ vbc_dg_set dg_set[2];
+};
+
+struct st_hpf_dg {
+ int hpf_switch[2];
+ int dg_val[2];
+ int hpf_val[2];
+};
+
+/*vbc mux setting*/
+enum {
+ SPRD_VBC_MUX_START = 0,
+ SPRD_VBC_ST0_CHAN_MUX = SPRD_VBC_MUX_START,
+ SPRD_VBC_ST1_CHAN_MUX,
+ SPRD_VBC_ST0_MUX,
+ SPRD_VBC_ST1_MUX,
+ SPRD_VBC_AD0_INMUX,
+ SPRD_VBC_AD1_INMUX,
+ SPRD_VBC_AD2_INMUX,
+ SPRD_VBC_AD3_INMUX,
+ SPRD_VBC_AD_IISMUX,
+ SPRD_VBC_AD23_IISMUX,
+ SPRD_VBC_DFM_DA0_ADDFM_INMUX,
+ SPRD_VBC_DFM_DA1_ADDFM_INMUX,
+ SPRD_VBC_DFM_DA0_ADDST_INMUX,
+ SPRD_VBC_DFM_DA1_ADDST_INMUX,
+ SPRD_VBC_MUX_MAX
+};
+
+#define IS_SPRD_VBC_MUX_RANG(reg) ((reg) >= SPRD_VBC_MUX_START && (reg) < (SPRD_VBC_MUX_MAX))
+#define SPRD_VBC_MUX_IDX(reg) (reg - SPRD_VBC_MUX_START)
+#define IS_SPRD_VBC_DFM_ST_MUX_RANG(id) (((id) >= SPRD_VBC_DFM_DA0_ADDFM_INMUX) && ((id) <= SPRD_VBC_DFM_DA1_ADDST_INMUX))
+
+static const char *vbc_mux_debug_str[SPRD_VBC_MUX_MAX] = {
+ "st0 chan mux",
+ "st1 chan mux",
+ "st0 mux",
+ "st1 mux",
+ "ad0 inmux",
+ "ad1 inmux",
+ "ad2 inmux",
+ "ad3 inmux",
+ "ad iis mux",
+ "ad23 iis mux",
+ "da0 addfm mux",
+ "da1 addfm mux",
+ "da0 addst mux",
+ "da1 addfm mux"
+};
+
+typedef int (*sprd_vbc_mux_set) (int sel);
+struct sprd_vbc_mux_op {
+ int val;
+ sprd_vbc_mux_set set;
+};
+
+enum {
+ VBC_LOOP_SWITCH_START = SPRD_VBC_MUX_MAX,
+ VBC_AD01_LOOP_SWITCH = VBC_LOOP_SWITCH_START,
+ VBC_AD23_LOOP_SWITCH,
+ VBC_LOOP_SWITCH_MAX
+};
+
+#define IS_SPRD_VBC_SWITCH_RANG(reg) ((reg) >= VBC_LOOP_SWITCH_START && (reg) < (VBC_LOOP_SWITCH_MAX))
+#define SPRD_VBC_SWITCH_IDX(reg) (reg - VBC_LOOP_SWITCH_START)
+
+struct vbc_codec_priv {
+ struct snd_soc_codec *codec;
+ struct vbc_equ vbc_eq_setting;
+ struct mutex load_mutex;
+ int vbc_control;
+ int vbc_loop_switch[SPRD_VBC_SWITCH_IDX(VBC_LOOP_SWITCH_MAX)];
+ struct vbc_dg dg[VBC_IDX_MAX];
+ int vbc_da_iis_port;
+ int adc_dgmux_val[ADC_DGMUX_MAX];
+ struct st_hpf_dg st_dg;
+ struct sprd_vbc_mux_op sprd_vbc_mux[SPRD_VBC_MUX_MAX];
+ int alc_dp_t_mode;
+};
+
+static int vbc_da0_addfm_set(int val)
+{
+ vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA0_ADDFM_SHIFT, VBDAPATH_DA0_ADDFM_MASK);
+ return 0;
+}
+
+static int vbc_da1_addfm_set(int val)
+{
+ vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA1_ADDFM_SHIFT, VBDAPATH_DA1_ADDFM_MASK);
+ return 0;
+}
+
+static int vbc_da0_addst_set(int val)
+{
+ vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA0_ADDST_SHIFT, VBDAPATH_DA0_ADDST_MASK);
+ return 0;
+}
+
+static int vbc_da1_addst_set(int val)
+{
+ vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA1_ADDST_SHIFT, VBDAPATH_DA1_ADDST_MASK);
+ return 0;
+}
+
+static int vbc_st0_chnmux_set(int val)
+{
+ vbc_reg_update(STCTL1, val << VBST0_SEL_CHN, (1 << VBST0_SEL_CHN));
+ return 0;
+}
+
+static int vbc_st1_chnmux_set(int val)
+{
+ vbc_reg_update(STCTL1, val << VBST1_SEL_CHN, (1 << VBST1_SEL_CHN));
+ return 0;
+}
+
+static int vbc_st0_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_ST0_INMUX_SHIFT,
+ VBADPATH_ST0_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_st1_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_ST1_INMUX_SHIFT,
+ VBADPATH_ST1_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad0_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_INMUX_SHIFT,
+ VBADPATH_AD0_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad1_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_INMUX_SHIFT,
+ VBADPATH_AD1_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad2_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD2_INMUX_SHIFT,
+ VBADPATH_AD2_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad3_inmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD3_INMUX_SHIFT,
+ VBADPATH_AD3_INMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad0_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_DGMUX_SHIFT,
+ VBADPATH_AD0_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad1_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_DGMUX_SHIFT,
+ VBADPATH_AD1_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad2_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD2_DGMUX_SHIFT,
+ VBADPATH_AD2_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad3_dgmux_set(int val)
+{
+ vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD3_DGMUX_SHIFT,
+ VBADPATH_AD3_DGMUX_MASK);
+ return 0;
+}
+
+static int vbc_ad_iismux_set(int port)
+{
+ vbc_reg_update(VBIISSEL, port << VBIISSEL_AD01_PORT_SHIFT,
+ VBIISSEL_AD01_PORT_MASK);
+ return 0;
+}
+
+static int vbc_ad23_iismux_set(int port)
+{
+ vbc_reg_update(VBIISSEL, port << VBIISSEL_AD23_PORT_SHIFT,
+ VBIISSEL_AD23_PORT_MASK);
+ return 0;
+}
+
+static int vbc_da_iismux_set(int port)
+{
+ vbc_reg_update(VBIISSEL, port << VBIISSEL_DA_PORT_SHIFT,
+ VBIISSEL_DA_PORT_MASK);
+ return 0;
+}
+
+static int vbc_try_ad_iismux_set(int port);
+static int vbc_try_ad23_iismux_set(int port);
+
+static sprd_vbc_mux_set vbc_mux_cfg[SPRD_VBC_MUX_MAX] = {
+ vbc_st0_chnmux_set,
+ vbc_st1_chnmux_set,
+ vbc_st0_inmux_set,
+ vbc_st1_inmux_set,
+ vbc_ad0_inmux_set,
+ vbc_ad1_inmux_set,
+ vbc_ad2_inmux_set,
+ vbc_ad3_inmux_set,
+ vbc_try_ad_iismux_set,
+ vbc_try_ad23_iismux_set,
+ vbc_da0_addfm_set,
+ vbc_da1_addfm_set,
+ vbc_da0_addst_set,
+ vbc_da1_addst_set
+};
+
+static inline int vbc_da0_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(DADGCTL, 0x80 | (0xFF & dg), 0xFF);
+ } else {
+ vbc_reg_update(DADGCTL, 0, 0x80);
+ }
+ return 0;
+}
+
+static inline int vbc_da1_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(DADGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00);
+ } else {
+ vbc_reg_update(DADGCTL, 0, 0x8000);
+ }
+ return 0;
+}
+
+static inline int vbc_ad0_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDG01CTL, 0x80 | (0xFF & dg), 0xFF);
+ } else {
+ vbc_reg_update(ADDG01CTL, 0, 0x80);
+ }
+ return 0;
+}
+
+static inline int vbc_ad1_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDG01CTL, (0x80 | (0xFF & dg)) << 8, 0xFF00);
+ } else {
+ vbc_reg_update(ADDG01CTL, 0, 0x8000);
+ }
+ return 0;
+}
+
+static inline int vbc_ad2_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDG23CTL, 0x80 | (0xFF & dg), 0xFF);
+ } else {
+ vbc_reg_update(ADDG23CTL, 0, 0x80);
+ }
+ return 0;
+}
+
+static inline int vbc_ad3_dg_set(int enable, int dg)
+{
+ if (enable) {
+ vbc_reg_update(ADDG23CTL, (0x80 | (0xFF & dg)) << 8, 0xFF00);
+ } else {
+ vbc_reg_update(ADDG23CTL, 0, 0x8000);
+ }
+ return 0;
+}
+
+static inline int vbc_st0_dg_set(int dg)
+{
+ vbc_reg_update(STCTL0, (0x7F & dg) << 4, 0x7F0);
+ return 0;
+}
+
+static inline int vbc_st1_dg_set(int dg)
+{
+ vbc_reg_update(STCTL1, (0x7F & dg) << 4, 0x7F0);
+ return 0;
+}
+
+static inline int vbc_st0_hpf_set(int enable, int hpf_val)
+{
+ if (enable) {
+ vbc_reg_update(STCTL0, BIT(VBST_HPF_0), BIT(VBST_HPF_0));
+ vbc_reg_update(STCTL0, 0xF & hpf_val, 0xF);
+ } else {
+ vbc_reg_update(STCTL0, 0, BIT(VBST_HPF_0));
+ vbc_reg_update(STCTL0, 3, 0xF);
+ }
+ return 0;
+}
+
+static inline int vbc_st1_hpf_set(int enable, int hpf_val)
+{
+ if (enable) {
+ vbc_reg_update(STCTL1, BIT(VBST_HPF_1), BIT(VBST_HPF_1));
+ vbc_reg_update(STCTL1, 0xF & hpf_val, 0xF);
+ } else {
+ vbc_reg_update(STCTL1, 0, BIT(VBST_HPF_1));
+ vbc_reg_update(STCTL1, 3, 0xF);
+ }
+ return 0;
+}
+
+static inline void vbc_da_alc_mode_set(int dp_t_mode)
+{
+ vbc_reg_update(DAHPCTL, dp_t_mode ? BIT(VBDAC_ALC_DP_T_MODE) : 0,
+ BIT(VBDAC_ALC_DP_T_MODE));
+}
+
+static inline void vbc_da_eq4_pos_sel(int pos)
+{
+ /*EQ4 pos sel */
+ vbc_reg_update(DAHPCTL, pos ? BIT(VBDAC_EQ4_POS_SEL) : 0,
+ BIT(VBDAC_EQ4_POS_SEL));
+}
+
+static int vbc_try_dg_set(struct vbc_codec_priv *vbc_codec, int vbc_idx, int id)
+{
+ struct vbc_dg *p_dg = &vbc_codec->dg[vbc_idx];
+ int dg = p_dg->dg_val[id];
+ if (p_dg->dg_switch[id]) {
+ p_dg->dg_set[id] (1, dg);
+ } else {
+ p_dg->dg_set[id] (0, dg);
+ }
+ return 0;
+}
+
+static int vbc_try_st_dg_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg;
+ if (id == VBC_LEFT) {
+ vbc_st0_dg_set(p_st_dg->dg_val[id]);
+ } else {
+ vbc_st1_dg_set(p_st_dg->dg_val[id]);
+ }
+ return 0;
+}
+
+static int vbc_try_st_hpf_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg;
+ if (id == VBC_LEFT) {
+ vbc_st0_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]);
+ } else {
+ vbc_st1_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]);
+ }
+ return 0;
+}
+
+static int vbc_try_src_set(struct vbc_codec_priv *vbc_codec, int vbc_idx)
+{
+#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN
+ vbc_src_set(dfm.hw_rate, vbc_idx);
+#else
+ vbc_src_set(0, vbc_idx);
+#endif
+ return 0;
+}
+
+static int vbc_try_da_iismux_set(int port)
+{
+ return vbc_da_iismux_set(port ? (port + 1) : 0);
+}
+
+static int vbc_try_ad_iismux_set(int port)
+{
+ return vbc_ad_iismux_set(port);
+}
+
+static int vbc_try_ad23_iismux_set(int port)
+{
+ return vbc_ad23_iismux_set(port);
+}
+
+static int vbc_try_ad_dgmux_set(struct vbc_codec_priv *vbc_codec, int id)
+{
+ switch (id) {
+ case ADC0_DGMUX:
+ vbc_ad0_dgmux_set(vbc_codec->adc_dgmux_val[ADC0_DGMUX]);
+ break;
+ case ADC1_DGMUX:
+ vbc_ad1_dgmux_set(vbc_codec->adc_dgmux_val[ADC1_DGMUX]);
+ break;
+ case ADC2_DGMUX:
+ vbc_ad2_dgmux_set(vbc_codec->adc_dgmux_val[ADC2_DGMUX]);
+ break;
+ case ADC3_DGMUX:
+ vbc_ad3_dgmux_set(vbc_codec->adc_dgmux_val[ADC3_DGMUX]);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DEBUG
+static const char *get_event_name(int event)
+{
+ const char *ev_name;
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ev_name = "PRE_PMU";
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ ev_name = "POST_PMU";
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ev_name = "PRE_PMD";
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ev_name = "POST_PMD";
+ break;
+ default:
+ BUG();
+ return 0;
+ }
+ return ev_name;
+}
+#endif
+
+static const char *st_chan_sel_txt[] = {
+ "AD01", "AD23"
+};
+
+static const char *st0_sel_txt[] = {
+ "AD0(2)ST0", "AD1(3)ST0", "NOINPUT",
+};
+
+static const char *st1_sel_txt[] = {
+ "AD1(3)ST1", "AD0(2)ST1", "NOINPUT",
+};
+
+static const char *ad0_inmux_txt[] = {
+ "IIS0AD0", "IIS1AD0", "NOINPUT",
+};
+
+static const char *ad1_inmux_txt[] = {
+ "IIS1AD1", "IIS0AD1", "NOINPUT",
+};
+
+static const char *ad2_inmux_txt[] = {
+ "IIS2AD2", "IIS3AD2", "NOINPUT",
+};
+
+static const char *ad3_inmux_txt[] = {
+ "IIS3AD3", "IIS2AD3", "NOINPUT",
+};
+
+static const char *ad_iis_txt[] = {
+ "AUDIIS0", "DIGFM", "EXTDIGFM", "EXTIIS6", "AUDIIS1",
+};
+
+static const char *ad23_iis_txt[] = {
+ "AUDIIS1", "DIGFM", "EXTDIGFM", "EXTIIS6", "AUDIIS0",
+};
+
+static const char *dfm_dac_st_txt[] = {
+ "BYPASS(ST)", "ADD(ST)", "SUBTRACT(ST)",
+};
+
+#define SPRD_VBC_ENUM(xreg, xmax, xtexts)\
+ SOC_ENUM_SINGLE(FUN_REG(xreg), 0, xmax, xtexts)
+
+static const struct soc_enum vbc_mux_sel_enum[SPRD_VBC_MUX_MAX] = {
+ /*ST CHAN MUX */
+ SPRD_VBC_ENUM(SPRD_VBC_ST0_CHAN_MUX, 2, st_chan_sel_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_ST1_CHAN_MUX, 2, st_chan_sel_txt),
+ /*ST INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_ST0_MUX, 4, st0_sel_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_ST1_MUX, 4, st1_sel_txt),
+ /*AD INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_AD0_INMUX, 4, ad0_inmux_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_AD1_INMUX, 4, ad1_inmux_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_AD2_INMUX, 4, ad2_inmux_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_AD3_INMUX, 4, ad3_inmux_txt),
+ /*IIS INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_AD_IISMUX, 5, ad_iis_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_AD23_IISMUX, 5, ad23_iis_txt),
+ /* DFM DAC INMUX */
+ SPRD_VBC_ENUM(SPRD_VBC_DFM_DA0_ADDFM_INMUX, 3, dfm_dac_st_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_DFM_DA1_ADDFM_INMUX, 3, dfm_dac_st_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_DFM_DA0_ADDST_INMUX, 3, dfm_dac_st_txt),
+ SPRD_VBC_ENUM(SPRD_VBC_DFM_DA1_ADDST_INMUX, 3, dfm_dac_st_txt),
+};
+
+static int vbc_chan_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ int vbc_idx = FUN_REG(w->reg);
+ int chan = w->shift;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s(%s%d) Event is %s\n", __func__,
+ vbc_get_name(vbc_idx), chan, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ vbc_chan_enable(1, vbc_idx, chan);
+ vbc_try_src_set(vbc_codec, vbc_idx);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ vbc_src_set(0, vbc_idx);
+ vbc_chan_enable(0, vbc_idx, chan);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int vbc_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ vbc_power(1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ vbc_power(0);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int dfm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event));
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ fm_set_vbc_buffer_size(); /*No use in FM function, just for debug VBC */
+ vbc_try_st_dg_set(vbc_codec, VBC_LEFT);
+ vbc_try_st_dg_set(vbc_codec, VBC_RIGHT);
+ if (!p_eq_setting->codec)
+ p_eq_setting->codec = codec;
+ /*eq setting */
+ if (p_eq_setting->is_active[VBC_PLAYBACK]
+ && p_eq_setting->data[VBC_PLAYBACK])
+ vbc_eq_try_apply(codec, VBC_PLAYBACK);
+ else
+ vbc_eq_profile_close(vbc_codec->codec, VBC_PLAYBACK);
+ vbc_enable(1);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ vbc_enable(0);
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int aud_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ unsigned int vbc_idx = FUN_REG(w->reg);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Event is %s\n Chan is %s", __func__,
+ get_event_name(event), vbc_get_name(vbc_idx));
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /*eq setting */
+ if (!p_eq_setting->codec)
+ p_eq_setting->codec = codec;
+ if (p_eq_setting->is_active[vbc_idx]
+ && p_eq_setting->data[vbc_idx])
+ vbc_eq_try_apply(codec, vbc_idx);
+ else
+ vbc_eq_profile_close(vbc_codec->codec, vbc_idx);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mux_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ unsigned int id = SPRD_VBC_MUX_IDX(FUN_REG(w->reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]);
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__,
+ vbc_mux_debug_str[id], mux->val, get_event_name(event));
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ mux->set = vbc_mux_cfg[id];
+ ret = mux->set(mux->val);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ mux->set = 0;
+ if (IS_SPRD_VBC_DFM_ST_MUX_RANG(id)) {
+ ret = vbc_mux_cfg[id] (0);
+ }
+ /*ret = vbc_mux_cfg[id] (0);*/
+ break;
+ default:
+ BUG();
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sprd_vbc_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]);
+
+ ucontrol->value.enumerated.item[0] = mux->val;
+
+ return 0;
+}
+
+static int sprd_vbc_mux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg));
+ unsigned int max = e->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]);
+ int ret = 0;
+
+ if (mux->val == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sp_asoc_pr_info("Set MUX[%s] to %d\n", vbc_mux_debug_str[reg],
+ ucontrol->value.enumerated.item[0]);
+
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+
+ /*notice the sequence */
+ ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+ /*update reg: must be set after snd_soc_dapm_put_enum_double->change = snd_soc_test_bits(widget->codec, e->reg, mask, val); */
+ mux->val = (ucontrol->value.enumerated.item[0] & mask);
+ if (mux->set) {
+ ret = mux->set(mux->val);
+ }
+
+ return ret;
+}
+
+#define SPRD_VBC_MUX(xname, xenum) \
+ SOC_DAPM_ENUM_EXT(xname, xenum, sprd_vbc_mux_get, sprd_vbc_mux_put)
+
+static const struct snd_kcontrol_new vbc_mux[SPRD_VBC_MUX_MAX] = {
+ SPRD_VBC_MUX("ST0 CHAN MUX", vbc_mux_sel_enum[SPRD_VBC_ST0_CHAN_MUX]),
+ SPRD_VBC_MUX("ST1 CHAN MUX", vbc_mux_sel_enum[SPRD_VBC_ST1_CHAN_MUX]),
+ SPRD_VBC_MUX("ST0 INMUX", vbc_mux_sel_enum[SPRD_VBC_ST0_MUX]),
+ SPRD_VBC_MUX("ST1 INMUX", vbc_mux_sel_enum[SPRD_VBC_ST1_MUX]),
+ SPRD_VBC_MUX("AD0 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD0_INMUX]),
+ SPRD_VBC_MUX("AD1 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD1_INMUX]),
+ SPRD_VBC_MUX("AD2 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD2_INMUX]),
+ SPRD_VBC_MUX("AD3 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD3_INMUX]),
+ SPRD_VBC_MUX("AD IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD_IISMUX]),
+ SPRD_VBC_MUX("AD23 IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD23_IISMUX]),
+ SPRD_VBC_MUX("DA0 ADDFM MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA0_ADDFM_INMUX]),
+ SPRD_VBC_MUX("DA1 ADDFM MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA1_ADDFM_INMUX]),
+ SPRD_VBC_MUX("DA0 ADDST MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA0_ADDST_INMUX]),
+ SPRD_VBC_MUX("DA1 ADDST MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA1_ADDST_INMUX]),
+};
+
+#define VBC_DAPM_MUX_E(wname, wreg) \
+ SND_SOC_DAPM_MUX_E(wname, FUN_REG(wreg), 0, 0, &vbc_mux[wreg], mux_event, \
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
+
+static int vbc_loop_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg));
+ ucontrol->value.integer.value[0] = vbc_codec->vbc_loop_switch[id];
+ return 0;
+}
+
+static int vbc_loop_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct snd_soc_codec *codec = widget->codec;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg));
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->vbc_loop_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC AD%s LOOP Switch Set %x\n",
+ id == 0 ? "01" : "23",
+ (int)ucontrol->value.integer.value[0]);
+
+ snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ vbc_codec->vbc_loop_switch[id] = ret;
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new vbc_loop_control[] = {
+ SOC_SINGLE_EXT("Switch",
+ FUN_REG(VBC_AD01_LOOP_SWITCH),
+ 0, 1, 0,
+ vbc_loop_switch_get,
+ vbc_loop_switch_put),
+ SOC_SINGLE_EXT("Switch",
+ FUN_REG(VBC_AD23_LOOP_SWITCH),
+ 0, 1, 0,
+ vbc_loop_switch_get,
+ vbc_loop_switch_put),
+};
+
+static const struct snd_soc_dapm_widget vbc_codec_dapm_widgets[] = {
+ /*power */
+ SND_SOC_DAPM_SUPPLY("VBC Power", SND_SOC_NOPM, 0, 0,
+ vbc_power_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ /* STchan mux */
+ VBC_DAPM_MUX_E("ST0 CHAN MUX", SPRD_VBC_ST0_CHAN_MUX),
+ VBC_DAPM_MUX_E("ST1 CHAN MUX", SPRD_VBC_ST1_CHAN_MUX),
+
+ /* ST inmux */
+ VBC_DAPM_MUX_E("ST0 INMUX", SPRD_VBC_ST0_MUX),
+ VBC_DAPM_MUX_E("ST1 INMUX", SPRD_VBC_ST1_MUX),
+
+ /*ADC inmux */
+ VBC_DAPM_MUX_E("AD0 INMUX", SPRD_VBC_AD0_INMUX),
+ VBC_DAPM_MUX_E("AD1 INMUX", SPRD_VBC_AD1_INMUX),
+ VBC_DAPM_MUX_E("AD2 INMUX", SPRD_VBC_AD2_INMUX),
+ VBC_DAPM_MUX_E("AD3 INMUX", SPRD_VBC_AD3_INMUX),
+
+ /*IIS MUX */
+ VBC_DAPM_MUX_E("AD IISMUX", SPRD_VBC_AD_IISMUX),
+ VBC_DAPM_MUX_E("AD23 IISMUX", SPRD_VBC_AD23_IISMUX),
+
+ /*ST Switch */
+ SND_SOC_DAPM_PGA_S("ST0 Switch", 4, SOC_REG(STCTL0), VBST_EN_0, 0, NULL,
+ 0),
+ SND_SOC_DAPM_PGA_S("ST1 Switch", 4, SOC_REG(STCTL1), VBST_EN_1, 0, NULL,
+ 0),
+
+ /* DFM DAC inmux */
+ VBC_DAPM_MUX_E("DA0 ADDFM MUX", SPRD_VBC_DFM_DA0_ADDFM_INMUX),
+ VBC_DAPM_MUX_E("DA1 ADDFM MUX", SPRD_VBC_DFM_DA1_ADDFM_INMUX),
+ VBC_DAPM_MUX_E("DA0 ADDST MUX", SPRD_VBC_DFM_DA0_ADDST_INMUX),
+ VBC_DAPM_MUX_E("DA1 ADDST MUX", SPRD_VBC_DFM_DA1_ADDST_INMUX),
+
+ SND_SOC_DAPM_LINE("DFM", dfm_event),
+ /*VBC Chan Switch */
+ SND_SOC_DAPM_PGA_S("DA0 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_LEFT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("DA1 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_RIGHT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD0 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_LEFT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD1 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_RIGHT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD2 Switch", 5, FUN_REG(VBC_CAPTRUE1), VBC_LEFT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_S("AD3 Switch", 5, FUN_REG(VBC_CAPTRUE1), VBC_RIGHT, 0,
+ vbc_chan_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /*AUD loop var vbc switch */
+ SND_SOC_DAPM_PGA_S("Aud input", 4, FUN_REG(VBC_CAPTRUE), 0, 0,
+ aud_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_S("Aud1 input", 4, FUN_REG(VBC_CAPTRUE1), 0, 0,
+ aud_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SWITCH("Aud Loop in VBC", SND_SOC_NOPM, 0, 0,
+ &vbc_loop_control[SPRD_VBC_SWITCH_IDX
+ (VBC_AD01_LOOP_SWITCH)]),
+ SND_SOC_DAPM_SWITCH("Aud1 Loop in VBC", SND_SOC_NOPM, 0, 0,
+ &vbc_loop_control[SPRD_VBC_SWITCH_IDX
+ (VBC_AD23_LOOP_SWITCH)]),
+};
+
+/* sprd_vbc supported interconnection*/
+static const struct snd_soc_dapm_route vbc_codec_intercon[] = {
+ /************************power********************************/
+ /* digital fm playback need to open DA Clk and DA power */
+ {"DFM", NULL, "VBC Power"},
+
+ /********************** capture in path in vbc ********************/
+ /* AD input route */
+ {"Aud Loop in VBC", "Switch", "Aud input"},
+
+ {"Aud1 Loop in VBC", "Switch", "Aud1 input"},
+
+ /* AD01 */
+ {"AD IISMUX", "DIGFM", "Dig FM Jack"},
+ {"AD IISMUX", "EXTDIGFM", "Dig FM Jack"},
+ {"AD IISMUX", "AUDIIS0", "Aud Loop in VBC"},
+
+ /* AD23 */
+ {"AD23 IISMUX", "DIGFM", "Dig FM Jack"},
+ {"AD23 IISMUX", "EXTDIGFM", "Dig FM Jack"},
+ {"AD23 IISMUX", "AUDIIS1", "Aud1 Loop in VBC"},
+
+ {"AD0 INMUX", "IIS0AD0", "AD IISMUX"},
+ {"AD0 INMUX", "IIS1AD0", "AD IISMUX"},
+ {"AD1 INMUX", "IIS1AD1", "AD IISMUX"},
+ {"AD1 INMUX", "IIS0AD1", "AD IISMUX"},
+ {"AD2 INMUX", "IIS2AD2", "AD23 IISMUX"},
+ {"AD2 INMUX", "IIS3AD2", "AD23 IISMUX"},
+ {"AD3 INMUX", "IIS3AD3", "AD23 IISMUX"},
+ {"AD3 INMUX", "IIS2AD3", "AD23 IISMUX"},
+
+ {"AD0 Switch", NULL, "AD0 INMUX"},
+ {"AD1 Switch", NULL, "AD1 INMUX"},
+ {"AD2 Switch", NULL, "AD2 INMUX"},
+ {"AD3 Switch", NULL, "AD3 INMUX"},
+
+ /********************** fm playback route ********************/
+ /*ST route */
+ {"ST0 CHAN MUX", "AD01", "AD0 Switch"},
+ {"ST0 CHAN MUX", "AD01", "AD1 Switch"},
+ {"ST0 CHAN MUX", "AD23", "AD2 Switch"},
+ {"ST0 CHAN MUX", "AD23", "AD3 Switch"},
+ {"ST1 CHAN MUX", "AD01", "AD0 Switch"},
+ {"ST1 CHAN MUX", "AD01", "AD1 Switch"},
+ {"ST1 CHAN MUX", "AD23", "AD2 Switch"},
+ {"ST1 CHAN MUX", "AD23", "AD3 Switch"},
+
+ {"ST0 INMUX", "AD0(2)ST0", "ST0 CHAN MUX"},
+ {"ST0 INMUX", "AD1(3)ST0", "ST0 CHAN MUX"},
+ {"ST1 INMUX", "AD1(3)ST1", "ST1 CHAN MUX"},
+ {"ST1 INMUX", "AD0(2)ST1", "ST1 CHAN MUX"},
+
+ {"ST0 Switch", NULL, "ST0 INMUX"},
+ {"ST1 Switch", NULL, "ST1 INMUX"},
+ {"DA0 Switch", NULL, "ST0 Switch"},
+ {"DA1 Switch", NULL, "ST1 Switch"},
+ {"DA0 ADDFM MUX", "ADD(ST)", "DA0 Switch"},
+ {"DA0 ADDFM MUX", "BYPASS(ST)", "DA0 Switch"},
+ {"DA0 ADDFM MUX", "SUBTRACT(ST)", "DA0 Switch"},
+ {"DA1 ADDFM MUX", "ADD(ST)", "DA1 Switch"},
+ {"DA1 ADDFM MUX", "BYPASS(ST)", "DA1 Switch"},
+ {"DA1 ADDFM MUX", "SUBTRACT(ST)", "DA1 Switch"},
+ {"DA0 ADDST MUX", "ADD(ST)", "DA0 Switch"},
+ {"DA0 ADDST MUX", "BYPASS(ST)", "DA0 Switch"},
+ {"DA0 ADDST MUX", "SUBTRACT(ST)", "DA0 Switch"},
+ {"DA1 ADDST MUX", "ADD(ST)", "DA1 Switch"},
+ {"DA1 ADDST MUX", "BYPASS(ST)", "DA1 Switch"},
+ {"DA1 ADDST MUX", "SUBTRACT(ST)", "DA1 Switch"},
+ {"DFM", NULL, "DA0 ADDFM MUX"},
+ {"DFM", NULL, "DA1 ADDFM MUX"},
+ {"DFM", NULL, "DA0 ADDST MUX"},
+ {"DFM", NULL, "DA1 ADDST MUX"},
+};
+
+int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai)
+{
+ struct vbc_codec_priv *vbc_codec = dev_get_drvdata(dai->dev);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ sp_asoc_pr_dbg("%s %s\n", __func__, vbc_get_name(vbc_idx));
+
+ switch (vbc_idx) {
+ case VBC_PLAYBACK:
+ vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port);
+ break;
+ case VBC_CAPTRUE:
+ vbc_try_ad_iismux_set(vbc_codec->sprd_vbc_mux
+ [SPRD_VBC_AD_IISMUX].val);
+ vbc_try_ad_dgmux_set(vbc_codec, 0);
+ vbc_try_ad_dgmux_set(vbc_codec, 1);
+ break;
+ case VBC_CAPTRUE1:
+ vbc_try_ad23_iismux_set(vbc_codec->sprd_vbc_mux
+ [SPRD_VBC_AD23_IISMUX].val);
+ vbc_try_ad_dgmux_set(vbc_codec, 2);
+ vbc_try_ad_dgmux_set(vbc_codec, 3);
+ break;
+ }
+
+ if (p_eq_setting->is_active[vbc_idx]
+ && p_eq_setting->data[vbc_idx])
+ vbc_eq_try_apply(vbc_codec->codec, vbc_idx);
+ else
+ vbc_eq_profile_close(vbc_codec->codec, vbc_idx);
+
+ vbc_da_alc_mode_set(vbc_codec->alc_dp_t_mode);
+ vbc_try_dg_set(vbc_codec, vbc_idx, VBC_LEFT);
+ vbc_try_dg_set(vbc_codec, vbc_idx, VBC_RIGHT);
+
+ return 0;
+}
+
+/*
+ * proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+static void vbc_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ int reg;
+
+ snd_iprintf(buffer, "vbc register dump\n");
+ for (reg = ARM_VB_BASE + 0x10; reg < ARM_VB_END; reg += 0x10) {
+ snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n",
+ (reg - ARM_VB_BASE)
+ , vbc_reg_read(reg + 0x00)
+ , vbc_reg_read(reg + 0x04)
+ , vbc_reg_read(reg + 0x08)
+ , vbc_reg_read(reg + 0x0C)
+ );
+ }
+}
+
+static void vbc_proc_init(struct snd_soc_codec *codec)
+{
+ struct snd_info_entry *entry;
+
+ if (!snd_card_proc_new(codec->card->snd_card, "vbc", &entry))
+ snd_info_set_text_ops(entry, NULL, vbc_proc_read);
+}
+#else /* !CONFIG_PROC_FS */
+static inline void vbc_proc_init(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static int vbc_da_eq_reg_offset(u32 reg)
+{
+ int i = 0;
+ if (reg == DAPATCHCTL) {
+ i = 0;
+ } else if ((reg >= DAHPCTL) && (reg <= STCTL1)) {
+ i = ((reg - DAHPCTL) >> 2) + 1;
+ } else if ((reg >= DACSRCCTL) && (reg <= MIXERCTL)) {
+ i = ((reg - DACSRCCTL) >> 2) + ((STCTL1 - DAHPCTL) >> 2) + 2;
+ } else if ((reg >= VBNGCVTHD) && (reg <= VBNGCTL)) {
+ i = ((reg - VBNGCVTHD) >> 2) + ((MIXERCTL - DACSRCCTL) >> 2) +
+ ((STCTL1 - DAHPCTL) >> 2) + 3;
+ } else if ((reg >= HPCOEF0_H) && (reg <= HPCOEF71_L)) {
+ i = ((reg - HPCOEF0_H) >> 2) + ((VBNGCTL - VBNGCVTHD) >> 2) +
+ ((MIXERCTL - DACSRCCTL) >> 2) + ((STCTL1 - DAHPCTL) >> 2) +
+ 4;
+ }
+ BUG_ON(i >= VBC_DA_EFFECT_PARAS_LEN);
+ return i;
+}
+
+static int vbc_ad_eq_reg_offset(u32 reg)
+{
+ int i = 0;
+ if (reg == ADPATCHCTL) {
+ i = 0;
+ } else if (reg == ADHPCTL) {
+ i = 1;
+ } else if ((reg >= AD01_HPCOEF0_H) && (reg <= AD01_HPCOEF42_L)) {
+ i = ((reg - AD01_HPCOEF0_H) >> 2) + 2;
+ } else if ((reg >= AD23_HPCOEF0_H) && (reg <= AD23_HPCOEF42_L)) {
+ i = ((reg - AD23_HPCOEF0_H) >> 2) + 2;
+ }
+ BUG_ON(i >= VBC_AD_EFFECT_PARAS_LEN);
+ return i;
+}
+
+static inline void vbc_da_eq_reg_set(u32 reg, void *data)
+{
+ u32 *effect_paras = (u32 *) data;
+ if (reg == DAHPCTL) {
+ /*EQ set */
+ vbc_reg_update(DAHPCTL, effect_paras[vbc_da_eq_reg_offset(reg)],
+ 0x1FF);
+ } else {
+ vbc_reg_write(reg, effect_paras[vbc_da_eq_reg_offset(reg)]);
+ }
+}
+
+static inline void vbc_da_eq_reg_set_range(u32 reg_start, u32 reg_end,
+ void *data)
+{
+ u32 reg_addr;
+ for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) {
+ vbc_da_eq_reg_set(reg_addr, data);
+ }
+}
+
+static inline void vbc_ad_eq_reg_set(u32 reg, void *data)
+{
+ u32 *effect_paras = (u32 *) data;
+ if (reg == ADHPCTL) {
+ vbc_reg_update(ADHPCTL, effect_paras[vbc_da_eq_reg_offset(reg)],
+ 0xC0);
+ } else {
+ vbc_reg_write(reg, effect_paras[vbc_ad_eq_reg_offset(reg)]);
+ }
+}
+
+static inline void vbc_ad_eq_reg_set_range(u32 reg_start, u32 reg_end,
+ void *data)
+{
+ u32 reg_addr;
+ for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) {
+ vbc_ad_eq_reg_set(reg_addr, data);
+ }
+}
+
+/*gray code change*/
+static void gray_dir(unsigned int *bit, int dir)
+{
+ if (dir) {
+ *bit >>= 1;
+ } else {
+ *bit <<= 1;
+ }
+}
+
+static int gray(u32 reg, int from, int to, int (*step_action) (int, int))
+{
+ int start_bit = (from > to) ? 31 : 0;
+ unsigned int bit = (1 << start_bit);
+ int r = from;
+ int diff = (from ^ to);
+ int i;
+ for (i = 0; i < 32; i++, gray_dir(&bit, start_bit)) {
+ if ((diff & bit)) {
+ r &= ~bit;
+ r |= (bit & to);
+ step_action(reg, r);
+ }
+ }
+ return 0;
+}
+
+static int step_action_set_reg(int reg, int r)
+{
+ vbc_reg_write(reg, r);
+ udelay(10);
+ return 0;
+}
+
+static void gray_set_reg(u32 reg, int from, int to)
+{
+ sp_asoc_pr_dbg("gray set reg(0x%x) = (0x%x) from (0x%x))\n", reg, to,
+ from);
+ gray(reg, from, to, step_action_set_reg);
+}
+
+static void vbc_eq_iir_ab_clr(u32 reg_addr)
+{
+ vbc_reg_write(reg_addr + 0x8, 0); /*b0_H */
+ vbc_reg_write(reg_addr + 0xC, 0); /*b0_L */
+ vbc_reg_write(reg_addr + 0x18, 0); /*b1_H */
+ vbc_reg_write(reg_addr + 0x1C, 0); /*b1_L */
+ vbc_reg_write(reg_addr + 0x28, 0); /*b2_H */
+ vbc_reg_write(reg_addr + 0x2C, 0); /*b2_L */
+ vbc_reg_write(reg_addr + 0x10, 0); /*a0_H */
+ vbc_reg_write(reg_addr + 0x14, 0); /*a0_L */
+ vbc_reg_write(reg_addr + 0x20, 0); /*a1_H */
+ vbc_reg_write(reg_addr + 0x24, 0); /*a1_L */
+ vbc_reg_write(reg_addr + 0x30, 0); /*a2_H */
+ vbc_reg_write(reg_addr + 0x34, 0); /*a2_L */
+}
+
+static void vbc_eq_iir_s_clr(u32 reg_addr)
+{
+#if 0
+ vbc_reg_write2(reg_addr, 0); /*s0_H */
+ vbc_reg_write2(reg_addr + 0x4, 0); /*s0_L */
+#else
+ u32 val = vbc_reg_read(reg_addr);
+ gray_set_reg(reg_addr, val, 0);
+ val = vbc_reg_read(reg_addr + 0x4);
+ gray_set_reg(reg_addr + 0x4, val, 0);
+#endif
+}
+
+static void vbc_eq_iir_ab_set_data(u32 reg_addr, void *data)
+{
+ vbc_da_eq_reg_set(reg_addr + 0x30, data); /*a2_H */
+ vbc_da_eq_reg_set(reg_addr + 0x34, data); /*a2_L */
+ vbc_da_eq_reg_set(reg_addr + 0x20, data); /*a1_H */
+ vbc_da_eq_reg_set(reg_addr + 0x24, data); /*a1_L */
+ vbc_da_eq_reg_set(reg_addr + 0x10, data); /*a0_H */
+ vbc_da_eq_reg_set(reg_addr + 0x14, data); /*a0_L */
+ vbc_da_eq_reg_set(reg_addr + 0x28, data); /*b2_H */
+ vbc_da_eq_reg_set(reg_addr + 0x2C, data); /*b2_L */
+ vbc_da_eq_reg_set(reg_addr + 0x18, data); /*b1_H */
+ vbc_da_eq_reg_set(reg_addr + 0x1C, data); /*b1_L */
+ vbc_da_eq_reg_set(reg_addr + 0x8, data); /*b0_H */
+ vbc_da_eq_reg_set(reg_addr + 0xC, data); /*b0_L */
+}
+
+static void vbc_eq_iir_s_set_data(u32 reg_addr, void *data)
+{
+#if 0
+ vbc_da_eq_reg_set(reg_addr, data); /*s0_H */
+ vbc_da_eq_reg_set(reg_addr + 0x4, data); /*s0_L */
+#else
+ u32 *effect_paras = (u32 *) data;
+ gray_set_reg(reg_addr, 0, effect_paras[vbc_da_eq_reg_offset(reg_addr)]);
+ gray_set_reg(reg_addr + 0x4, 0,
+ effect_paras[vbc_da_eq_reg_offset(reg_addr + 0x4)]);
+#endif
+}
+
+static void vbc_da_alc_reg_set(void *data)
+{
+ u32 *effect_paras = (u32 *) data;
+ u32 reg_addr;
+ u32 val;
+#ifdef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ for (reg_addr = DAHPCTL; reg_addr <= DAALCCTL10; reg_addr += 4) {
+#else
+ for (reg_addr = DAALCCTL0; reg_addr <= DAALCCTL10; reg_addr += 4) {
+#endif
+ val = vbc_reg_read(reg_addr);
+ gray_set_reg(reg_addr, val,
+ effect_paras[vbc_da_eq_reg_offset(reg_addr)]);
+ }
+}
+
+static void vbc_eq_reg_apply(struct snd_soc_codec *codec, void *data,
+ int vbc_idx)
+{
+ u32 reg;
+ void *effect_paras;
+ int val;
+ if (vbc_idx == VBC_PLAYBACK) {
+ val = ((u32 *) data)[vbc_da_eq_reg_offset(DAHPCTL)];
+ /*DA EQ6 set */
+ /*s6-s0 clear */
+ for (reg = HPCOEF42_H; reg >= HPCOEF0_H; reg -= 0x38) {
+ vbc_eq_iir_s_clr(reg);
+ }
+ /*a,b clear */
+ for (reg = HPCOEF0_H; reg <= HPCOEF35_H; reg += 0x38) {
+ vbc_eq_iir_ab_clr(reg);
+ }
+ /*iir state clear */
+ vbc_reg_update(DAHPCTL, BIT(VBDAEQ_HP_REG_CLR),
+ BIT(VBDAEQ_HP_REG_CLR));
+
+ /*a,b set */
+#ifdef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ effect_paras = data;
+#else
+ if (val & VBDAC_EQ6_EN)
+ effect_paras = data;
+ else
+ effect_paras = &vbc_da_eq_profile_default;
+#endif
+
+ for (reg = HPCOEF35_H; reg >= HPCOEF0_H; reg -= 0x38) {
+ vbc_eq_iir_ab_set_data(reg, effect_paras);
+ }
+ /*s0-s6 set */
+ for (reg = HPCOEF0_H; reg <= HPCOEF42_H; reg += 0x38) {
+ vbc_eq_iir_s_set_data(reg, effect_paras);
+ }
+
+ vbc_da_eq_reg_set(DAHPCTL, data);
+
+ /*ALC set */
+#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ if (val & VBDAC_ALC_EN)
+ effect_paras = data;
+ else
+ effect_paras = &vbc_da_eq_profile_default;
+#endif
+ vbc_da_alc_reg_set(effect_paras);
+
+ /*iir state set */
+ vbc_reg_update(DAHPCTL, 0, BIT(VBDAEQ_HP_REG_CLR));
+#if 0
+ /*DA EQ4 set */
+ for (reg = HPCOEF71_H; reg >= HPCOEF43_H; reg -= 0x38) {
+ vbc_eq_iir_s_clr(reg);
+ }
+ for (reg = HPCOEF43_H; reg <= HPCOEF64_H; reg += 0x38) {
+ vbc_eq_iir_ab_clr(reg);
+ }
+ if (val & VBDAC_EQ4_EN)
+ effect_paras = data;
+ else
+ effect_paras = &vbc_da_eq_profile_default;
+
+ for (reg = HPCOEF64_H; reg >= HPCOEF43_H; reg -= 0x38) {
+ vbc_eq_iir_ab_set_data(reg, effect_paras);
+ }
+ for (reg = HPCOEF43_H; reg <= HPCOEF71_H; reg += 0x38) {
+ vbc_eq_iir_s_set_data(reg, effect_paras);
+ }
+
+#endif
+ /*other */
+ vbc_da_eq_reg_set(DACSRCCTL, data);
+ vbc_da_eq_reg_set(MIXERCTL, data);
+ vbc_da_eq_reg_set_range(VBNGCVTHD, VBNGCTL, data);
+ } else {
+#if 0
+ val = ((u32 *) data)[vbc_ad_eq_reg_offset(ADHPCTL)];
+ if (vbc_idx == VBC_CAPTRUE) {
+ /*AD01 EQ6 set */
+ for (reg = AD01_HPCOEF42_H; reg >= AD01_HPCOEF0_H;
+ reg -= 0x38) {
+ vbc_eq_iir_s_clr(reg);
+ }
+ for (reg = AD01_HPCOEF0_H; reg <= AD01_HPCOEF35_H;
+ reg += 0x38) {
+ vbc_eq_iir_ab_clr(reg);
+ }
+ /*iir state clear */
+ vbc_reg_write(ADHPCTL, BIT(VBADC01EQ_HP_REG_CLR),
+ BIT(VBADC01EQ_HP_REG_CLR));
+
+ if (val & VBADC01_EQ6_EN)
+ effect_paras = data;
+ else
+ effect_paras = &vbc_ad_eq_profile_default;
+
+ for (reg = AD01_HPCOEF35_H; reg >= AD01_HPCOEF0_H;
+ reg -= 0x38) {
+ vbc_eq_iir_ab_set_data(reg, data);
+ }
+ for (reg = AD01_HPCOEF0_H; reg <= AD01_HPCOEF42_H;
+ reg += 0x38) {
+ vbc_eq_iir_s_set_data(reg, data);
+ }
+ /*iir state set */
+ vbc_reg_write(ADHPCTL, 0, BIT(VBADC01EQ_HP_REG_CLR));
+ } else {
+ /*AD23 EQ6 set */
+ for (reg = AD23_HPCOEF42_H; reg >= AD23_HPCOEF0_H;
+ reg -= 0x38) {
+ vbc_eq_iir_s_clr(reg);
+ }
+ for (reg = AD23_HPCOEF0_H; reg <= AD23_HPCOEF35_H;
+ reg += 0x38) {
+ vbc_eq_iir_ab_clr(reg);
+ }
+ /*iir state clear */
+ vbc_reg_write(ADHPCTL, BIT(VBADC23EQ_HP_REG_CLR),
+ BIT(VBADC23EQ_HP_REG_CLR));
+
+ if (val & VBADC23_EQ6_EN)
+ effect_paras = data;
+ else
+ effect_paras = &vbc_ad_eq_profile_default;
+ for (reg = AD23_HPCOEF35_H; reg >= AD23_HPCOEF0_H;
+ reg -= 0x38) {
+ vbc_eq_iir_ab_set_data(reg, data);
+ }
+ for (reg = AD23_HPCOEF0_H; reg <= AD23_HPCOEF42_H;
+ reg += 0x38) {
+ vbc_eq_iir_s_set_data(reg, data);
+ }
+ /*iir state set */
+ vbc_reg_write(ADHPCTL, 0, BIT(VBADC23EQ_HP_REG_CLR));
+ }
+#endif
+ }
+}
+
+static void vbc_eq_profile_apply(struct snd_soc_codec *codec, void *data,
+ int vbc_idx)
+{
+ if (codec) {
+ vbc_eq_reg_apply(codec, data, vbc_idx);
+ }
+}
+
+static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx)
+{
+ switch (vbc_idx) {
+ case VBC_PLAYBACK:
+ vbc_eq_profile_apply(codec,
+ &vbc_da_eq_profile_default, vbc_idx);
+ break;
+ case VBC_CAPTRUE:
+ case VBC_CAPTRUE1:
+ vbc_eq_profile_apply(codec,
+ &vbc_ad_eq_profile_default, vbc_idx);
+ break;
+ default:
+ break;
+ }
+}
+
+static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ u32 *data;
+
+ sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)p_eq_setting->vbc_eq_apply);
+ if (p_eq_setting->vbc_eq_apply) {
+ mutex_lock(&vbc_codec->load_mutex);
+ if (vbc_idx < SPRD_VBC_PLAYBACK_COUNT) {
+ struct vbc_da_eq_profile *now =
+ &(((struct vbc_da_eq_profile
+ *)(p_eq_setting->data[VBC_PLAYBACK]))
+ [p_eq_setting->now_profile[VBC_PLAYBACK]]);
+ data = now->effect_paras;
+ sp_asoc_pr_info("VBC %s EQ Apply '%s'\n",
+ vbc_get_name(vbc_idx), now->name);
+ } else {
+ struct vbc_ad_eq_profile *now =
+ &(((struct vbc_ad_eq_profile
+ *)(p_eq_setting->data[vbc_idx]))
+ [p_eq_setting->now_profile[vbc_idx]]);
+ data = now->effect_paras;
+ sp_asoc_pr_info("VBC %s EQ Apply '%s'\n",
+ vbc_get_name(vbc_idx), now->name);
+ }
+ p_eq_setting->vbc_eq_apply(codec, data, vbc_idx);
+ mutex_unlock(&vbc_codec->load_mutex);
+ }
+}
+
+static int vbc_eq_profile_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int vbc_idx = FUN_REG(mc->reg);
+ ucontrol->value.integer.value[0] = p_eq_setting->now_profile[vbc_idx];
+ return 0;
+}
+
+static int vbc_eq_profile_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int vbc_idx = FUN_REG(mc->reg);
+ int profile_max;
+
+ profile_max = p_eq_setting->hdr.num_da[vbc_idx];
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == p_eq_setting->now_profile[vbc_idx]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s EQ Select %ld max %d\n",
+ vbc_get_name(vbc_idx),
+ ucontrol->value.integer.value[0], profile_max);
+
+ if (ret < profile_max) {
+ p_eq_setting->now_profile[vbc_idx] = ret;
+ }
+
+ if (p_eq_setting->is_active[vbc_idx]
+ && p_eq_setting->data[vbc_idx])
+ vbc_eq_try_apply(codec, vbc_idx);
+
+ return ret;
+}
+
+static int vbc_eq_loading(struct snd_soc_codec *codec)
+{
+ int ret;
+ const u8 *fw_data;
+ const struct firmware *fw;
+ int i;
+ int vbc_idx;
+ int offset = 0;
+ int len = 0;
+ int old_num_profile;
+ int old_num_da[VBC_IDX_MAX];
+ int offset_len[VBC_IDX_MAX + 1];
+ int sum_count = 0;
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+ mutex_lock(&vbc_codec->load_mutex);
+ p_eq_setting->is_loading = 1;
+
+ /* request firmware for VBC EQ */
+ ret = request_firmware(&fw, "vbc_eq", p_eq_setting->dev);
+ if (ret != 0) {
+ pr_err("ERR:Failed to load firmware: %d\n", ret);
+ goto req_fw_err;
+ }
+ fw_data = fw->data;
+ old_num_profile = p_eq_setting->hdr.num_profile;
+ if (fw_data == NULL) {
+ pr_err("ERR:firmware data error!\n");
+ goto eq_out;
+ }
+ for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) {
+ old_num_da[vbc_idx] = p_eq_setting->hdr.num_da[vbc_idx];
+ }
+
+ memcpy(&p_eq_setting->hdr, fw_data, sizeof(p_eq_setting->hdr));
+
+ if (strncmp
+ (p_eq_setting->hdr.magic, VBC_EQ_FIRMWARE_MAGIC_ID,
+ VBC_EQ_FIRMWARE_MAGIC_LEN)) {
+ pr_err("ERR:Firmware magic error!\n");
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ if (p_eq_setting->hdr.profile_version != VBC_EQ_PROFILE_VERSION) {
+ pr_err("ERR:Firmware support version is 0x%x!\n",
+ VBC_EQ_PROFILE_VERSION);
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ if (p_eq_setting->hdr.num_profile > VBC_EQ_PROFILE_CNT_MAX) {
+ pr_err
+ ("ERR:Firmware profile to large at %d, max count is %d!\n",
+ p_eq_setting->hdr.num_profile, VBC_EQ_PROFILE_CNT_MAX);
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) {
+ sum_count += p_eq_setting->hdr.num_da[vbc_idx];
+ }
+
+ if (p_eq_setting->hdr.num_profile != sum_count) {
+ pr_err("ERR:Firmware profile total number is wrong!\n");
+ ret = -EINVAL;
+ goto eq_out;
+ }
+
+ offset_len[VBC_PLAYBACK] = sizeof(struct vbc_fw_header);
+ offset_len[VBC_CAPTRUE] = sizeof(struct vbc_da_eq_profile);
+ offset_len[VBC_CAPTRUE1] = sizeof(struct vbc_ad_eq_profile);
+ offset_len[VBC_CAPTRUE1 + 1] = sizeof(struct vbc_ad_eq_profile);
+
+ for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) {
+ if (old_num_da[vbc_idx] != p_eq_setting->hdr.num_da[vbc_idx]) {
+ if (p_eq_setting->now_profile[vbc_idx] >=
+ p_eq_setting->hdr.num_da[vbc_idx])
+ p_eq_setting->now_profile[vbc_idx] = 0;
+ vbc_safe_kfree(&p_eq_setting->data[vbc_idx]);
+ }
+
+ if (vbc_idx) {
+ offset +=
+ offset_len[vbc_idx] *
+ p_eq_setting->hdr.num_da[vbc_idx - 1];
+ } else {
+ offset += offset_len[vbc_idx];
+ }
+
+ if (p_eq_setting->hdr.num_da[vbc_idx] == 0) {
+ continue;
+ }
+
+ len =
+ p_eq_setting->hdr.num_da[vbc_idx] * offset_len[vbc_idx + 1];
+ if (p_eq_setting->data[vbc_idx] == NULL) {
+ p_eq_setting->data[vbc_idx] = kzalloc(len, GFP_KERNEL);
+ if (p_eq_setting->data[vbc_idx] == NULL) {
+ ret = -ENOMEM;
+ for (--vbc_idx; vbc_idx >= 0; vbc_idx--) {
+ vbc_safe_kfree
+ (&p_eq_setting->data[vbc_idx]);
+ }
+ goto eq_out;
+ }
+ }
+
+ memcpy(p_eq_setting->data[vbc_idx], fw_data + offset, len);
+
+ for (i = 0; i < p_eq_setting->hdr.num_da[vbc_idx]; i++) {
+ const char *magic =
+ (char *)(p_eq_setting->data[vbc_idx]) +
+ (i * offset_len[vbc_idx + 1]);
+ if (strncmp
+ (magic, VBC_EQ_FIRMWARE_MAGIC_ID,
+ VBC_EQ_FIRMWARE_MAGIC_LEN)) {
+ pr_err
+ ("ERR:%s Firmware profile[%d] magic error!magic: %s \n",
+ vbc_get_name(vbc_idx), i, magic);
+ ret = -EINVAL;
+ for (; vbc_idx >= 0; vbc_idx--) {
+ vbc_safe_kfree
+ (&p_eq_setting->data[vbc_idx]);
+ }
+ goto eq_out;
+ }
+ }
+ }
+
+ ret = 0;
+ goto eq_out;
+
+eq_out:
+ release_firmware(fw);
+req_fw_err:
+ p_eq_setting->is_loading = 0;
+ mutex_unlock(&vbc_codec->load_mutex);
+ if (ret >= 0) {
+ struct vbc_da_eq_profile *profile =
+ &(((struct vbc_da_eq_profile
+ *)(p_eq_setting->data[VBC_PLAYBACK]))
+ [0]);
+ u32 *data = profile->effect_paras;
+ if (data[vbc_da_eq_reg_offset(DAHPCTL)] &
+ BIT(VBDAC_ALC_DP_T_MODE))
+ vbc_codec->alc_dp_t_mode = 1;
+ else
+ vbc_codec->alc_dp_t_mode = 0;
+ sp_asoc_pr_dbg("DAHPCTL:%x----alc_dp_t_mode:%d",
+ data[vbc_da_eq_reg_offset(DAHPCTL)],
+ vbc_codec->alc_dp_t_mode);
+ for (i = 0; i <= 2; i++) {
+ if (p_eq_setting->is_active[i]
+ && p_eq_setting->data[i])
+ vbc_eq_try_apply(codec, i);
+ }
+ }
+ sp_asoc_pr_dbg("return %i\n", ret);
+ return ret;
+}
+
+static int vbc_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ ucontrol->value.integer.value[0] = vbc_codec->vbc_control;
+ return 0;
+}
+
+static int vbc_switch_reg_val[];
+static int vbc_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+
+ if (vbc_codec->vbc_control == ucontrol->value.integer.value[0])
+ return 0;
+
+ sp_asoc_pr_info("VBC Switch to %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ vbc_codec->vbc_control = ucontrol->value.integer.value[0];
+
+ arch_audio_vbc_switch(vbc_switch_reg_val[vbc_codec->vbc_control]);
+ vbc_set_phys_addr(vbc_switch_reg_val[vbc_codec->vbc_control]);
+
+ return 1;
+}
+
+static int vbc_eq_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ ucontrol->value.integer.value[0] = p_eq_setting->is_active[id];
+ return 0;
+}
+
+static int vbc_eq_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int ret;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == p_eq_setting->is_active[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s EQ Switch %s\n", vbc_get_name(id),
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ if ((ret == 0) || (ret == 1)) {
+ p_eq_setting->is_active[id] = ret;
+ if (p_eq_setting->is_active[id]
+ && p_eq_setting->data[id]) {
+ p_eq_setting->vbc_eq_apply = vbc_eq_profile_apply;
+ vbc_eq_try_apply(codec, id);
+ } else {
+ p_eq_setting->vbc_eq_apply = 0;
+ vbc_eq_profile_close(codec, id);
+ }
+ }
+
+ return ret;
+}
+
+static int vbc_eq_load_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ ucontrol->value.integer.value[0] = p_eq_setting->is_loading;
+ return 0;
+}
+
+static int vbc_eq_load_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int ret;
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+
+ sp_asoc_pr_info("VBC EQ %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == 1) {
+ ret = vbc_eq_loading(codec);
+ }
+
+ return ret;
+}
+
+static int vbc_dg_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_val[id];
+ return 0;
+}
+
+static int vbc_dg_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->dg[vbc_idx].dg_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s%d DG set 0x%02x\n",
+ vbc_get_name(vbc_idx), id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->dg[vbc_idx].dg_val[id] = ret;
+ }
+
+ vbc_try_dg_set(vbc_codec, vbc_idx, id);
+
+ return ret;
+}
+
+static int vbc_st_dg_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.dg_val[id];
+ return 0;
+}
+
+static int vbc_st_dg_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.dg_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d DG set 0x%02x\n", id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->st_dg.dg_val[id] = ret;
+ }
+
+ vbc_try_st_dg_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int vbc_dg_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_switch[id];
+ return 0;
+}
+
+static int vbc_dg_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ int vbc_idx = mc->shift;
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->dg[vbc_idx].dg_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC %s%d DG Switch %s\n",
+ vbc_get_name(vbc_idx), id,
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ vbc_codec->dg[vbc_idx].dg_switch[id] = ret;
+
+ vbc_try_dg_set(vbc_codec, vbc_idx, id);
+
+ return ret;
+}
+
+static int vbc_st_hpf_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_switch[id];
+ return 0;
+}
+
+static int vbc_st_hpf_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.hpf_switch[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d HPF Switch %s\n", id,
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ vbc_codec->st_dg.hpf_switch[id] = ret;
+
+ vbc_try_st_hpf_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int vbc_st_hpf_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_val[id];
+ return 0;
+}
+
+static int vbc_st_hpf_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->st_dg.hpf_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC ST%d HPF set 0x%02x\n", id,
+ (int)ucontrol->value.integer.value[0]);
+
+ if (ret <= VBC_DG_VAL_MAX) {
+ vbc_codec->st_dg.hpf_val[id] = ret;
+ }
+
+ vbc_try_st_hpf_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int adc_dgmux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ucontrol->value.integer.value[0] = vbc_codec->adc_dgmux_val[id];
+ return 0;
+}
+
+static int adc_dgmux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+
+ ret = ucontrol->value.integer.value[0];
+ if (ret == vbc_codec->adc_dgmux_val[id]) {
+ return ret;
+ }
+
+ sp_asoc_pr_info("VBC AD%d DG mux : %ld\n", id,
+ ucontrol->value.integer.value[0]);
+
+ vbc_codec->adc_dgmux_val[id] = ret;
+ vbc_try_ad_dgmux_set(vbc_codec, id);
+
+ return ret;
+}
+
+static int dac_iismux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ ucontrol->value.integer.value[0] = vbc_codec->vbc_da_iis_port;
+ return 0;
+}
+
+static int dac_iismux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+
+ if (vbc_codec->vbc_da_iis_port == ucontrol->value.integer.value[0])
+ return 0;
+
+ sp_asoc_pr_info("VBC DA output to %s\n",
+ texts->texts[ucontrol->value.integer.value[0]]);
+
+ vbc_codec->vbc_da_iis_port = ucontrol->value.integer.value[0];
+
+ vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port);
+
+ return 1;
+}
+
+static int vbc_switch_reg_val[] = {
+ AUDIO_TO_CP0_DSP_CTRL,
+ AUDIO_TO_CP1_DSP_CTRL,
+ AUDIO_TO_AP_ARM_CTRL,
+ AUDIO_TO_CP2_ARM_CTRL,
+};
+
+static const char *switch_function[] =
+ { "cp0-dsp", "cp1-dsp", "ap", "cp2-arm" };
+static const char *eq_load_function[] = { "idle", "loading" };
+static const char *da_iis_mux_function[] =
+ { "sprd-codec", "ext-codec-4", "ext-codec-6" };
+static const char *fm_sample_rate_function[] = { "32000", "48000" };
+
+static const struct soc_enum vbc_enum[] = {
+ SOC_ENUM_SINGLE_EXT(4, switch_function),
+ SOC_ENUM_SINGLE_EXT(2, eq_load_function),
+ SOC_ENUM_SINGLE_EXT(3, da_iis_mux_function),
+ SOC_ENUM_SINGLE_EXT(2, fm_sample_rate_function),
+};
+
+static const struct snd_kcontrol_new vbc_codec_snd_controls[] = {
+ SOC_ENUM_EXT("VBC Switch", vbc_enum[0], vbc_switch_get,
+ vbc_switch_put),
+ SOC_SINGLE_EXT("VBC DA EQ Switch", FUN_REG(VBC_PLAYBACK), 0, 1, 0,
+ vbc_eq_switch_get,
+ vbc_eq_switch_put),
+ SOC_SINGLE_EXT("VBC AD01 EQ Switch", FUN_REG(VBC_CAPTRUE), 0, 1, 0,
+ vbc_eq_switch_get,
+ vbc_eq_switch_put),
+ SOC_SINGLE_EXT("VBC AD02 EQ Switch", FUN_REG(VBC_CAPTRUE1), 0, 1, 0,
+ vbc_eq_switch_get,
+ vbc_eq_switch_put),
+ SOC_ENUM_EXT("VBC EQ Update", vbc_enum[1], vbc_eq_load_get,
+ vbc_eq_load_put),
+
+ SOC_SINGLE_EXT("VBC DACL DG Set", FUN_REG(VBC_LEFT),
+ VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC DACR DG Set", FUN_REG(VBC_RIGHT),
+ VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADCL DG Set", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADCR DG Set", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADC23L DG Set", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE1, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC ADC23R DG Set", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE1, VBC_DG_VAL_MAX, 0, vbc_dg_get,
+ vbc_dg_put),
+ SOC_SINGLE_EXT("VBC STL DG Set", FUN_REG(VBC_LEFT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put),
+ SOC_SINGLE_EXT("VBC STR DG Set", FUN_REG(VBC_RIGHT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put),
+
+ SOC_SINGLE_EXT("VBC DACL DG Switch", FUN_REG(VBC_LEFT),
+ VBC_PLAYBACK, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC DACR DG Switch", FUN_REG(VBC_RIGHT),
+ VBC_PLAYBACK, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADCL DG Switch", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADCR DG Switch", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADC23L DG Switch", FUN_REG(VBC_LEFT),
+ VBC_CAPTRUE1, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC ADC23R DG Switch", FUN_REG(VBC_RIGHT),
+ VBC_CAPTRUE1, 1, 0, vbc_dg_switch_get,
+ vbc_dg_switch_put),
+ SOC_SINGLE_EXT("VBC STL HPF Switch", FUN_REG(VBC_LEFT),
+ 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put),
+ SOC_SINGLE_EXT("VBC STR HPF Switch", FUN_REG(VBC_RIGHT),
+ 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put),
+ SOC_SINGLE_EXT("VBC STL HPF Set", FUN_REG(VBC_LEFT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put),
+ SOC_SINGLE_EXT("VBC STR HPF Set", FUN_REG(VBC_RIGHT),
+ 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put),
+
+ SOC_SINGLE_EXT("VBC AD0 DG Mux", FUN_REG(ADC0_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_SINGLE_EXT("VBC AD1 DG Mux", FUN_REG(ADC1_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_SINGLE_EXT("VBC AD2 DG Mux", FUN_REG(ADC2_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_SINGLE_EXT("VBC AD3 DG Mux", FUN_REG(ADC3_DGMUX), 0, 1, 0,
+ adc_dgmux_get, adc_dgmux_put),
+ SOC_ENUM_EXT("VBC DA IIS Mux", vbc_enum[2], dac_iismux_get,
+ dac_iismux_put),
+ SOC_SINGLE_EXT("VBC DA EQ Profile Select", FUN_REG(VBC_PLAYBACK), 0,
+ VBC_EQ_PROFILE_CNT_MAX, 0,
+ vbc_eq_profile_get, vbc_eq_profile_put),
+ SOC_SINGLE_EXT("VBC AD01 EQ Profile Select", FUN_REG(VBC_CAPTRUE), 0,
+ VBC_EQ_PROFILE_CNT_MAX, 0,
+ vbc_eq_profile_get, vbc_eq_profile_put),
+ SOC_SINGLE_EXT("VBC AD23 EQ Profile Select", FUN_REG(VBC_CAPTRUE1), 0,
+ VBC_EQ_PROFILE_CNT_MAX, 0,
+ vbc_eq_profile_get, vbc_eq_profile_put),
+};
+
+static unsigned int vbc_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ /* Because snd_soc_update_bits reg is 16 bits short type,
+ so muse do following convert
+ */
+ if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) {
+ reg |= SPRD_VBC_BASE_HI;
+ return vbc_reg_read(reg);
+ } else if (IS_SPRD_VBC_MUX_RANG(FUN_REG(reg))) {
+ int id = SPRD_VBC_MUX_IDX(FUN_REG(reg));
+ struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]);
+ return mux->val;
+ } else if (IS_SPRD_VBC_SWITCH_RANG(FUN_REG(reg))) {
+ int id = SPRD_VBC_SWITCH_IDX(FUN_REG(reg));
+ return vbc_codec->vbc_loop_switch[id];
+ }
+
+ sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg);
+ return 0;
+}
+
+static int vbc_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) {
+ reg |= SPRD_VBC_BASE_HI;
+ return vbc_reg_write(reg, val);
+ }
+ sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg);
+ return 0;
+}
+
+static int vbc_codec_soc_probe(struct snd_soc_codec *codec)
+{
+ struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec);
+ struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting;
+ int ret = 0;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ codec->dapm.idle_bias_off = 1;
+
+ vbc_codec->codec = codec;
+ p_eq_setting->codec = codec;
+
+ vbc_proc_init(codec);
+
+ return ret;
+}
+
+/* power down chip */
+static int vbc_codec_soc_remove(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_vbc_codec = {
+ .probe = vbc_codec_soc_probe,
+ .remove = vbc_codec_soc_remove,
+ .read = vbc_codec_read,
+ .write = vbc_codec_write,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .dapm_widgets = vbc_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(vbc_codec_dapm_widgets),
+ .dapm_routes = vbc_codec_intercon,
+ .num_dapm_routes = ARRAY_SIZE(vbc_codec_intercon),
+ .controls = vbc_codec_snd_controls,
+ .num_controls = ARRAY_SIZE(vbc_codec_snd_controls),
+};
+
+static int sprd_vbc_codec_probe(struct platform_device *pdev)
+{
+ struct vbc_codec_priv *vbc_codec;
+ int ret;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ vbc_codec = devm_kzalloc(&pdev->dev, sizeof(struct vbc_codec_priv),
+ GFP_KERNEL);
+ if (vbc_codec == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, vbc_codec);
+
+ ret = snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_vbc_codec, NULL, 0);
+ if (ret != 0) {
+ pr_err("ERR:Failed to register VBC-CODEC: %d\n", ret);
+ }
+
+ /* AP refer to array vbc_switch_reg_val */
+ vbc_codec->vbc_control = 2;
+ vbc_codec->vbc_eq_setting.dev = &pdev->dev;
+ vbc_codec->st_dg.dg_val[0] = 0x18;
+ vbc_codec->st_dg.dg_val[1] = 0x18;
+ vbc_codec->st_dg.hpf_val[0] = 0x3;
+ vbc_codec->st_dg.hpf_val[1] = 0x3;
+ vbc_codec->dg[0].dg_val[0] = 0x18;
+ vbc_codec->dg[0].dg_val[1] = 0x18;
+ vbc_codec->dg[1].dg_val[0] = 0x18;
+ vbc_codec->dg[1].dg_val[1] = 0x18;
+ vbc_codec->dg[2].dg_val[0] = 0x18;
+ vbc_codec->dg[2].dg_val[1] = 0x18;
+ vbc_codec->dg[0].dg_set[0] = vbc_da0_dg_set;
+ vbc_codec->dg[0].dg_set[1] = vbc_da1_dg_set;
+ vbc_codec->dg[1].dg_set[0] = vbc_ad0_dg_set;
+ vbc_codec->dg[1].dg_set[1] = vbc_ad1_dg_set;
+ vbc_codec->dg[2].dg_set[0] = vbc_ad2_dg_set;
+ vbc_codec->dg[2].dg_set[1] = vbc_ad3_dg_set;
+ mutex_init(&vbc_codec->load_mutex);
+
+ return ret;
+}
+
+static int sprd_vbc_codec_remove(struct platform_device *pdev)
+{
+ struct vbc_codec_priv *vbc_codec = platform_get_drvdata(pdev);
+ vbc_codec->vbc_eq_setting.dev = 0;
+
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_DESCRIPTION("SPRD ASoC VBC Codec Driver");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_AUTHOR("Ken.Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("codec:VBC Codec");
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h
new file mode 100644
index 00000000..6a973e71
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h
@@ -0,0 +1,30 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h
+ *
+ * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_CODEC_H
+#define __VBC_CODEC_H
+
+#include "vbc-comm.h"
+
+#define VBC_EQ_FIRMWARE_MAGIC_LEN (4)
+#define VBC_EQ_FIRMWARE_MAGIC_ID ("VBEQ")
+#define VBC_EQ_PROFILE_VERSION (0x00000002)
+#define VBC_EQ_PROFILE_CNT_MAX (100)
+#define VBC_EQ_PROFILE_NAME_MAX (32)
+#define VBC_DA_EFFECT_PARAS_LEN (20+72*2)
+#define VBC_AD_EFFECT_PARAS_LEN (2+ 43*2)
+
+#endif /* __VBC_CODEC_H */
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
new file mode 100644
index 00000000..739a1908
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
@@ -0,0 +1,374 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function.
+ *
+ * Copyright (C) 2012 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "sprd-asoc-common.h"
+#include "vbc-comm.h"
+
+static DEFINE_SPINLOCK(vbc_lock);
+
+/* vbc local power suppliy and chan on */
+static struct vbc_refcount {
+ atomic_t vbc_power_on; /*vbc reg power enable */
+ atomic_t vbc_on; /*vbc enable */
+ atomic_t chan_on[VBC_IDX_MAX][2];
+} vbc_refcnt;
+
+struct sprd_vbc_src_reg_info {
+ int reg;
+ int clr_bit;
+ int f1f2f3_bp_bit;
+ int f1_sel_bit;
+ int en_bit;
+};
+
+struct sprd_vbc_src_info {
+ struct sprd_vbc_src_reg_info reg_info;
+};
+
+static struct sprd_vbc_src_info vbc_src[VBC_IDX_MAX] = {
+ {{0}},
+ {{ADCSRCCTL, VBADCSRC_CLR_01, VBADCSRC_F1F2F3_BP_01, VBADCSRC_F1_SEL_01,
+ VBADCSRC_EN_01}},
+ {{ADCSRCCTL, VBADCSRC_CLR_23, VBADCSRC_F1F2F3_BP_23, VBADCSRC_F1_SEL_23,
+ VBADCSRC_EN_23}},
+};
+
+static const char *sprd_vbc_name[VBC_IDX_MAX] = {
+ "DAC",
+ "ADC01",
+ "ADC23",
+};
+
+static inline const char *vbc_get_name(int vbc_idx)
+{
+ return sprd_vbc_name[vbc_idx];
+}
+
+static inline int vbc_reg_read(unsigned int reg)
+{
+ return __raw_readl((void *__iomem)(reg - VBC_BASE + sprd_vbc_base));
+}
+
+static inline void vbc_reg_raw_write(unsigned int reg, int val)
+{
+ __raw_writel(val, (void *__iomem)(reg - VBC_BASE + sprd_vbc_base));
+}
+
+static int vbc_reg_write(unsigned int reg, int val)
+{
+ spin_lock(&vbc_lock);
+ vbc_reg_raw_write(reg, val);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("VBC:[0x%04x] W:[0x%08x] R:[0x%08x]\n", (reg - VBC_BASE) & 0xFFFF, val,
+ vbc_reg_read(reg));
+ return 0;
+}
+
+/*
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static int vbc_reg_update(unsigned int reg, int val, int mask)
+{
+ int new, old;
+ spin_lock(&vbc_lock);
+ old = vbc_reg_read(reg);
+ new = (old & ~mask) | (val & mask);
+ vbc_reg_raw_write(reg, new);
+ spin_unlock(&vbc_lock);
+ sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new,
+ vbc_reg_read(reg));
+ return old != new;
+}
+
+static struct clk *s_vbc_clk = 0;
+static void vbc_clk_set(struct clk *clk)
+{
+ s_vbc_clk = clk;
+}
+
+static struct clk *vbc_clk_get(void)
+{
+ return s_vbc_clk;
+}
+
+static inline void vbc_reg_enable(void)
+{
+ int ret = 0;
+ if (0/*s_vbc_clk*/) {
+ ret = clk_prepare(s_vbc_clk);
+ WARN(ret != 0, "clk_vbc prepare failed, return %d", ret );
+ ret = clk_enable(s_vbc_clk);
+ WARN(ret != 0, "clk_vbc enable failed, return %d", ret );
+ } else {
+ arch_audio_vbc_reg_enable();
+ }
+}
+
+static inline void vbc_reg_disable(void)
+{
+ if (0/*s_vbc_clk*/) {
+ clk_disable(s_vbc_clk);
+ clk_unprepare(s_vbc_clk);
+ } else {
+ arch_audio_vbc_reg_disable();
+ }
+}
+
+static int vbc_power(int enable)
+{
+ int i;
+ atomic_t *vbc_power_on = &vbc_refcnt.vbc_power_on;
+ if (enable) {
+ atomic_inc(vbc_power_on);
+ if (atomic_read(vbc_power_on) == 1) {
+ arch_audio_vbc_enable();
+ vbc_reg_enable();
+ for (i = 0; i < SPRD_VBC_PLAYBACK_COUNT; i++) {
+ vbc_da_buffer_clear_all(i);
+ }
+ arch_audio_sleep_xtl_enable();
+ arch_audio_memory_sleep_enable();
+ sp_asoc_pr_dbg("VBC Power On\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_power_on)) {
+ arch_audio_vbc_reset();
+ arch_audio_vbc_disable();
+ vbc_reg_disable();
+ arch_audio_sleep_xtl_disable();
+ arch_audio_memory_sleep_disable();
+ sp_asoc_pr_dbg("VBC Power Off\n");
+ }
+ if (atomic_read(vbc_power_on) < 0) {
+ atomic_set(vbc_power_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("VBC Power REF: %d", atomic_read(vbc_power_on));
+ return 0;
+}
+
+static inline int vbc_da_enable_raw(int enable, int chan)
+{
+ vbc_reg_update(VBCHNEN, ((enable ? 1 : 0) << (VBDACHEN_SHIFT + chan)),
+ (1 << (VBDACHEN_SHIFT + chan)));
+ return 0;
+}
+
+static inline int vbc_ad_enable_raw(int enable, int chan)
+{
+ if (enable) {
+ vbc_reg_update(VBCHNEN, (1 << (VBADCHEN_SHIFT + chan)),
+ (1 << (VBADCHEN_SHIFT + chan)));
+ } else {
+ pr_info("Not close ad%d chan!", (chan ? 1:0));
+ }
+
+ return 0;
+}
+
+static inline int vbc_ad23_enable_raw(int enable, int chan)
+{
+ if (enable) {
+ vbc_reg_update(VBCHNEN, (1 << (VBAD23CHEN_SHIFT + chan)),
+ (1 << (VBAD23CHEN_SHIFT + chan)));
+ } else {
+ pr_info("Not close ad%d chan!", (chan ? 3:2));
+ }
+ return 0;
+}
+
+static inline void vbc_da_eq6_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ6_EN) : 0),
+ BIT(VBDAC_EQ6_EN));
+}
+
+static inline void vbc_da_eq4_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ4_EN) : 0),
+ BIT(VBDAC_EQ4_EN));
+}
+
+static inline void vbc_da_alc_enable(int enable)
+{
+ vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_ALC_EN) : 0),
+ BIT(VBDAC_ALC_EN));
+}
+
+static inline void vbc_ad01_eq6_enable(int enable)
+{
+ vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC01_EQ6_EN) : 0),
+ BIT(VBADC01_EQ6_EN));
+}
+
+static inline void vbc_ad23_eq6_enable(int enable)
+{
+ vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC23_EQ6_EN) : 0),
+ BIT(VBADC23_EQ6_EN));
+}
+
+static inline int vbc_enable_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBENABLE),
+ (1 << VBENABLE));
+ return 0;
+}
+
+typedef int (*vbc_chan_enable_raw) (int enable, int chan);
+static vbc_chan_enable_raw vbc_chan_enable_fun[VBC_IDX_MAX] = {
+ vbc_da_enable_raw,
+ vbc_ad_enable_raw,
+ vbc_ad23_enable_raw,
+};
+
+static int vbc_chan_enable(int enable, int vbc_idx, int chan)
+{
+ atomic_t *chan_on = &vbc_refcnt.chan_on[vbc_idx][chan];
+ if (enable) {
+ atomic_inc(chan_on);
+ if (atomic_read(chan_on) == 1) {
+ vbc_chan_enable_fun[vbc_idx] (1, chan);
+ sp_asoc_pr_dbg("VBC %s%d On\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ } else {
+ if (atomic_dec_and_test(chan_on)) {
+ vbc_chan_enable_fun[vbc_idx] (0, chan);
+ sp_asoc_pr_dbg("VBC %s%d Off\n", vbc_get_name(vbc_idx),
+ chan);
+ }
+ if (atomic_read(chan_on) < 0) {
+ atomic_set(chan_on, 0);
+ }
+ }
+ sp_asoc_pr_dbg("%s%d REF: %d", vbc_get_name(vbc_idx), chan,
+ atomic_read(chan_on));
+ return 0;
+}
+
+static DEFINE_MUTEX(vbc_mutex);
+static int vbc_enable(int enable)
+{
+ atomic_t *vbc_on = &vbc_refcnt.vbc_on;
+ mutex_lock(&vbc_mutex);
+ if (enable) {
+ atomic_inc(vbc_on);
+ if (atomic_read(vbc_on) == 1) {
+#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ vbc_da_eq6_enable(1);
+ vbc_da_alc_enable(1);
+#endif
+ /*todo?? */
+ /*vbc_da_eq4_enable(1); */
+ /*vbc_ad01_eq6_enable(1); */
+ /*vbc_ad23_eq6_enable(1); */
+ vbc_enable_set(1);
+ sp_asoc_pr_dbg("VBC Enable\n");
+ }
+ } else {
+ if (atomic_dec_and_test(vbc_on)) {
+ vbc_enable_set(0);
+#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ
+ vbc_da_eq6_enable(0);
+ vbc_da_alc_enable(0);
+#endif
+ /*vbc_da_eq4_enable(0); */
+ /*vbc_ad01_eq6_enable(0); */
+ /*vbc_ad23_eq6_enable(0); */
+ sp_asoc_pr_dbg("VBC Disable");
+ }
+ if (atomic_read(vbc_on) < 0) {
+ atomic_set(vbc_on, 0);
+ }
+ }
+ mutex_unlock(&vbc_mutex);
+
+ sp_asoc_pr_dbg("VBC EN REF: %d", atomic_read(vbc_on));
+ return 0;
+}
+
+static int vbc_src_set(int rate, int vbc_idx)
+{
+ int f1f2f3_bp;
+ int f1_sel;
+ int en_sel;
+ int val;
+ int mask;
+ struct sprd_vbc_src_reg_info *reg_info = &vbc_src[vbc_idx].reg_info;
+
+ if (!vbc_src[vbc_idx].reg_info.reg) {
+ return -EINVAL;
+ }
+
+ sp_asoc_pr_dbg("Rate:%d, Chan: %s", rate, vbc_get_name(vbc_idx));
+
+ /*src_clr */
+ vbc_reg_update(vbc_src[vbc_idx].reg_info.reg,
+ (1 << reg_info->clr_bit), (1 << reg_info->clr_bit));
+ udelay(10);
+ vbc_reg_update(reg_info->reg, 0, (1 << reg_info->clr_bit));
+
+ switch (rate) {
+ case 32000:
+ f1f2f3_bp = 0;
+ f1_sel = 1;
+ en_sel = 1;
+ break;
+ case 48000:
+ f1f2f3_bp = 0;
+ f1_sel = 0;
+ en_sel = 1;
+ break;
+ case 44100:
+ f1f2f3_bp = 1;
+ f1_sel = 0;
+ en_sel = 1;
+ break;
+ default:
+ f1f2f3_bp = 0;
+ f1_sel = 0;
+ en_sel = 0;
+ break;
+ }
+
+ /*src_set */
+ mask = (1 << reg_info->f1f2f3_bp_bit)
+ | (1 << reg_info->f1_sel_bit)
+ | (1 << reg_info->en_bit);
+
+ val = (f1f2f3_bp << reg_info->f1f2f3_bp_bit)
+ | (f1_sel << reg_info->f1_sel_bit)
+ | (en_sel << reg_info->en_bit);
+
+ vbc_reg_update(reg_info->reg, val, mask);
+
+ return 0;
+}
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h
new file mode 100644
index 00000000..50c69258
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h
@@ -0,0 +1,299 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_COMM_H
+#define __VBC_COMM_H
+
+#include <soc/sprd/sprd-audio.h>
+
+#define VBC_VERSION "vbc.r2p0"
+
+/* VBADBUFFDTA */
+enum {
+ VBISADCK_INV = 9,
+ VBISDACK_INV,
+ VBISAD23CK_INV,
+ VBIIS_DLOOP = 13,
+};
+/* VBDABUFFDTA */
+enum {
+ RAMSW_NUMB = 9,
+ RAMSW_EN,
+ VBAD0DMA_EN,
+ VBAD1DMA_EN,
+ VBDA0DMA_EN,
+ VBDA1DMA_EN,
+ VBENABLE,
+};
+/* VBDAPATH FM MIXER*/
+enum {
+ DAPATH_NO_MIX = 0,
+ DAPATH_ADD_FM,
+ DAPATH_SUB_FM,
+};
+
+/* VBADPATH ST INMUX*/
+enum {
+ ST_INPUT_NORMAL = 0,
+ ST_INPUT_INVERSE,
+ ST_INPUT_ZERO,
+};
+
+/* AD DGMUX NUM*/
+enum {
+ ADC0_DGMUX = 0,
+ ADC1_DGMUX,
+ ADC2_DGMUX,
+ ADC3_DGMUX,
+ ADC_DGMUX_MAX
+};
+
+/* -------------------------- */
+
+#define PHYS_VBDA0 (VBC_PHY_BASE + 0x0000) /* 0x0000 Voice band DAC0 data buffer */
+#define PHYS_VBDA1 (VBC_PHY_BASE + 0x0004) /* 0x0004 Voice band DAC1 data buffer */
+#define PHYS_VBAD0 (VBC_PHY_BASE + 0x0008) /* 0x0008 Voice band ADC0 data buffer */
+#define PHYS_VBAD1 (VBC_PHY_BASE + 0x000C) /* 0x000C Voice band ADC1 data buffer */
+#define PHYS_VBAD2 (VBC_PHY_BASE + 0x00C0) /* 0x00C0 Voice band ADC2 data buffer */
+#define PHYS_VBAD3 (VBC_PHY_BASE + 0x00C4) /* 0x00C4 Voice band ADC3 data buffer */
+
+#define ARM_VB_BASE VBC_BASE
+#define VBDA0 (ARM_VB_BASE + 0x0000) /* Voice band DAC0 data buffer */
+#define VBDA1 (ARM_VB_BASE + 0x0004) /* Voice band DAC1 data buffer */
+#define VBAD0 (ARM_VB_BASE + 0x0008) /* Voice band ADC0 data buffer */
+#define VBAD1 (ARM_VB_BASE + 0x000C) /* Voice band ADC1 data buffer */
+#define VBBUFFSIZE (ARM_VB_BASE + 0x0010) /* Voice band buffer size */
+#define VBADBUFFDTA (ARM_VB_BASE + 0x0014) /* Voice band AD buffer control */
+#define VBDABUFFDTA (ARM_VB_BASE + 0x0018) /* Voice band DA buffer control */
+#define VBADCNT (ARM_VB_BASE + 0x001C) /* Voice band AD buffer counter */
+#define VBDACNT (ARM_VB_BASE + 0x0020) /* Voice band DA buffer counter */
+#define VBAD23CNT (ARM_VB_BASE + 0x0024) /* Voice band AD23 buffer counter */
+#define VBADDMA (ARM_VB_BASE + 0x0028) /* Voice band AD23 DMA control */
+#define VBBUFFAD23 (ARM_VB_BASE + 0x002C) /* Voice band AD23 buffer size */
+#define VBINTTYPE (ARM_VB_BASE + 0x0034)
+#define VBDATASWT (ARM_VB_BASE + 0x0038)
+#define VBIISSEL (ARM_VB_BASE + 0x003C)
+
+#define DAPATCHCTL (ARM_VB_BASE + 0x0040)
+#define DADGCTL (ARM_VB_BASE + 0x0044)
+#define DAHPCTL (ARM_VB_BASE + 0x0048)
+#define DAALCCTL0 (ARM_VB_BASE + 0x004C)
+#define DAALCCTL1 (ARM_VB_BASE + 0x0050)
+#define DAALCCTL2 (ARM_VB_BASE + 0x0054)
+#define DAALCCTL3 (ARM_VB_BASE + 0x0058)
+#define DAALCCTL4 (ARM_VB_BASE + 0x005C)
+#define DAALCCTL5 (ARM_VB_BASE + 0x0060)
+#define DAALCCTL6 (ARM_VB_BASE + 0x0064)
+#define DAALCCTL7 (ARM_VB_BASE + 0x0068)
+#define DAALCCTL8 (ARM_VB_BASE + 0x006C)
+#define DAALCCTL9 (ARM_VB_BASE + 0x0070)
+#define DAALCCTL10 (ARM_VB_BASE + 0x0074)
+#define STCTL0 (ARM_VB_BASE + 0x0078)
+#define STCTL1 (ARM_VB_BASE + 0x007C)
+#define ADPATCHCTL (ARM_VB_BASE + 0x0080)
+#define ADDG01CTL (ARM_VB_BASE + 0x0084)
+#define ADDG23CTL (ARM_VB_BASE + 0x0088)
+#define ADHPCTL (ARM_VB_BASE + 0x008C)
+#define ADCSRCCTL (ARM_VB_BASE + 0x0090)
+#define DACSRCCTL (ARM_VB_BASE + 0x0094)
+#define MIXERCTL (ARM_VB_BASE + 0x0098)
+#define STFIFOLVL (ARM_VB_BASE + 0x009C)
+#define VBIRQEN (ARM_VB_BASE + 0x00A0)
+#define VBIRQCLR (ARM_VB_BASE + 0x00A4)
+#define VBIRQRAW (ARM_VB_BASE + 0x00A8)
+#define VBIRQSTS (ARM_VB_BASE + 0x00AC)
+#define VBNGCVTHD (ARM_VB_BASE + 0x00B0)
+#define VBNGCTTHD (ARM_VB_BASE + 0x00B4)
+#define VBNGCTL (ARM_VB_BASE + 0x00B8)
+
+/* DA HPF EQ6 start, end */
+#define HPCOEF0_H (ARM_VB_BASE + 0x0100)
+#define HPCOEF0_L (ARM_VB_BASE + 0x0104)
+#define HPCOEF35_H (ARM_VB_BASE + 0x0218)
+#define HPCOEF35_L (ARM_VB_BASE + 0x021c)
+#define HPCOEF42_H (ARM_VB_BASE + 0x0250)
+#define HPCOEF42_L (ARM_VB_BASE + 0x0254)
+/* DA HPF EQ4 start, end*/
+#define HPCOEF43_H (ARM_VB_BASE + 0x0258)
+#define HPCOEF43_L (ARM_VB_BASE + 0x025C)
+#define HPCOEF64_H (ARM_VB_BASE + 0x0300)
+#define HPCOEF64_L (ARM_VB_BASE + 0x0304)
+#define HPCOEF71_H (ARM_VB_BASE + 0x0338)
+#define HPCOEF71_L (ARM_VB_BASE + 0x033C)
+
+/* AD01 HPF EQ6 start, end */
+#define AD01_HPCOEF0_H (ARM_VB_BASE + 0x0400)
+#define AD01_HPCOEF0_L (ARM_VB_BASE + 0x0404)
+#define AD01_HPCOEF35_H (ARM_VB_BASE + 0x0418)
+#define AD01_HPCOEF35_L (ARM_VB_BASE + 0x041c)
+#define AD01_HPCOEF42_H (ARM_VB_BASE + 0x0550)
+#define AD01_HPCOEF42_L (ARM_VB_BASE + 0x0554)
+/* AD23 HPF EQ6 start, end */
+#define AD23_HPCOEF0_H (ARM_VB_BASE + 0x0600)
+#define AD23_HPCOEF0_L (ARM_VB_BASE + 0x0604)
+#define AD23_HPCOEF35_H (ARM_VB_BASE + 0x0618)
+#define AD23_HPCOEF35_L (ARM_VB_BASE + 0x061c)
+#define AD23_HPCOEF42_H (ARM_VB_BASE + 0x0750)
+#define AD23_HPCOEF42_L (ARM_VB_BASE + 0x0754)
+
+#define ARM_VB_END (ARM_VB_BASE + 0x0758)
+#define IS_SPRD_VBC_RANG(reg) (((reg) >= ARM_VB_BASE) && ((reg) < ARM_VB_END))
+#define SPRD_VBC_BASE_HI (ARM_VB_BASE & 0xFFFF0000)
+
+#define VBADBUFFERSIZE_SHIFT (0)
+#define VBADBUFFERSIZE_MASK (0xFF<<VBADBUFFERSIZE_SHIFT)
+#define VBDABUFFERSIZE_SHIFT (8)
+#define VBDABUFFERSIZE_MASK (0xFF<<VBDABUFFERSIZE_SHIFT)
+#define VBAD23BUFFERSIZE_SHIFT (0)
+#define VBAD23BUFFERSIZE_MASK (0xFF<<VBAD23BUFFERSIZE_SHIFT)
+
+#define VBCHNEN (ARM_VB_BASE + 0x00C8)
+#define VBDA0_EN (0)
+#define VBDA1_EN (1)
+#define VBAD0_EN (2)
+#define VBAD1_EN (3)
+#define VBAD2_EN (4)
+#define VBAD3_EN (5)
+
+#define VBDACHEN_SHIFT (0)
+#define VBADCHEN_SHIFT (2)
+#define VBAD23CHEN_SHIFT (4)
+
+#define VBAD2DMA_EN (0)
+#define VBAD3DMA_EN (1)
+
+ /* VBIISSEL */
+#define VBIISSEL_AD01_PORT_SHIFT (0)
+#define VBIISSEL_AD01_PORT_MASK (0x7)
+#define VBIISSEL_AD23_PORT_SHIFT (3)
+#define VBIISSEL_AD23_PORT_MASK (0x7<<VBIISSEL_AD23_PORT_SHIFT)
+#define VBIISSEL_DA_PORT_SHIFT (6)
+#define VBIISSEL_DA_PORT_MASK (0x7<<VBIISSEL_DA_PORT_SHIFT)
+#define VBIISSEL_DA_LRCK (14)
+#define VBIISSEL_AD01_LRCK (13)
+ /* VBDATASWT */
+#define VBDATASWT_AD23_LRCK (6)
+ /* DAPATHCTL */
+#define VBDAPATH_DA0_ADDFM_SHIFT (0)
+#define VBDAPATH_DA0_ADDFM_MASK (0x3<<VBDAPATH_DA0_ADDFM_SHIFT)
+#define VBDAPATH_DA1_ADDFM_SHIFT (2)
+#define VBDAPATH_DA1_ADDFM_MASK (0x3<<VBDAPATH_DA1_ADDFM_SHIFT)
+#define VBDAPATH_DA0_ADDST_SHIFT (4)
+#define VBDAPATH_DA0_ADDST_MASK (0x3<<VBDAPATH_DA0_ADDST_SHIFT)
+#define VBDAPATH_DA1_ADDST_SHIFT (6)
+#define VBDAPATH_DA1_ADDST_MASK (0x3<<VBDAPATH_DA1_ADDST_SHIFT)
+
+ /* ADPATHCTL */
+#define VBADPATH_AD0_INMUX_SHIFT (0)
+#define VBADPATH_AD0_INMUX_MASK (0x3<<VBADPATH_AD0_INMUX_SHIFT)
+#define VBADPATH_AD1_INMUX_SHIFT (2)
+#define VBADPATH_AD1_INMUX_MASK (0x3<<VBADPATH_AD1_INMUX_SHIFT)
+#define VBADPATH_AD2_INMUX_SHIFT (4)
+#define VBADPATH_AD2_INMUX_MASK (0x3<<VBADPATH_AD2_INMUX_SHIFT)
+#define VBADPATH_AD3_INMUX_SHIFT (6)
+#define VBADPATH_AD3_INMUX_MASK (0x3<<VBADPATH_AD3_INMUX_SHIFT)
+#define VBADPATH_AD0_DGMUX_SHIFT (8)
+#define VBADPATH_AD0_DGMUX_MASK (0x1<<VBADPATH_AD0_DGMUX_SHIFT)
+#define VBADPATH_AD1_DGMUX_SHIFT (9)
+#define VBADPATH_AD1_DGMUX_MASK (0x1<<VBADPATH_AD1_DGMUX_SHIFT)
+#define VBADPATH_AD2_DGMUX_SHIFT (10)
+#define VBADPATH_AD2_DGMUX_MASK (0x1<<VBADPATH_AD2_DGMUX_SHIFT)
+#define VBADPATH_AD3_DGMUX_SHIFT (11)
+#define VBADPATH_AD3_DGMUX_MASK (0x1<<VBADPATH_AD3_DGMUX_SHIFT)
+#define VBADPATH_ST0_INMUX_SHIFT (12)
+#define VBADPATH_ST0_INMUX_MASK (0x3<<VBADPATH_ST0_INMUX_SHIFT)
+#define VBADPATH_ST1_INMUX_SHIFT (14)
+#define VBADPATH_ST1_INMUX_MASK (0x3<<VBADPATH_ST1_INMUX_SHIFT)
+ /* DACSRCCTL */
+#define VBDACSRC_EN (0)
+#define VBDACSRC_CLR (1)
+#define VBDACSRC_F1F2F3_BP (3)
+#define VBDACSRC_F1_SEL (4)
+#define VBDACSRC_F0_BP (5)
+#define VBDACSRC_F0_SEL (6)
+ /* ADCSRCCTL */
+#define VBADCSRC_EN_01 (0)
+#define VBADCSRC_CLR_01 (1)
+#define VBADCSRC_PAUSE_01 (2)
+#define VBADCSRC_F1F2F3_BP_01 (3)
+#define VBADCSRC_F1_SEL_01 (4)
+#define VBADCSRC_EN_23 (8)
+#define VBADCSRC_CLR_23 (9)
+#define VBADCSRC_PAUSE_23 (10)
+#define VBADCSRC_F1F2F3_BP_23 (11)
+#define VBADCSRC_F1_SEL_23 (12)
+/* STCTL0 */
+#define VBST_EN_0 (12)
+#define VBST_HPF_0 (11)
+/* STCTL1 */
+#define VBST_HPF_1 (11)
+#define VBST_EN_1 (12)
+#define VBST0_SEL_CHN (13)
+#define VBST1_SEL_CHN (14)
+ /* DAHPCTL */
+#define VBDAEQ_HP_REG_CLR (9)
+#define VBDAC_EQ6_EN (10)
+#define VBDAC_ALC_EN (11)
+#define VBDAC_EQ4_EN (12)
+#define VBDAC_EQ4_POS_SEL (13)
+#define VBDAC_ALC_DP_T_MODE (14)
+ /* ADHPCTL */
+#define VBADC01_EQ6_EN (0)
+#define VBADC23_EQ6_EN (1)
+#define VBADC01EQ_HP_REG_CLR (2)
+#define VBADC23EQ_HP_REG_CLR (3)
+
+enum {
+ VBC_LEFT = 0,
+ VBC_RIGHT = 1,
+};
+
+enum {
+ VBC_PLAYBACK = 0,
+ SPRD_VBC_PLAYBACK_COUNT = 1,
+ VBC_CAPTRUE = 1,
+ VBC_CAPTRUE1 = 2,
+ VBC_IDX_MAX
+};
+
+static const char *vbc_get_name(int vbc_idx);
+
+static int vbc_reg_read(unsigned int reg);
+static void vbc_reg_raw_write(unsigned int reg, int val);
+static int vbc_reg_write(unsigned int reg, int val);
+static int vbc_reg_update(unsigned int reg, int val, int mask);
+static void vbc_da_buffer_clear_all(int vbc_idx);
+static int vbc_enable(int enable);
+static void vbc_clk_set(struct clk *clk);
+static struct clk *vbc_clk_get(void);
+static int vbc_power(int enable);
+static int vbc_chan_enable(int enable, int vbc_idx, int chan);
+static int vbc_src_set(int rate, int vbc_idx);
+static int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai);
+static int fm_set_vbc_buffer_size(void);
+static int vbc_set_phys_addr(int vbc_switch);
+
+static inline void vbc_safe_mem_release(void **free)
+{
+ if (*free) {
+ kfree(*free);
+ *free = NULL;
+ }
+}
+
+#define vbc_safe_kfree(p) vbc_safe_mem_release((void**)p)
+
+#endif /* __VBC_COMM_H */
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc.c b/sound/soc/sprd/dai/vbc/r2p0/vbc.c
new file mode 100644
index 00000000..84a45b67
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc.c
@@ -0,0 +1,807 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" VBC ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#ifndef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE
+#include "sprd-pcm.h"
+#else
+#include "sprd-dmaengine-pcm.h"
+#endif
+#include "vbc.h"
+#include "dfm.h"
+
+typedef int (*vbc_dma_set) (int enable);
+
+struct sprd_vbc_buffer_info {
+ int reg;
+ int shift;
+ int mask;
+ int max;
+};
+
+struct sprd_vbc_priv {
+ vbc_dma_set dma_set[2];
+ int (*arch_enable) (int chan);
+ int (*arch_disable) (int chan);
+ int used_chan_count;
+ struct sprd_vbc_buffer_info buf_info;
+ int rate;
+};
+
+struct sprd_dfm_priv dfm = { 0, 0 };
+
+EXPORT_SYMBOL_GPL(dfm);
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_out = {
+ .name = "VBC PCM Stereo out",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 2,
+ .des_step = 0,
+ },
+};
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_in = {
+ .name = "VBC PCM Stereo in",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 0,
+ .des_step = 2,
+ },
+};
+
+static struct sprd_pcm_dma_params vbc_pcm23_stereo_in = {
+ .name = "VBC PCM23 Stereo in",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 0,
+ .des_step = 2,
+ },
+};
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX];
+static unsigned long sprd_vbc_base = 0;
+static unsigned int sprd_vbc_phy_base = 0;
+
+static int vbc_iis_high_for_da1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_DA_LRCK,
+ 1 << VBIISSEL_DA_LRCK);
+ return 0;
+}
+
+#if 0
+static int vbc_iis_high_for_ad1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_AD01_LRCK,
+ 1 << VBIISSEL_AD01_LRCK);
+ return 0;
+}
+
+static int vbc_iis_high_for_ad2(int enable)
+{
+ vbc_reg_update(VBDATASWT, (enable ? 1 : 0) << VBDATASWT_AD23_LRCK,
+ 1 << VBDATASWT_AD23_LRCK);
+ return 0;
+}
+#endif
+
+static int vbc_set_buffer_size(int vbc_idx, int buffer_size)
+{
+ int val = vbc_reg_read(vbc[vbc_idx].buf_info.reg);
+ WARN_ON(buffer_size > vbc[vbc_idx].buf_info.max);
+ if ((buffer_size > 0)
+ && (buffer_size <= vbc[vbc_idx].buf_info.max)) {
+ val &= ~(vbc[vbc_idx].buf_info.mask);
+ val |= (((buffer_size - 1) << vbc[vbc_idx].buf_info.shift)
+ & vbc[vbc_idx].buf_info.mask);
+ vbc_reg_update(vbc[vbc_idx].buf_info.reg, val,
+ vbc[vbc_idx].buf_info.mask);
+ } else {
+ pr_err("ERR:buffer_size error! %d\n", buffer_size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fm_set_vbc_buffer_size(void)
+{
+ int i;
+ for (i = SPRD_VBC_PLAYBACK_COUNT; i < VBC_IDX_MAX; i++) {
+ vbc_set_buffer_size(i, VBC_FIFO_FRAME_NUM);
+ }
+ return 0;
+}
+
+static inline int vbc_sw_write_buffer(int enable)
+{
+ /* Software access ping-pong buffer enable when VBENABE bit low */
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << RAMSW_EN),
+ (1 << RAMSW_EN));
+ return 0;
+}
+
+static inline int vbc_ad0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD0DMA_EN),
+ (1 << VBAD0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_ad1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD1DMA_EN),
+ (1 << VBAD1DMA_EN));
+ return 0;
+}
+
+static inline int vbc_ad2_dma_set(int enable)
+{
+ vbc_reg_update(VBADDMA, ((enable ? 1 : 0) << VBAD2DMA_EN),
+ (1 << VBAD2DMA_EN));
+ return 0;
+}
+
+static inline int vbc_ad3_dma_set(int enable)
+{
+ vbc_reg_update(VBADDMA, ((enable ? 1 : 0) << VBAD3DMA_EN),
+ (1 << VBAD3DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA0DMA_EN),
+ (1 << VBDA0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA1DMA_EN),
+ (1 << VBDA1DMA_EN));
+ return 0;
+}
+
+static void vbc_da_buffer_clear(int id)
+{
+ int i;
+ vbc_reg_update(VBDABUFFDTA, ((id ? 1 : 0) << RAMSW_NUMB),
+ (1 << RAMSW_NUMB));
+ for (i = 0; i < VBC_FIFO_FRAME_NUM; i++) {
+ vbc_reg_raw_write(VBDA0, 0);
+ vbc_reg_raw_write(VBDA1, 0);
+ }
+}
+
+static void vbc_da_buffer_clear_all(int vbc_idx)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ }
+
+ vbc_sw_write_buffer(true);
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_da_buffer_clear(1); /* clear data buffer 1 */
+ vbc_da_buffer_clear(0); /* clear data buffer 0 */
+ vbc_sw_write_buffer(false);
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ }
+}
+
+static int vbc_da_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_PLAYBACK, chan);
+}
+
+static int vbc_da_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_PLAYBACK, chan);
+}
+
+static int vbc_ad_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_CAPTRUE, chan);
+}
+
+static int vbc_ad_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_CAPTRUE, chan);
+}
+
+static int vbc_ad23_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_CAPTRUE1, chan);
+}
+
+static int vbc_ad23_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_CAPTRUE1, chan);
+}
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX] = {
+ /* All Playback */
+ { /*PlayBack */
+ .dma_set = {vbc_da0_dma_set, vbc_da1_dma_set},
+ .arch_enable = vbc_da_arch_enable,
+ .arch_disable = vbc_da_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBDABUFFERSIZE_SHIFT, VBDABUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+
+ /* All Capture */
+ { /*Capture for ad01 */
+ .dma_set = {vbc_ad0_dma_set, vbc_ad1_dma_set},
+ .arch_enable = vbc_ad_arch_enable,
+ .arch_disable = vbc_ad_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBADBUFFERSIZE_SHIFT, VBADBUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+ { /*Capture for ad23 */
+ .dma_set = {vbc_ad2_dma_set, vbc_ad3_dma_set},
+ .arch_enable = vbc_ad23_arch_enable,
+ .arch_disable = vbc_ad23_arch_disable,
+ .buf_info = {VBBUFFAD23, VBAD23BUFFERSIZE_SHIFT, VBAD23BUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+};
+
+/* NOTE:
+ this index need use for the [struct sprd_vbc_priv] vbc[3] index
+ default MUST return 0.
+ */
+static inline int vbc_str_2_index(int stream, int id)
+{
+ if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+ return id + SPRD_VBC_PLAYBACK_COUNT;
+ }
+ return id;
+}
+
+static int vbc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+ sp_asoc_pr_dbg("dfm.hw_rate:%d, dfm.sample_rate:%d", dfm.hw_rate,
+ dfm.sample_rate);
+
+ if (dfm.sample_rate != 0) {
+ if ((vbc_idx == VBC_PLAYBACK) || (vbc_idx == VBC_CAPTRUE)) {
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ dfm.sample_rate,
+ dfm.sample_rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+ }
+ }
+
+ vbc_power(1);
+
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_codec_startup(vbc_idx, dai);
+
+ WARN_ON(!vbc[vbc_idx].arch_enable);
+ WARN_ON(!vbc[vbc_idx].arch_disable);
+ WARN_ON(!vbc[vbc_idx].dma_set[0]);
+ WARN_ON(!vbc[vbc_idx].dma_set[1]);
+
+ return 0;
+}
+
+static void vbc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ /* vbc da close MUST clear da buffer */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ vbc_da_buffer_clear_all(vbc_idx);
+ }
+
+ vbc_power(0);
+}
+
+static int vbc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ struct sprd_pcm_dma_params *dma_data[VBC_IDX_MAX] = {
+ &vbc_pcm_stereo_out,
+ &vbc_pcm_stereo_in,
+ &vbc_pcm23_stereo_in,
+ };
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data[vbc_idx]);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ vbc[vbc_idx].used_chan_count = params_channels(params);
+ if (vbc[vbc_idx].used_chan_count > 2) {
+ pr_err("ERR:VBC Can NOT Supports Grate 2 Channels\n");
+ }
+
+ vbc_iis_high_for_da1(1);
+#ifdef CONFIG_SND_SOC_SPRD_VBC_LR_INVERT
+ if (vbc[vbc_idx].used_chan_count == 2) {
+ vbc_iis_high_for_da1(0);
+ }
+#endif
+
+#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (params_rate(params) == 44100) {
+ if ((vbc_idx == 1 && dfm.hw_rate == 0) ||(vbc_idx == 2)) {/*dfm is closed for ad01 */
+ vbc_src_set(CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE,
+ vbc_idx);
+ }
+ } else {
+ vbc_src_set(0, vbc_idx); /*close adc src */
+ }
+ }
+#endif
+
+ vbc[vbc_idx].rate = params_rate(params);
+ return 0;
+}
+
+static int vbc_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ vbc[vbc_idx].rate = 0;
+
+ return 0;
+}
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE
+struct sprd_runtime_data {
+ int dma_addr_offset;
+ struct sprd_pcm_dma_params *params;
+ struct dma_chan *dma_chn[2];
+ struct sprd_dma_cfg *dma_cfg_array;
+ dma_addr_t dma_cfg_phy[2];
+ void *dma_cfg_virt[2];
+ struct dma_async_tx_descriptor *dma_tx_des[2];
+ dma_cookie_t cookie[2];
+ int int_pos_update[2];
+
+ //dma_addr_t *dma_desc_array;
+ //dma_addr_t dma_desc_array_phys;
+ int burst_len;
+ int hw_chan;
+ int dma_pos_pre[2];
+ int interleaved;
+ int cb_called;
+ int new_pos;
+
+#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM
+ int buffer_in_iram;
+#endif
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+ struct snd_info_entry *proc_info_entry;
+#endif
+};
+#endif
+
+static int vbc_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret = 0;
+ int i;
+
+#if 0
+ sp_asoc_pr_dbg("%s\n", __func__);
+#endif
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ vbc[vbc_idx].dma_set[i] (1);
+ }
+ vbc_enable(1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ vbc[vbc_idx].dma_set[i] (0);
+ }
+ vbc_enable(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops vbc_dai_ops = {
+ .startup = vbc_startup,
+ .shutdown = vbc_shutdown,
+ .hw_params = vbc_hw_params,
+ .trigger = vbc_trigger,
+ .hw_free = vbc_hw_free,
+};
+
+static int dfm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ static const unsigned int dfm_all_rates[] = { 32000, 44100, 48000, 8000};
+ static const struct snd_pcm_hw_constraint_list dfm_rates_constraint = {
+ .count = ARRAY_SIZE(dfm_all_rates),
+ .list = dfm_all_rates,
+ };
+ int ret;
+ int vbc_idx;
+ struct snd_soc_card *card = dai->card;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n, vbc[vbc_idx].rate:da:%d, ad:%d, ad23:%d",
+ __func__, vbc[0].rate, vbc[1].rate, vbc[2].rate);
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &dfm_rates_constraint);
+ if (ret < 0)
+ return ret;
+
+ for (vbc_idx = VBC_PLAYBACK; vbc_idx < VBC_CAPTRUE1; vbc_idx++) {
+ if (vbc[vbc_idx].rate != 0) {
+#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN
+ if (vbc[vbc_idx].rate != 44100)
+ return -1;
+#else
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ vbc[vbc_idx].rate,
+ vbc[vbc_idx].rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+#endif
+ }
+ }
+
+ kfree(snd_soc_dai_get_dma_data(dai, substream));
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+
+ snd_soc_dapm_enable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 1;
+ }
+ snd_soc_dapm_ignore_suspend(&dai->codec->dapm, "DFM");
+
+ return 0;
+}
+
+static void dfm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = dai->card;
+ int i;
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ snd_soc_dapm_disable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 0;
+ }
+}
+
+static int dfm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int dfm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+ dfm.sample_rate = 0;
+ dfm.hw_rate = 0;
+ return 0;
+}
+
+static struct snd_soc_dai_ops dfm_dai_ops = {
+ .startup = dfm_startup,
+ .shutdown = dfm_shutdown,
+ .hw_params = dfm_hw_params,
+ .hw_free = dfm_hw_free,
+};
+
+static struct snd_soc_dai_driver vbc_dai[] = {
+ {
+ .name = "vbc-r2p0",
+ .id = 0,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2, /* AD01 */
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &vbc_dai_ops,
+ },
+ {
+ .name = "vbc-r2p0-ad23",
+ .id = 1,
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2, /* AD23 */
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &vbc_dai_ops,
+ },
+ {
+ .name = "vbc-dfm",
+ .id = DFM_MAGIC_ID,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &dfm_dai_ops,
+ }
+};
+
+static const struct snd_soc_component_driver sprd_vbc_component = {
+ .name = "vbc",
+};
+
+static int vbc_set_phys_addr(int vbc_switch)
+{
+ if (vbc_switch == AUDIO_TO_CP2_ARM_CTRL) { /*switch to cp2-arm */
+ vbc_pcm_stereo_out.dev_paddr[0] = CP2_PHYS_VBDA0;
+ vbc_pcm_stereo_out.dev_paddr[1] = CP2_PHYS_VBDA1;
+ } else {
+ vbc_pcm_stereo_out.dev_paddr[0] = PHYS_VBDA0 + sprd_vbc_phy_base;
+ vbc_pcm_stereo_out.dev_paddr[1] = PHYS_VBDA1 + sprd_vbc_phy_base;
+ }
+ return 0;
+}
+
+static int sprd_vbc_codec_probe(struct platform_device *pdev);
+static int vbc_drv_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ struct clk *vbc_clk;
+ struct device_node *node = pdev->dev.of_node;
+ unsigned int val_arr[2] = {0};
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ arch_audio_vbc_switch(AUDIO_TO_ARM_CTRL);
+ ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE);
+ if (ret != AUDIO_TO_ARM_CTRL) {
+ pr_err("Failed to Switch VBC to AP\n");
+ return -1;
+ }
+
+ if(node){
+ if (!of_property_read_u32_array(node, "sprd,reg_vbc", &val_arr[0], 2)) {
+ sprd_vbc_base = (unsigned long)ioremap_nocache(val_arr[0], val_arr[1]);
+ if(!sprd_vbc_base) {
+ pr_err("ERR: cannot create iomap address for VBC!\n");
+ return -EINVAL;
+ }
+ sprd_vbc_phy_base = val_arr[0];
+ } else {
+ pr_err("ERR:Must give me the VBC reg address!\n");
+ return -EINVAL;
+ }
+ } else {
+ BUG();
+ /*
+ sprd_vbc_base = ioremap_nocache(VBC_BASE_DEFAULT, VBC_BASE_SIZE_DEFAULT);
+ if(!sprd_vbc_base) {
+ pr_err("ERR: cannot create iomap address for VBC with default address!\n");
+ return -EINVAL;
+ }
+ sprd_vbc_phy_base = VBC_BASE_DEFAULT;
+ */
+ }
+
+ vbc_pcm_stereo_out.dev_paddr[0] = PHYS_VBDA0 + sprd_vbc_phy_base;
+ vbc_pcm_stereo_out.dev_paddr[1] = PHYS_VBDA1 + sprd_vbc_phy_base;
+ vbc_pcm_stereo_in.dev_paddr[0] = PHYS_VBAD0 + sprd_vbc_phy_base;
+ vbc_pcm_stereo_in.dev_paddr[1] = PHYS_VBAD1 + sprd_vbc_phy_base;
+ vbc_pcm23_stereo_in.dev_paddr[0]= PHYS_VBAD2 + sprd_vbc_phy_base;
+ vbc_pcm23_stereo_in.dev_paddr[1]= PHYS_VBAD3 + sprd_vbc_phy_base;
+
+ for (i = 0; i < 2; i++) {
+ vbc_pcm_stereo_out.channels[i] = arch_audio_vbc_da_dma_info(i);
+ vbc_pcm_stereo_in.channels[i] = arch_audio_vbc_ad_dma_info(i);
+ vbc_pcm23_stereo_in.channels[i] =
+ arch_audio_vbc_ad23_dma_info(i);
+ }
+
+ /* 1. probe CODEC */
+ ret = sprd_vbc_codec_probe(pdev);
+
+ if (ret < 0) {
+ goto probe_err;
+ }
+
+ /* 2. probe DAIS */
+ ret =
+ snd_soc_register_component(&pdev->dev, &sprd_vbc_component, vbc_dai,
+ ARRAY_SIZE(vbc_dai));
+
+ if (ret < 0) {
+ pr_err("ERR:Register VBC to DAIS Failed!\n");
+ goto probe_err;
+ }
+
+ vbc_clk = clk_get(&pdev->dev, "clk_vbc");
+ if (IS_ERR(vbc_clk)) {
+ pr_err("Cannot request clk_vbc\n");
+ } else {
+ vbc_clk_set(vbc_clk);
+ }
+
+ return ret;
+probe_err:
+ iounmap(sprd_vbc_base);
+ sp_asoc_pr_dbg("return %i\n", ret);
+
+ return ret;
+}
+
+static int sprd_vbc_codec_remove(struct platform_device *pdev);
+static int vbc_drv_remove(struct platform_device *pdev)
+{
+ struct clk *vbc_clk = vbc_clk_get();
+
+ if (vbc_clk) {
+ clk_put(vbc_clk);
+ vbc_clk_set(0);
+ }
+
+ snd_soc_unregister_component(&pdev->dev);
+
+ sprd_vbc_codec_remove(pdev);
+
+ iounmap(sprd_vbc_base);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id vbc_of_match[] = {
+ {.compatible = "sprd,vbc-r2p0",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, vbc_of_match);
+#endif
+
+static struct platform_driver vbc_driver = {
+ .driver = {
+ .name = "vbc-r2p0",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vbc_of_match),
+ },
+
+ .probe = vbc_drv_probe,
+ .remove = vbc_drv_remove,
+};
+
+static int __init sprd_vbc_driver_init(void)
+{
+ return platform_driver_register(&vbc_driver);
+}
+
+late_initcall(sprd_vbc_driver_init);
+
+/* include the other module of VBC */
+#include "vbc-comm.c"
+#include "vbc-codec.c"
+
+MODULE_DESCRIPTION("SPRD ASoC VBC CUP-DAI driver");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cpu-dai:vbc-r2p0");
diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc.h b/sound/soc/sprd/dai/vbc/r2p0/vbc.h
new file mode 100644
index 00000000..4949fb7e
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r2p0/vbc.h
@@ -0,0 +1,22 @@
+/*
+ * sound/soc/sprd/dai/vbc/r2p0/vbc.h
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __VBC_H
+#define __VBC_H
+
+#include "vbc-comm.h"
+
+#endif /* __VBC_H */