summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/dai/vbc/r1p0
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /sound/soc/sprd/dai/vbc/r1p0
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'sound/soc/sprd/dai/vbc/r1p0')
-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
8 files changed, 2716 insertions, 0 deletions
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 */