diff options
Diffstat (limited to 'sound/soc/sprd/dai/vbc/r2p0')
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/Kconfig | 20 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/Makefile | 6 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c | 2596 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h | 30 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c | 374 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h | 299 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc.c | 807 | ||||
| -rw-r--r-- | sound/soc/sprd/dai/vbc/r2p0/vbc.h | 22 |
8 files changed, 4154 insertions, 0 deletions
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 */ |
