diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /sound/soc/sprd | |
Diffstat (limited to 'sound/soc/sprd')
64 files changed, 27948 insertions, 0 deletions
diff --git a/sound/soc/sprd/Kconfig b/sound/soc/sprd/Kconfig new file mode 100644 index 00000000..c002b52e --- /dev/null +++ b/sound/soc/sprd/Kconfig @@ -0,0 +1,59 @@ +menu "SoC Audio for the SPREADTRUM" +config SND_SOC_SPRD + bool "sprd audio base" + +config SND_SOC_SPRD_VBC_R1P0_SPRD_CODEC_V1 + tristate "VBC(R1P0)CODEC(V1)" + select SND_SOC_SPRD_VBC_R1P0 + select SND_SOC_SPRD_VAUDIO + select SND_SOC_SPRD_AUDIO_DMA + select SND_SOC_SPRD_CODEC_V1 + help + Say Y if you want to built in to kernel, Say M for ko module. + use VBC(R1P0) and SPRD CODEC(V1) for the audio system. + +config SND_SOC_SPRD_VBC_R2P0_SPRD_CODEC_V3 + tristate "VBC(R2P0)CODEC(V3)" + select SND_SOC_SPRD_VBC_R2P0 + select SND_SOC_SPRD_VAUDIO + select SND_SOC_SPRD_AUDIO_DMA + select SND_SOC_SPRD_CODEC_V3 + help + Say Y if you want to built in to kernel, Say M for ko module. + use VBC(R2P0) and SPRD CODEC(V3) for the audio system. + +config SND_SOC_SPRD_VBC_R2P0_SPRD_CODEC_V4 + tristate "VBC(R2P0)CODEC(V4)" + select SND_SOC_SPRD_VBC_R2P0 + select SND_SOC_SPRD_VAUDIO + select SND_SOC_SPRD_AUDIO_DMA + select SND_SOC_SPRD_CODEC_V4 + help + Say Y if you want to built in to kernel, Say M for ko module. + use VBC(R2P0) and SPRD CODEC(V4) for the audio system. + +config SND_SOC_SPRD_I2S_V0_NULL_CODEC + tristate "I2S(V0)NC" + select SND_SOC_SPRD_I2S + select SND_SOC_SPRD_AUDIO_DMA + select SND_SOC_NULL_CODEC + help + Say Y if you want to built in to kernel, Say M for ko module. + this option will build spreadtrum I2S driver for external codec + or like BT/Digital FM etc. + +config SND_SOC_SPRD_AUDIO_DEBUG + bool "sprd audio debug" + help + Say Y if defined this will include more + print debug information for detail. + Or, maybe could reduce the code size. + +source "sound/soc/sprd/dai/Kconfig" +source "sound/soc/sprd/dai/vbc/Kconfig" +source "sound/soc/sprd/dai/vaudio/Kconfig" +source "sound/soc/sprd/dai/i2s/Kconfig" +source "sound/soc/sprd/codec/sprd/Kconfig" +source "sound/soc/sprd/codec/null-codec/Kconfig" + +endmenu diff --git a/sound/soc/sprd/Makefile b/sound/soc/sprd/Makefile new file mode 100644 index 00000000..ace33ad0 --- /dev/null +++ b/sound/soc/sprd/Makefile @@ -0,0 +1,14 @@ +# SPRD Platform Support +snd-soc-i2s-objs := i2s-r0p0-null-codec.o +obj-$(CONFIG_SND_SOC_SPRD_I2S_V0_NULL_CODEC) += snd-soc-i2s.o +obj-$(CONFIG_SND_SOC_SPRD) += sprd-asoc-common.o sprd-asoc-card.o + +obj-$(CONFIG_SND_SOC_SPRD_AUDIO_DMA) += dai/ +obj-$(CONFIG_SND_SOC_SPRD_VBC_R1P0) += dai/vbc/r1p0/ +obj-$(CONFIG_SND_SOC_SPRD_VBC_R2P0) += dai/vbc/r2p0/ +obj-$(CONFIG_SND_SOC_SPRD_VAUDIO) += dai/vaudio/ +obj-$(CONFIG_SND_SOC_SPRD_I2S) += dai/i2s/ +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V1) += codec/sprd/v1/ +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V3) += codec/sprd/v3/ +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V4) += codec/sprd/v4/ +obj-$(CONFIG_SND_SOC_NULL_CODEC) += codec/null-codec/ diff --git a/sound/soc/sprd/codec/null-codec/Kconfig b/sound/soc/sprd/codec/null-codec/Kconfig new file mode 100644 index 00000000..22c2e902 --- /dev/null +++ b/sound/soc/sprd/codec/null-codec/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_NULL_CODEC + tristate # "null codec" diff --git a/sound/soc/sprd/codec/null-codec/Makefile b/sound/soc/sprd/codec/null-codec/Makefile new file mode 100644 index 00000000..d6806e1d --- /dev/null +++ b/sound/soc/sprd/codec/null-codec/Makefile @@ -0,0 +1,6 @@ + +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/codec + +snd-soc-sprd-null-codec-objs := null-codec.o +obj-$(CONFIG_SND_SOC_NULL_CODEC) += snd-soc-sprd-null-codec.o diff --git a/sound/soc/sprd/codec/null-codec/null-codec.c b/sound/soc/sprd/codec/null-codec/null-codec.c new file mode 100644 index 00000000..0a8a9a0a --- /dev/null +++ b/sound/soc/sprd/codec/null-codec/null-codec.c @@ -0,0 +1,110 @@ +/* + * sound/soc/sprd/codec/null-codec/null-codec.c + * + * NULL-CODEC -- SpreadTrum just for codec code. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt("NULCD") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/soc.h> + +#include "sprd-asoc-common.h" +#include "null-codec.h" + +/* PCM Playing and Recording default in full duplex mode */ +struct snd_soc_dai_driver null_codec_dai[] = { + { + .name = "null-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_null_codec = { 0 }; + +static int null_codec_codec_probe(struct platform_device *pdev) +{ + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_null_codec, null_codec_dai, + ARRAY_SIZE(null_codec_dai)); + if (ret != 0) { + pr_err("ERR:Failed to register NULL CODEC: %d\n", ret); + return ret; + } + + return 0; + +} + +static int null_codec_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id codec_of_match[] = { + {.compatible = "sprd,null-codec",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, codec_of_match); +#endif + +static struct platform_driver null_codec_codec_driver = { + .driver = { + .name = "null-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(codec_of_match), + }, + .probe = null_codec_codec_probe, + .remove = null_codec_codec_remove, +}; + +module_platform_driver(null_codec_codec_driver); + +MODULE_DESCRIPTION("NULL-CODEC ALSA SoC codec driver"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:null-codec"); diff --git a/sound/soc/sprd/codec/null-codec/null-codec.h b/sound/soc/sprd/codec/null-codec/null-codec.h new file mode 100644 index 00000000..de413f0c --- /dev/null +++ b/sound/soc/sprd/codec/null-codec/null-codec.h @@ -0,0 +1,20 @@ +/* + * sound/soc/sprd/codec/null-codec/null-codec.h + * + * NULL-CODEC -- SpreadTrum just for codec code. + * + * 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 __NULL_CODEC_H +#define __NULL_CODEC_H + +#endif /* __NULL_CODEC_H */ diff --git a/sound/soc/sprd/codec/sprd/Kconfig b/sound/soc/sprd/codec/sprd/Kconfig new file mode 100644 index 00000000..1661b5e9 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/Kconfig @@ -0,0 +1,30 @@ +menu "SPRD Codec" + +source "sound/soc/sprd/codec/sprd/v1/Kconfig" +source "sound/soc/sprd/codec/sprd/v3/Kconfig" +source "sound/soc/sprd/codec/sprd/v4/Kconfig" + +config SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK + bool "Earpiece with in Speaker" + help + Say Y if your device is use ear&spk in one, + and ear have TDD noise when in call mode. + this feature should to open. + +config SND_SOC_SPRD_CODEC_NO_HP_POP + bool "Codec no HP POP" + help + Say Y if you want *NOT* the CODEC do hp pop ramp up/down process. + some customer use the external hp amplifier will need this feature, + Or will cause hp pop noise serious. + +config SND_SOC_SPRD_HP_POP_DELAY_TIME + int "HP Switch delay time(0-1000) unit(MS)" + depends on SND_SOC_SPRD_CODEC_NO_HP_POP + range 0 1000 + default 350 + help + if defined CODEC_NO_HP_POP , maybe need this delay + for reduce external hp amplifier noise. + this delay time value, unit is MS. +endmenu diff --git a/sound/soc/sprd/codec/sprd/v1/Kconfig b/sound/soc/sprd/codec/sprd/v1/Kconfig new file mode 100644 index 00000000..72fa453d --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_SPRD_CODEC_V1 + tristate # "Sprd-codec-v1" diff --git a/sound/soc/sprd/codec/sprd/v1/Makefile b/sound/soc/sprd/codec/sprd/v1/Makefile new file mode 100644 index 00000000..e092d558 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/Makefile @@ -0,0 +1,7 @@ +#SPRD CODEC + +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/codec + +snd-soc-sprd-sprd-codec-v1-objs := sprd-codec.o sprd-audio-power.o +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V1) += snd-soc-sprd-sprd-codec-v1.o diff --git a/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.c b/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.c new file mode 100755 index 00000000..ea29f53e --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.c @@ -0,0 +1,614 @@ +/* + * sound/soc/sprd/codec/sprd/v1/sprd-audio-power.c + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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("POWER") fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#include <mach/sprd-audio.h> +#include "sprd-audio-power.h" +#include "sprd-asoc-common.h" + +#ifdef CONFIG_AUDIO_POWER_ALLOW_UNSUPPORTED +#define UNSUP_MASK 0x0000 +#else +#define UNSUP_MASK 0x8000 +#endif + +#define UNSUP(x) (UNSUP_MASK | (x)) +#define IS_UNSUP(x) (UNSUP_MASK & (x)) +#define LDO_MV(x) (~UNSUP_MASK & (x)) + +struct sprd_audio_power_info { + int id; + /* enable/disable register information */ + int en_reg; + int en_bit; + int (*power_enable) (void); + int (*power_disable) (void); + /* configure deepsleep cut off or not */ + int (*sleep_ctrl) (void *, int); + /* voltage level register information */ + int v_reg; + int v_mask; + int v_shift; + int v_table_len; + const u16 *v_table; + int min_uV; + int max_uV; + + /* unit: us */ + int on_delay; + int off_delay; + + /* used by regulator core */ + struct regulator_desc desc; + + void *data; +}; + +#define SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, d_id, \ +e_reg, e_bit, p_enable, p_disable, s_ctrl, \ +reg, mask, shift, table, table_size, d_on_delay, d_off_delay) \ +static struct regulator_consumer_supply label[] = { \ + { .supply = #label },\ +}; \ +static struct regulator_init_data label##_data = { \ + .supply_regulator = r_supply, \ + .constraints = { \ + .name = "SRG_" #label, \ + }, \ + .num_consumer_supplies = ARRAY_SIZE(label), \ + .consumer_supplies = label, \ +}; \ +static struct sprd_audio_power_info SPRD_AUDIO_POWER_INFO_##label = { \ + .id = d_id, \ + .en_reg = e_reg, \ + .en_bit = e_bit, \ + .power_enable = p_enable, \ + .power_disable = p_disable, \ + .sleep_ctrl = s_ctrl, \ + .v_reg = reg, \ + .v_mask = mask, \ + .v_shift = shift, \ + .v_table_len = table_size, \ + .v_table = table, \ + .on_delay = d_on_delay, \ + .off_delay = d_off_delay, \ + .desc = { \ + .name = #label, \ + .id = SPRD_AUDIO_POWER_##label, \ + .n_voltages = table_size, \ + .ops = &sprd_audio_power_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ +} + +#define SPRD_AUDIO_POWER_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, ARRAY_SIZE(v_table), on_delay, off_delay) + +#define SPRD_AUDIO_POWER_SIMPLE_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + 0, 0, 0, 0, 0, on_delay, off_delay) + +#define SPRD_AUDIO_POWER_REG_LDO(label, id, power_enable, power_disable) \ +SPRD_AUDIO_POWER_LDO_LOW(label, 0, id, 0, 0, power_enable, power_disable, \ + 0, 0, 0, 0, 0, 0, 0, 0) + +static inline int sprd_power_read(struct sprd_audio_power_info *info, int reg) +{ + return arch_audio_power_read(reg); +} + +static inline int +sprd_power_write(struct sprd_audio_power_info *info, int reg, int val) +{ + int ret = 0; + ret = arch_audio_power_write(reg, val); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + arch_audio_power_read(reg)); + return ret; +} + +static inline int +sprd_power_u_bits(struct sprd_audio_power_info *info, + unsigned int reg, unsigned int mask, unsigned int value) +{ + int ret = 0; + ret = arch_audio_power_write_mask(reg, value, mask); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, value, + arch_audio_power_read(reg)); + return ret; +} + +static int sprd_audio_power_is_enabled(struct regulator_dev *rdev) +{ + int is_enable; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Is Enabled\n", rdev->desc->name); + + if (info->en_reg) { + is_enable = sprd_power_read(info, info->en_reg) & info->en_bit; + } else { + is_enable = info->en_bit; + } + + return ! !is_enable; +} + +static int audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + BUG_ON(!info->v_table); + + for (vsel = 0; vsel < info->v_table_len; vsel++) { + int mV = info->v_table[vsel]; + int uV; + + if (IS_UNSUP(mV)) + continue; + uV = LDO_MV(mV) * 1000; + + /* use the first in-range value */ + if (min_uV <= uV && uV <= max_uV) { + sp_asoc_pr_dbg("%s: %dus\n", __func__, uV); + sprd_power_u_bits(info, + info->v_reg, + info->v_mask << info->v_shift, + vsel << info->v_shift); + + return vsel; + } + } + return -EDOM; +} + +static int sprd_audio_power_enable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Enable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, + info->en_reg, info->en_bit, + info->en_bit); + } else { + if (info->power_enable) + ret = info->power_enable(); + info->en_bit = 1; + } + + udelay(info->on_delay); + + if (info->v_table) { + if (info->min_uV || info->max_uV) + audio_power_set_voltage(rdev, info->min_uV, + info->max_uV); + } + return ret; +} + +static int sprd_audio_power_disable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Disable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, info->en_reg, info->en_bit, 0); + } else { + if (info->power_disable) + ret = info->power_disable(); + info->en_bit = 0; + } + + udelay(info->off_delay); + + return ret; +} + +static int sprd_audio_power_get_status(struct regulator_dev *rdev) +{ + sp_asoc_pr_dbg("%s Get Status\n", rdev->desc->name); + + if (!sprd_audio_power_is_enabled(rdev)) + return REGULATOR_STATUS_OFF; + return REGULATOR_STATUS_NORMAL; +} + +static int sprd_audio_power_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + int ret = 0; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Set Mode 0x%x\n", rdev->desc->name, mode); + + if (!info->sleep_ctrl) { + return ret; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = info->sleep_ctrl(info, 0); + break; + case REGULATOR_MODE_STANDBY: + ret = info->sleep_ctrl(info, 1); + break; + default: + return -EINVAL; + } + return ret; +} + +static const u16 VCOM_VSEL_table[] = { + 2900, 3100, 3200, 3300, + 3400, 3500, 3600, 3800, +}; + +static const u16 VPA_VSEL_table[] = { + 2900, 3100, 3200, 3300, + 3400, 3500, 3600, 3800, +}; + +static const u16 VMIC_VSEL_table[] = { + 2100, 1900, 2300, 2500, +}; + +static const u16 VBG_IBAIS_VSEL_table[] = { + 150, 200, 250, 300, +}; + +static int sprd_audio_power_list_voltage(struct regulator_dev *rdev, + unsigned index) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int mV; + + sp_asoc_pr_dbg("%s List Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + if (index >= info->v_table_len) { + return 0; + } + + mV = info->v_table[index]; + return IS_UNSUP(mV) ? 0 : (LDO_MV(mV) * 1000); +} + +static int +sprd_audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_info("%s Set Voltage %d--%d\n", rdev->desc->name, min_uV, + max_uV); + + if (!info->v_table) { + return 0; + } + info->min_uV = min_uV; + info->max_uV = max_uV; + + vsel = audio_power_set_voltage(rdev, min_uV, max_uV); + if (vsel != -EDOM) + *selector = vsel; + + return vsel; +} + +static int sprd_audio_power_get_voltage(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_dbg("%s Get Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + vsel = (sprd_power_read(info, info->v_reg) & info->v_mask) + >> info->v_shift; + return LDO_MV(info->v_table[vsel]) * 1000; +} + +static struct regulator_ops sprd_audio_power_ops = { + .list_voltage = sprd_audio_power_list_voltage, + + .set_voltage = sprd_audio_power_set_voltage, + .get_voltage = sprd_audio_power_get_voltage, + + .enable = sprd_audio_power_enable, + .disable = sprd_audio_power_disable, + .is_enabled = sprd_audio_power_is_enabled, + + .set_mode = sprd_audio_power_set_mode, + + .get_status = sprd_audio_power_get_status, +}; + +static inline int vreg_enable(void) +{ + int ret = 0; + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + arch_audio_codec_analog_reg_enable(); + arch_audio_codec_analog_reset(); + return ret; +} + +static inline int vreg_disable(void) +{ + arch_audio_codec_reset(); + arch_audio_codec_analog_reg_disable(); + return 0; +} + +/* + * We list regulators here if systems need some level of + * software control over them after boot. + */ +SPRD_AUDIO_POWER_REG_LDO(VREG, 1, vreg_enable, vreg_disable); +SPRD_AUDIO_POWER_SIMPLE_LDO(VB, "VREG", 2, PMUR1, BIT(VB_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(BG, "VB", 3, PMUR1, BIT(BG_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(BG_IBIAS, "BG", 4, PMUR1, BIT(BG_IBIAS_EN), NULL, 0, + 0); +SPRD_AUDIO_POWER_LDO(VCOM, "BG_IBIAS", 5, PMUR1, BIT(VCM_EN), NULL, PMUR3, + VCM_V_MASK, VCM_V, VCOM_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VCOM_BUF, "VCOM", 6, PMUR1, BIT(VCM_BUF_EN), NULL, + 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VBO, "VB", 7, PMUR1, BIT(VBO_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(MICBIAS, "VCOM", 8, PMUR1, BIT(MICBIAS_EN), NULL, + PMUR3, MICBIAS_V_MASK, MICBIAS_V, VMIC_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_LDO(AUXMICBIAS, "VCOM", 9, PMUR1, BIT(AUXMICBIAS_EN), NULL, + PMUR3, AUXMICBIAS_V_MASK, AUXMICBIAS_V, VMIC_VSEL_table, 0, + 0); + +#define SPRD_OF_MATCH(comp, label) \ + { \ + .compatible = comp, \ + .data = &SPRD_AUDIO_POWER_INFO_##label, \ + } + +static const struct of_device_id sprd_audio_power_of_match[] = { + SPRD_OF_MATCH("sp,audio-vreg", VREG), + SPRD_OF_MATCH("sp,audio-vb", VB), + SPRD_OF_MATCH("sp,audio-bg", BG), + SPRD_OF_MATCH("sp,audio-bg-ibias", BG_IBIAS), + SPRD_OF_MATCH("sp,audio-vcom", VCOM), + SPRD_OF_MATCH("sp,audio-vcom-buf", VCOM_BUF), + SPRD_OF_MATCH("sp,audio-vbo", VBO), + SPRD_OF_MATCH("sp,audio-micbias", MICBIAS), + SPRD_OF_MATCH("sp,audio-auxmicbias", AUXMICBIAS), +}; + +static int sprd_audio_power_probe(struct platform_device *pdev) +{ + int i, id; + struct sprd_audio_power_info *info; + const struct sprd_audio_power_info *template; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + struct regulator_config config = { }; + int min_index = 0; + int max_index = 0; + + id = pdev->id; + + sp_asoc_pr_info("Probe %d\n", id); + + initdata = pdev->dev.platform_data; + for (i = 0, template = NULL; i < ARRAY_SIZE(sprd_audio_power_of_match); + i++) { + template = sprd_audio_power_of_match[i].data; + if (!template || template->desc.id != id) + continue; + break; + } + + if (!template) + return -ENODEV; + + if (!initdata) + return -EINVAL; + + info = kmemdup(template, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask |= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS; + + if (info->v_table) { + for (i = 0; i < info->v_table_len; i++) { + if (info->v_table[i] < info->v_table[min_index]) + min_index = i; + if (info->v_table[i] > info->v_table[max_index]) + max_index = i; + } + c->min_uV = LDO_MV((info->v_table)[min_index]) * 1000; + c->max_uV = LDO_MV((info->v_table)[max_index]) * 1000; + sp_asoc_pr_info("min_uV:%d, max_uV:%d\n", c->min_uV, c->max_uV); + } + info->min_uV = 0; + info->max_uV = 0; + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = info; + config.of_node = pdev->dev.of_node; + + rdev = regulator_register(&info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "ERR:Can't Register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + kfree(info); + return PTR_ERR(rdev); + } + sp_asoc_pr_info("Register %s Success!\n", info->desc.name); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int sprd_audio_power_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct twlreg_info *info = rdev->reg_data; + + regulator_unregister(platform_get_drvdata(pdev)); + kfree(info); + return 0; +} + +static struct platform_driver sprd_audio_power_driver = { + .driver = { + .name = "sprd-audio-power", + .owner = THIS_MODULE, + }, + .probe = sprd_audio_power_probe, + .remove = sprd_audio_power_remove, +}; + +static int __init sprd_audio_power_regulator_init(void) +{ + return platform_driver_register(&sprd_audio_power_driver); +} + +postcore_initcall(sprd_audio_power_regulator_init); + +static void __exit sprd_audio_power_regulator_exit(void) +{ + platform_driver_unregister(&sprd_audio_power_driver); +} + +module_exit(sprd_audio_power_regulator_exit); + +MODULE_DESCRIPTION("SPRD audio power regulator driver"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_ALIAS("platform:sprd-audio-power"); +MODULE_LICENSE("GPL"); + +#define SPRD_AUDIO_DEVICE(label) \ +{ \ + .name = "sprd-audio-power", \ + .id = SPRD_AUDIO_POWER_##label, \ + .dev = { \ + .platform_data = &label##_data, \ + }, \ +} + +static struct platform_device sprd_audio_regulator_devices[] = { + SPRD_AUDIO_DEVICE(VREG), + SPRD_AUDIO_DEVICE(VB), + SPRD_AUDIO_DEVICE(BG), + SPRD_AUDIO_DEVICE(BG_IBIAS), + SPRD_AUDIO_DEVICE(VCOM), + SPRD_AUDIO_DEVICE(VCOM_BUF), + SPRD_AUDIO_DEVICE(VBO), + SPRD_AUDIO_DEVICE(MICBIAS), + SPRD_AUDIO_DEVICE(AUXMICBIAS), +}; + +#ifdef CONFIG_OF +static int sprd_audio_power_version(void) +{ + struct device_node *node = of_find_node_by_path("/sprd-audio-devices"); + struct device_node *child; + if (node) { + u32 val; + for_each_child_of_node(node, child) { + if (!of_property_read_u32 + (child, "sprd,audio_power_ver", &val)) { + sp_asoc_pr_dbg + ("Configure Audio Power Version is %d\n", + val); + return val; + } + } + pr_err("ERR:Not found sprd,audio_power_ver!\n"); + } else { + pr_err("ERR:No sprd-audio-devices Node!\n"); + } + return 0; +} +#else +int sprd_audio_power_version(void) + __attribute__ ((weak, alias("__sprd_audio_power_version"))); + +static int __sprd_audio_power_version(void) +{ + return 1; +} +#endif + +static int sprd_audio_power_add_devices(void) +{ + int ret = 0; + int i; + if (1 != sprd_audio_power_version()) { + return 0; + } + for (i = 0; i < ARRAY_SIZE(sprd_audio_regulator_devices); i++) { + ret = + platform_device_register(&sprd_audio_regulator_devices[i]); + if (ret < 0) { + pr_err("ERR:Add Regulator %d Failed %d\n", + sprd_audio_regulator_devices[i].id, ret); + return ret; + } + } + return ret; +} + +static int __init sprd_audio_power_init(void) +{ + return sprd_audio_power_add_devices(); +} + +postcore_initcall(sprd_audio_power_init); diff --git a/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.h b/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.h new file mode 100644 index 00000000..6bd73bb4 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/sprd-audio-power.h @@ -0,0 +1,33 @@ +/* + * sound/soc/sprd/codec/sprd/v1/sprd-audio-power.h + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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. + */ + +#ifndef __SPRD_AUDIO_POWER_H +#define __SPRD_AUDIO_POWER_H + +#include "sprd-codec.h" + +#define SPRD_AUDIO_POWER_VREG 1 +#define SPRD_AUDIO_POWER_VB 2 +#define SPRD_AUDIO_POWER_BG 3 +#define SPRD_AUDIO_POWER_BG_IBIAS 4 +#define SPRD_AUDIO_POWER_VCOM 5 +#define SPRD_AUDIO_POWER_VCOM_BUF 6 +#define SPRD_AUDIO_POWER_VBO 7 +#define SPRD_AUDIO_POWER_MICBIAS 8 +#define SPRD_AUDIO_POWER_AUXMICBIAS 9 + +#endif diff --git a/sound/soc/sprd/codec/sprd/v1/sprd-codec.c b/sound/soc/sprd/codec/sprd/v1/sprd-codec.c new file mode 100644 index 00000000..0fb6de91 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/sprd-codec.c @@ -0,0 +1,3006 @@ +/* + * sound/soc/sprd/codec/sprd/v1/sprd-codec.c + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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("CDCV1") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/atomic.h> +#include <linux/regulator/consumer.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/power_supply.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/sprd-audio-hook.h> + +#include <mach/sprd-audio.h> +#include <mach/globalregs.h> +#include "sprd-codec.h" +#include "sprd-asoc-common.h" + +#define SOC_REG(r) ((unsigned short)(r)) +#define FUN_REG(f) ((unsigned short)(-((f) + 1))) +#define ID_FUN(id, lr) ((unsigned short)(((id) << 1) | (lr))) + +#define SPRD_CODEC_AP_BASE_HI (SPRD_CODEC_AP_BASE & 0xFFFF0000) +#define SPRD_CODEC_DP_BASE_HI (SPRD_CODEC_DP_BASE & 0xFFFF0000) + +enum { + SPRD_CODEC_PLAYBACK, + SPRD_CODEC_CAPTRUE, + SPRD_CODEC_CHAN_MAX, +}; + +static const char *sprd_codec_chan_name[SPRD_CODEC_CHAN_MAX] = { + "DAC", + "ADC", +}; + +static inline const char *sprd_codec_chan_get_name(int chan_id) +{ + return sprd_codec_chan_name[chan_id]; +} + +enum { + SPRD_CODEC_PGA_SPKL = 0, + SPRD_CODEC_PGA_SPKR, + SPRD_CODEC_PGA_HPL, + SPRD_CODEC_PGA_HPR, + SPRD_CODEC_PGA_EAR, + SPRD_CODEC_PGA_ADCL, + SPRD_CODEC_PGA_ADCR, + + SPRD_CODEC_PGA_MAX +}; + +static const char *sprd_codec_pga_debug_str[SPRD_CODEC_PGA_MAX] = { + "SPKL", + "SPKR", + "HPL", + "HPR", + "EAR", + "ADCL", + "ADCR", +}; + +typedef int (*sprd_codec_pga_set) (struct snd_soc_codec * codec, int pgaval); + +struct sprd_codec_pga { + sprd_codec_pga_set set; + int min; +}; + +struct sprd_codec_pga_op { + int pgaval; + sprd_codec_pga_set set; +}; + +enum { + SPRD_CODEC_LEFT = 0, + SPRD_CODEC_RIGHT = 1, +}; + +enum { + SPRD_CODEC_MIXER_START = 0, + SPRD_CODEC_AIL = SPRD_CODEC_MIXER_START, + SPRD_CODEC_AIR, + SPRD_CODEC_MAIN_MIC, + SPRD_CODEC_AUX_MIC, + SPRD_CODEC_HP_MIC, + SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACL = SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACR, + SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCL = SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCR, + SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACL = SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACR, + SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCL = SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCR, + SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_DACL = SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_MIXER_MAX, + + SPRD_CODEC_MIXER_MAX = SPRD_CODEC_EAR_MIXER_MAX << SPRD_CODEC_RIGHT +}; + +static const char *sprd_codec_mixer_debug_str[SPRD_CODEC_MIXER_MAX] = { + "AIL->ADCL", + "AIL->ADCR", + "AIR->ADCL", + "AIR->ADCR", + "MAIN MIC->ADCL", + "MAIN MIC->ADCR", + "AUX MIC->ADCL", + "AUX MIC->ADCR", + "HP MIC->ADCL", + "HP MIC->ADCR", + "DACL->HPL", + "DACL->HPR", + "DACR->HPL", + "DACR->HPR", + "ADCL->HPL", + "ADCL->HPR", + "ADCR->HPL", + "ADCR->HPR", + "DACL->SPKL", + "DACL->SPKR", + "DACR->SPKL", + "DACR->SPKR", + "ADCL->SPKL", + "ADCL->SPKR", + "ADCR->SPKL", + "ADCR->SPKR", + "DACL->EAR", + "DACR->EAR(bug)" +}; + +#define IS_SPRD_CODEC_MIXER_RANG(reg) (((reg) >= SPRD_CODEC_MIXER_START) && ((reg) <= SPRD_CODEC_MIXER_MAX)) + +typedef int (*sprd_codec_mixer_set) (struct snd_soc_codec * codec, int on); +struct sprd_codec_mixer { + int on; + sprd_codec_mixer_set set; +}; + +uint32_t sprd_get_vbat_voltage(void) + __attribute__ ((weak, alias("__sprd_get_vbat_voltage"))); +static uint32_t __sprd_get_vbat_voltage(void) +{ + pr_err("ERR: Can't get vbat!\n"); + return 3800; +} + +struct sprd_codec_ldo_v_map { + int ldo_v_level; + int volt; +}; + +const static struct sprd_codec_ldo_v_map ldo_v_map[] = { + /*{ , 3400}, */ + {LDO_V_29, 3600}, + {LDO_V_31, 3700}, + {LDO_V_32, 3800}, + {LDO_V_33, 3900}, + {LDO_V_34, 4000}, + {LDO_V_35, 4100}, + {LDO_V_36, 4200}, + {LDO_V_38, 4300}, +}; + +const static struct sprd_codec_ldo_v_map ldo_vcom_v_map[] = { + /*{ , 3400}, */ + {LDO_V_29, 3800}, + {LDO_V_31, 3900}, + {LDO_V_32, 4000}, + {LDO_V_33, 4100}, + {LDO_V_34, 4200}, + {LDO_V_35, 4300}, + {LDO_V_36, 4400}, + {LDO_V_38, 4500}, +}; + +struct sprd_codec_ldo_cfg { + const struct sprd_codec_ldo_v_map *v_map; + int v_map_size; +}; + +struct sprd_codec_inter_pa { + /* FIXME little endian */ + int LDO_V_sel:4; + int DTRI_F_sel:4; + int is_DEMI_mode:1; + int is_classD_mode:1; + int is_LDO_mode:1; + int is_auto_LDO_mode:1; + int RESV:20; +}; + +struct sprd_codec_pa_setting { + union { + struct sprd_codec_inter_pa setting; + u32 value; + }; + int set; +}; + +enum { + SPRD_CODEC_MIC_BIAS, + SPRD_CODEC_AUXMIC_BIAS, + SPRD_CODEC_MIC_BIAS_MAX +}; + +static const char *mic_bias_name[SPRD_CODEC_MIC_BIAS_MAX] = { + "Mic Bias", + "AuxMic Bias", +}; + +/* codec private data */ +struct sprd_codec_priv { + struct snd_soc_codec *codec; + atomic_t adie_dac_refcount; + atomic_t adie_adc_refcount; + int da_sample_val; + int ad_sample_val; + struct sprd_codec_mixer mixer[SPRD_CODEC_MIXER_MAX]; + struct sprd_codec_pga_op pga[SPRD_CODEC_PGA_MAX]; + int mic_bias[SPRD_CODEC_MIC_BIAS_MAX]; + + int ap_irq; + struct completion completion_hp_pop; + + int dp_irq; + struct completion completion_dac_mute; + struct power_supply audio_ldo; + + struct sprd_codec_pa_setting inter_pa; + struct mutex inter_pa_mutex; + + struct regulator *main_mic; + struct regulator *aux_mic; + atomic_t ldo_refcount; + struct delayed_work ovp_delayed_work; + + struct sprd_codec_ldo_cfg s_vcom; + struct sprd_codec_ldo_cfg s_vpa; + +#define SPRD_CODEC_PA_SW_AOL (BIT(0)) +#define SPRD_CODEC_PA_SW_EAR (BIT(1)) +#define SPRD_CODEC_PA_SW_FUN (SPRD_CODEC_PA_SW_AOL | SPRD_CODEC_PA_SW_EAR) + int sprd_codec_fun; + spinlock_t sprd_codec_fun_lock; + spinlock_t sprd_codec_pa_sw_lock; +}; + +static int sprd_codec_power_get(struct device *dev, struct regulator **regu, + const char *id) +{ + if (!*regu) { + *regu = regulator_get(dev, id); + if (IS_ERR(*regu)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(*regu), id); + *regu = 0; + return PTR_ERR(*regu); + } + } + return 0; +} + +static int sprd_codec_power_put(struct regulator **regu) +{ + if (*regu) { + regulator_set_mode(*regu, REGULATOR_MODE_NORMAL); + regulator_disable(*regu); + regulator_put(*regu); + *regu = 0; + } + return 0; +} + +static int sprd_codec_mic_bias_set(struct regulator **regu, int on) +{ + int ret = 0; + if (*regu) { + if (on) { + ret = regulator_enable(*regu); + if (ret) { + sprd_codec_power_put(regu); + return ret; + } + } else { + ret = regulator_disable(*regu); + } + } + return ret; +} + +#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 void sprd_codec_set_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun |= fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static void sprd_codec_clr_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun &= ~fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static int sprd_codec_test_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + int ret; + spin_lock(&sprd_codec->sprd_codec_fun_lock); + ret = sprd_codec->sprd_codec_fun & fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); + return ret; +} + +static void sprd_codec_wait(u32 wait_time) +{ + if (wait_time) + schedule_timeout_uninterruptible(msecs_to_jiffies(wait_time)); +} + +#if 0 /* sometime we will use this print function */ +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg); +static void sprd_codec_print_regs(struct snd_soc_codec *codec) +{ + int reg; + pr_warn("sprd_codec register digital part\n"); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_DP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } + pr_warn("sprd_codec register analog part\n"); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_AP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } +} +#endif + +static inline int _sprd_codec_ldo_cfg_set(struct sprd_codec_ldo_cfg *p_v_cfg, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + p_v_cfg->v_map = v_map; + p_v_cfg->v_map_size = v_map_size; + return 0; +} + +static inline int _sprd_codec_hold(int index, int volt, + const struct sprd_codec_ldo_v_map *v_map) +{ + int scope = 40; /* unit: mv */ + int ret = ((v_map[index].volt - volt) <= scope); + if (index >= 1) { + return (ret || ((volt - v_map[index - 1].volt) <= scope)); + } + return ret; +} + +static int sprd_codec_auto_ldo_volt(struct snd_soc_codec *codec, + void (*set_level) (struct snd_soc_codec *, + int), + const struct sprd_codec_ldo_v_map *v_map, + const int v_map_size, int init) +{ + int i; + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("Get vbat voltage %d\n", volt); + for (i = 0; i < v_map_size; i++) { + if (volt <= ldo_v_map[i].volt) { + sp_asoc_pr_dbg("Hold %d\n", + _sprd_codec_hold(i, volt, v_map)); + if (init || !_sprd_codec_hold(i, volt, v_map)) { + set_level(codec, v_map[i].ldo_v_level); + } + return 0; + } + } + return -EFAULT; +} + +static inline void sprd_codec_vcm_v_sel(struct snd_soc_codec *codec, int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("VCM Set %d\n", v_sel); + mask = VCM_V_MASK << VCM_V; + val = (v_sel << VCM_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR3), mask, val); +} + +static int sprd_codec_pga_spk_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR3; + val = (pgaval & 0xF) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_spkr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR3; + val = pgaval & 0xF; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0F, val); +} + +static int sprd_codec_pga_hpl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR1; + val = (pgaval & 0xF) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_hpr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR1; + val = pgaval & 0xF; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0F, val); +} + +static int sprd_codec_pga_ear_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR2; + val = ((pgaval & 0xF) << 4); + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_adcl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR; + val = (pgaval & 0xF) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_adcr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR; + val = pgaval & 0xF; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0F, val); +} + +static struct sprd_codec_pga sprd_codec_pga_cfg[SPRD_CODEC_PGA_MAX] = { + {sprd_codec_pga_spk_set, 0}, + {sprd_codec_pga_spkr_set, 0}, + {sprd_codec_pga_hpl_set, 0}, + {sprd_codec_pga_hpr_set, 0}, + {sprd_codec_pga_ear_set, 0}, + + {sprd_codec_pga_adcl_set, 0}, + {sprd_codec_pga_adcr_set, 0}, +}; + +/* adc mixer */ + +static int vcmadcl_set(struct snd_soc_codec *codec) +{ + int need_on; + need_on = snd_soc_read(codec, AAICR3) & (BIT(AIL_ADCL) | BIT(AIR_ADCL)); + return snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(VCM_ADCL), + (! !need_on) << VCM_ADCL); +} + +static int vcmadcr_set(struct snd_soc_codec *codec) +{ + int need_on; + need_on = snd_soc_read(codec, AAICR3) & (BIT(AIL_ADCR) | BIT(AIR_ADCR)); + return snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(VCM_ADCR), + (! !need_on) << VCM_ADCR); +} + +static int ailadcl_set(struct snd_soc_codec *codec, int on) +{ + int ret; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + ret = snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(AIL_ADCL), + on << AIL_ADCL); + if (ret < 0) + return ret; + return vcmadcl_set(codec); +} + +static int ailadcr_set(struct snd_soc_codec *codec, int on) +{ + int ret; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + ret = snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(AIL_ADCR), + on << AIL_ADCR); + if (ret < 0) + return ret; + return vcmadcr_set(codec); +} + +static int airadcl_set(struct snd_soc_codec *codec, int on) +{ + int ret; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + ret = snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(AIR_ADCL), + on << AIR_ADCL); + if (ret < 0) + return ret; + return vcmadcl_set(codec); +} + +static int airadcr_set(struct snd_soc_codec *codec, int on) +{ + int ret; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + ret = snd_soc_update_bits(codec, SOC_REG(AAICR3), BIT(AIR_ADCR), + on << AIR_ADCR); + if (ret < 0) + return ret; + return vcmadcr_set(codec); +} + +static int mainmicadcl_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(MICP_ADCL) | BIT(MICN_ADCL); + return snd_soc_update_bits(codec, SOC_REG(AAICR1), mask, on ? mask : 0); +} + +static int mainmicadcr_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(MICP_ADCR) | BIT(MICN_ADCR); + return snd_soc_update_bits(codec, SOC_REG(AAICR1), mask, on ? mask : 0); +} + +static int auxmicadcl_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(AUXMICP_ADCL) | BIT(AUXMICN_ADCL); + return snd_soc_update_bits(codec, SOC_REG(AAICR2), mask, on ? mask : 0); +} + +static int auxmicadcr_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(AUXMICP_ADCR) | BIT(AUXMICN_ADCR); + return snd_soc_update_bits(codec, SOC_REG(AAICR2), mask, on ? mask : 0); +} + +static int hpmicadcl_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(HPMICP_ADCL) | BIT(HPMICN_ADCL); + return snd_soc_update_bits(codec, SOC_REG(AAICR1), mask, on ? mask : 0); +} + +static int hpmicadcr_set(struct snd_soc_codec *codec, int on) +{ + int mask; + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mask = BIT(HPMICP_ADCR) | BIT(HPMICN_ADCR); + return snd_soc_update_bits(codec, SOC_REG(AAICR1), mask, on ? mask : 0); +} + +/* adcpga*_en setting */ +static int adcpgax_auto_setting(struct snd_soc_codec *codec) +{ + unsigned int reg1, reg2, reg3; + int val; + int mask; + reg1 = snd_soc_read(codec, DAOCR1); + reg2 = snd_soc_read(codec, DAOCR2); + reg3 = snd_soc_read(codec, DAOCR3); + val = 0; + if ((reg1 & BIT(ADCL_P_HPL)) || (reg2 & BIT(ADCL_P_AOLP)) + || (reg3 & BIT(ADCL_P_AORP))) { + val |= BIT(ADCPGAL_P_EN); + } + if ((reg1 & BIT(ADCL_N_HPR)) || (reg2 & BIT(ADCL_N_AOLN)) + || (reg3 & BIT(ADCL_N_AORN))) { + val |= BIT(ADCPGAL_N_EN); + } + if ((reg1 & (BIT(ADCR_P_HPL) | BIT(ADCR_P_HPR))) + || (reg2 & BIT(ADCR_P_AOLP)) || (reg3 & BIT(ADCR_P_AORP))) { + val |= BIT(ADCPGAR_P_EN); + } + if ((reg2 & BIT(ADCR_N_AOLN)) || (reg3 & BIT(ADCR_N_AORN))) { + val |= BIT(ADCPGAR_N_EN); + } + mask = + BIT(ADCPGAL_P_EN) | BIT(ADCPGAL_N_EN) | BIT(ADCPGAR_P_EN) | + BIT(ADCPGAR_N_EN); + return snd_soc_update_bits(codec, SOC_REG(AACR2), mask, val); +} + +/* hp mixer */ + +static int daclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(DACL_P_HPL), + on << DACL_P_HPL); +} + +static int daclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(DACL_N_HPR), + on << DACL_N_HPR); +} + +static int dacrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(DACR_P_HPL), + on << DACR_P_HPL); +} + +static int dacrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(DACR_P_HPR), + on << DACR_P_HPR); +} + +static int adclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(ADCL_P_HPL), + on << ADCL_P_HPL); + return adcpgax_auto_setting(codec); +} + +static int adclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(ADCL_N_HPR), + on << ADCL_N_HPR); + return adcpgax_auto_setting(codec); +} + +static int adcrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(ADCR_P_HPL), + on << ADCR_P_HPL); + return adcpgax_auto_setting(codec); +} + +static int adcrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR1), BIT(ADCR_P_HPR), + on << ADCR_P_HPR); + return adcpgax_auto_setting(codec); +} + +/* spkl mixer */ + +static int daclspkl_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(DACL_P_AOLP) | BIT(DACL_N_AOLN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), mask, on ? mask : 0); +} + +static int dacrspkl_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(DACR_P_AOLP) | BIT(DACR_N_AOLN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), mask, on ? mask : 0); +} + +static int adclspkl_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(ADCL_P_AOLP) | BIT(ADCL_N_AOLN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR2), mask, on ? mask : 0); + return adcpgax_auto_setting(codec); +} + +static int adcrspkl_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(ADCR_P_AOLP) | BIT(ADCR_N_AOLN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR2), mask, on ? mask : 0); + return adcpgax_auto_setting(codec); +} + +/* spkr mixer */ + +static int daclspkr_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(DACL_P_AORP) | BIT(DACL_N_AORN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3), mask, on ? mask : 0); +} + +static int dacrspkr_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(DACR_P_AORP) | BIT(DACR_N_AORN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3), mask, on ? mask : 0); +} + +static int adclspkr_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(ADCL_P_AORP) | BIT(ADCL_N_AORN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR3), mask, on ? mask : 0); + return adcpgax_auto_setting(codec); +} + +static int adcrspkr_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(ADCR_P_AORP) | BIT(ADCR_N_AORN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + snd_soc_update_bits(codec, SOC_REG(DAOCR3), mask, on ? mask : 0); + return adcpgax_auto_setting(codec); +} + +/* ear mixer */ + +static int daclear_set(struct snd_soc_codec *codec, int on) +{ + int mask = BIT(DACL_P_EARP) | BIT(DACL_N_EARN); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR4), mask, on ? mask : 0); +} + +static sprd_codec_mixer_set mixer_setting[SPRD_CODEC_MIXER_MAX] = { + /* adc mixer */ + ailadcl_set, ailadcr_set, + airadcl_set, airadcr_set, + mainmicadcl_set, mainmicadcr_set, + auxmicadcl_set, auxmicadcr_set, + hpmicadcl_set, hpmicadcr_set, + /* hp mixer */ + daclhpl_set, daclhpr_set, + dacrhpl_set, dacrhpr_set, + adclhpl_set, adclhpr_set, + adcrhpl_set, adcrhpr_set, + /* spk mixer */ + daclspkl_set, daclspkr_set, + dacrspkl_set, dacrspkr_set, + adclspkl_set, adclspkr_set, + adcrspkl_set, adcrspkr_set, + /* ear mixer */ + daclear_set, 0, +}; + +/* DO NOT USE THIS FUNCTION */ +static inline void __sprd_codec_pa_sw_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + mask = BIT(PA_SW_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR2), mask, val); +} + +static inline void sprd_codec_pa_sw_set(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_set_fun(sprd_codec, fun); + __sprd_codec_pa_sw_en(codec, 1); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_pa_sw_clr(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_clr_fun(sprd_codec, fun); + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + __sprd_codec_pa_sw_en(codec, 0); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +/* inter PA */ + +static inline void sprd_codec_pa_d_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_D_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR2), mask, val); +} + +static inline void sprd_codec_pa_demi_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_DEMI_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR2), mask, val); + + mask = BIT(DRV_OCP_AOL_PD) | BIT(DRV_OCP_AOR_PD); + val = mask; + snd_soc_update_bits(codec, SOC_REG(DCR3), mask, val); +} + +static inline void sprd_codec_pa_ldo_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_LDO_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR2), mask, val); + if (on) { + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + } +} + +static inline void sprd_codec_pa_ldo_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_LDO_V_MASK << PA_LDO_V; + val = (v_sel << PA_LDO_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR4), mask, val); +} + +static inline void sprd_codec_pa_ovp_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_OVP_V_MASK << PA_OVP_V; + val = (v_sel << PA_OVP_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR6), mask, val); + snd_soc_update_bits(codec, SOC_REG(AUDIF_OVPTMR_CTL), 0x1A, 0x3F); +} + +static inline void sprd_codec_pa_dtri_f_sel(struct snd_soc_codec *codec, + int f_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, f_sel); + mask = PA_DTRI_F_MASK << PA_DTRI_F; + val = (f_sel << PA_DTRI_F) & mask; + snd_soc_update_bits(codec, SOC_REG(DCR2), mask, val); +} + +static inline void sprd_codec_pa_en(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + if (on) { + mask = BIT(PA_EN); + val = mask; + } else { + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + mask = BIT(PA_EN) | BIT(PA_SW_EN) | BIT(PA_LDO_EN); + else + mask = BIT(PA_EN) | BIT(PA_LDO_EN); + val = 0; + } + snd_soc_update_bits(codec, SOC_REG(PMUR2), mask, val); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_inter_pa_init(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec->inter_pa.setting.LDO_V_sel = 0x03; + sprd_codec->inter_pa.setting.DTRI_F_sel = 0x01; +} + +static void sprd_codec_ovp_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(OVP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, mask); +} + +static void sprd_codec_ovp_start(struct sprd_codec_priv *sprd_codec, int ms) +{ + schedule_delayed_work(&sprd_codec->ovp_delayed_work, + msecs_to_jiffies(ms)); +} + +static int sprd_ovp_irq(struct snd_soc_codec *codec, int is_ovp) +{ + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("OVP %d curr_volt %d\n", is_ovp, volt); + if (is_ovp) { + sprd_codec_pa_en(codec, 0); + snd_soc_update_bits(codec, SOC_REG(DCR1), BIT(AOL_EN), 0); + } else { + snd_soc_update_bits(codec, SOC_REG(DCR1), BIT(AOL_EN), + BIT(AOL_EN)); + sprd_codec_pa_en(codec, 1); + } + return 0; +} + +#define to_sprd_codec(x) container_of((x), struct sprd_codec_priv, ovp_delayed_work) +#define to_delay_worker(x) container_of((x), struct delayed_work, work) +static void sprd_codec_ovp_delay_worker(struct work_struct *work) +{ + struct sprd_codec_priv *sprd_codec = + to_sprd_codec(to_delay_worker(work)); + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!(snd_soc_read(codec, IFR1) & BIT(OVP_FLAG))) { + sprd_ovp_irq(codec, 0); + } else { + sprd_ovp_irq(codec, 1); + sprd_codec_ovp_start(sprd_codec, 500); + } +} + +static inline int sprd_codec_pa_ldo_auto(struct snd_soc_codec *codec, int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt + (codec, sprd_codec_pa_ldo_v_sel, sprd_codec->s_vpa.v_map, + sprd_codec->s_vpa.v_map_size, init); +} + +static inline int sprd_codec_pa_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vpa, v_map, v_map_size); +} + +static int sprd_inter_speaker_pa(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_pa *p_setting = &sprd_codec->inter_pa.setting; + sp_asoc_pr_info("inter PA Switch %s\n", STR_ON_OFF(on)); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (on) { + sprd_codec_pa_ovp_v_sel(codec, PA_OVP_465); + sprd_codec_ovp_irq_enable(codec); + sprd_codec_pa_d_en(codec, p_setting->is_classD_mode); + sprd_codec_pa_demi_en(codec, p_setting->is_DEMI_mode); + sprd_codec_pa_ldo_en(codec, p_setting->is_LDO_mode); + if (p_setting->is_LDO_mode) { + if (p_setting->is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(codec, 1); + } else { + sprd_codec_pa_ldo_v_sel(codec, + p_setting->LDO_V_sel); + } + } else { + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + } + sprd_codec_pa_dtri_f_sel(codec, p_setting->DTRI_F_sel); + sprd_codec_pa_en(codec, 1); + sprd_codec->inter_pa.set = 1; + } else { + sprd_codec->inter_pa.set = 0; + sprd_codec_pa_en(codec, 0); + sprd_codec_wait(2); + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + sprd_codec_pa_ldo_en(codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + return 0; +} + +static int spk_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_speaker_pa(w->codec, on); +} + +static int sprd_codec_set_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + switch (rate) { + case 8000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_8000 << shift); + break; + case 11025: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_11025 << shift); + break; + case 16000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_16000 << shift); + break; + case 22050: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_22050 << shift); + break; + case 32000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_32000 << shift); + break; + case 44100: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_44100 << shift); + break; + case 48000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_48000 << shift); + break; + case 96000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_96000 << shift); + break; + default: + pr_err("ERR:SPRD-CODEC not support this rate %d\n", rate); + break; + } + sp_asoc_pr_dbg("Set Playback rate 0x%x\n", + snd_soc_read(codec, AUD_DAC_CTL)); + return 0; +} + +static int sprd_codec_set_ad_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + int set; + set = rate / 4000; + if (set > 13) { + pr_err("ERR:SPRD-CODEC not support this ad rate %d\n", rate); + } + snd_soc_update_bits(codec, SOC_REG(AUD_ADC_CTL), mask, set << shift); + return 0; +} + +static int sprd_codec_sample_rate_setting(struct sprd_codec_priv *sprd_codec) +{ + sp_asoc_pr_dbg("%s AD %d DA %d \n", __func__, + sprd_codec->ad_sample_val, sprd_codec->da_sample_val); + if (sprd_codec->ad_sample_val) { + sprd_codec_set_ad_sample_rate(sprd_codec->codec, + sprd_codec->ad_sample_val, 0x0F, + 0); + } + if (sprd_codec->da_sample_val) { + sprd_codec_set_sample_rate(sprd_codec->codec, + sprd_codec->da_sample_val, 0x0F, 0); + } + return 0; +} + +static inline int sprd_codec_vcom_ldo_auto(struct snd_soc_codec *codec, + int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt(codec, sprd_codec_vcm_v_sel, + sprd_codec->s_vcom.v_map, + sprd_codec->s_vcom.v_map_size, init); +} + +static inline int sprd_codec_vcom_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vcom, v_map, v_map_size); +} + +static DEFINE_MUTEX(ldo_mutex); +static int sprd_codec_ldo_on(struct sprd_codec_priv *sprd_codec) +{ + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + atomic_inc(&sprd_codec->ldo_refcount); + if (atomic_read(&sprd_codec->ldo_refcount) == 1) { + sp_asoc_pr_dbg("LDO ON!\n"); + arch_audio_codec_analog_enable(); + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 1); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_ldo_off(struct sprd_codec_priv *sprd_codec) +{ + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + if (atomic_dec_and_test(&sprd_codec->ldo_refcount)) { + arch_audio_codec_analog_disable(); + sp_asoc_pr_dbg("LDO OFF!\n"); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_analog_open(struct snd_soc_codec *codec) +{ + int ret = 0; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + sprd_codec_sample_rate_setting(sprd_codec); + + /* SC8825 ask from ASIC to set initial value */ + snd_soc_write(codec, AUD_SDM_CTL0, 0x400); + snd_soc_write(codec, AUD_SDM_CTL1, 0); + + return ret; +} + +static int sprd_codec_digital_open(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + /* FIXME : Some Clock SYNC bug will cause MUTE */ + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), BIT(DAC_MUTE_EN), 0); + + sprd_codec_sample_rate_setting(sprd_codec); + + return ret; +} + +static void sprd_codec_power_enable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + ret = sprd_codec_ldo_on(sprd_codec); + if (ret != 0) + pr_err("ERR:SPRD-CODEC open ldo error %d\n", ret); + + sprd_codec_analog_open(codec); +} + +static void sprd_codec_power_disable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + + sprd_codec_ldo_off(sprd_codec); +} + +static int digital_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: + arch_audio_codec_digital_reg_enable(); + arch_audio_codec_digital_reset(); + sprd_codec_digital_open(w->codec); + break; + case SND_SOC_DAPM_POST_PMD: + arch_audio_codec_digital_reg_disable(); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int analog_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + sprd_codec_power_enable(codec); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_power_disable(codec); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int chan_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int chan_id = FUN_REG(w->reg); + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + + sp_asoc_pr_info("%s %s\n", sprd_codec_chan_get_name(chan_id), + STR_ON_OFF(on)); + + return 0; +} + +static int adie_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + 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: + atomic_inc(&sprd_codec->adie_dac_refcount); + if (atomic_read(&sprd_codec->adie_dac_refcount) == 1) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_ENB), + BIT(AUDIFA_DAC_EN), + BIT(AUDIFA_DAC_EN)); + } + break; + case SND_SOC_DAPM_POST_PMD: + if (atomic_dec_and_test(&sprd_codec->adie_dac_refcount)) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_ENB), + BIT(AUDIFA_DAC_EN), 0); + } + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int adie_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + 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: + atomic_inc(&sprd_codec->adie_adc_refcount); + if (atomic_read(&sprd_codec->adie_adc_refcount) == 1) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_ENB), + BIT(AUDIFA_ADC_EN), + BIT(AUDIFA_ADC_EN)); + } + break; + case SND_SOC_DAPM_POST_PMD: + if (atomic_dec_and_test(&sprd_codec->adie_adc_refcount)) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_ENB), + BIT(AUDIFA_ADC_EN), 0); + } + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int dfm_out_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + + sp_asoc_pr_info("DFM-OUT %s\n", STR_ON_OFF(on)); + + return 0; +} +static int _mixer_set_mixer(struct snd_soc_codec *codec, int id, int lr, + int try_on, int need_set) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int reg = ID_FUN(id, lr); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[reg]); + if (try_on) { + mixer->set = mixer_setting[reg]; + /* NOTES: reduce linein pop noise must open ADCL/R ->HP MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. */ + if (need_set) + return mixer->set(codec, mixer->on); + } else { + mixer_setting[reg] (codec, 0); + mixer->set = 0; + } + return 0; +} + +static inline int _mixer_setting(struct snd_soc_codec *codec, int start, + int end, int lr, int try_on, int need_set) +{ + int id; + for (id = start; id < end; id++) { + _mixer_set_mixer(codec, id, lr, try_on, need_set); + } + return 0; +} + +static inline int _mixer_setting_one(struct snd_soc_codec *codec, int id, + int try_on, int need_set) +{ + int lr = id & 0x1; + id >>= 1; + return _mixer_setting(codec, id, id + 1, lr, try_on, need_set); +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static void sprd_codec_hp_pop_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(AUDIO_POP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, mask); +} +#endif + +static irqreturn_t sprd_codec_ap_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = snd_soc_read(codec, AUDIF_INT_MASK); + sp_asoc_pr_dbg("HP POP IRQ Mask = 0x%x\n", mask); + if (BIT(AUDIO_POP_IRQ) & mask) { + mask = BIT(AUDIO_POP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, 0); + complete(&sprd_codec->completion_hp_pop); + } + if (BIT(OVP_IRQ) & mask) { + mask = BIT(OVP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, 0); + sprd_codec_ovp_start(sprd_codec, 1); + sprd_codec_ovp_irq_enable(codec); + } + return IRQ_HANDLED; +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static inline int is_hp_pop_compelet(struct snd_soc_codec *codec) +{ + int val; + val = snd_soc_read(codec, IFR1); + val = (val >> HP_POP_FLG) & HP_POP_FLG_MASK; + sp_asoc_pr_dbg("HP POP= 0x%x\n", val); + return HP_POP_FLG_NEAR_CMP == val; +} + +static inline int hp_pop_wait_for_compelet(struct snd_soc_codec *codec) +{ + int i; + int hp_pop_complete; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + hp_pop_complete = msecs_to_jiffies(SPRD_CODEC_HP_POP_TIMEOUT); + for (i = 0; i < 2; i++) { + sp_asoc_pr_dbg("HP POP %d IRQ Enable\n", i); + sprd_codec_hp_pop_irq_enable(codec); + init_completion(&sprd_codec->completion_hp_pop); + hp_pop_complete = + wait_for_completion_timeout(&sprd_codec->completion_hp_pop, + hp_pop_complete); + sp_asoc_pr_dbg("HP POP %d Completion %d\n", i, hp_pop_complete); + if (!hp_pop_complete) { + if (!is_hp_pop_compelet(codec)) { + pr_err("ERR:HP POP %d timeout not complete\n", + i); + } else { + pr_err("ERR:HP POP %d timeout but complete\n", + i); + } + } else { + /* 01 change to 10 maybe walk to 11 shortly, + so, check it double. */ + sprd_codec_wait(2); + if (is_hp_pop_compelet(codec)) { + return 0; + } + } + } + return 0; +} + +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int mask; + 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: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_STEP_2 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_UP << HP_POP_CTL); + sp_asoc_pr_dbg("U PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); + break; + case SND_SOC_DAPM_PRE_PMD: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_STEP_1 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_DOWN << HP_POP_CTL); + sp_asoc_pr_dbg("D PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); + break; + case SND_SOC_DAPM_POST_PMU: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); + break; + case SND_SOC_DAPM_POST_PMD: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#else +#ifndef CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME +#define CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME (0) +#endif +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; + sp_asoc_pr_dbg("%s Event is %s wait %dms\n", __func__, + get_event_name(event), + CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_wait(CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} +#endif + +static int hp_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + int mask; +#endif + 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: +#if 0 /* do not enable the diff function from weifeng.ni */ + snd_soc_update_bits(codec, SOC_REG(DCR1), BIT(DIFF_EN), + BIT(DIFF_EN)); +#endif + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); +#endif + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, SOC_REG(DCR1), BIT(DIFF_EN), 0); + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR1), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR1)); +#endif + goto _pre_pmd; + break; + case SND_SOC_DAPM_POST_PMD: + /* do nothing now */ + break; + default: + BUG(); + ret = -EINVAL; + } + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, DCR1) & BIT(HPL_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, DCR1) & BIT(HPR_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, DCR1) & BIT(HPL_EN), 0); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, DCR1) & BIT(HPR_EN), 0); + +_pre_pmd: + + return ret; +} + +static int spk_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + if (snd_soc_read(codec, DCR1) & BIT(AOL_EN)) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + break; + case SND_SOC_DAPM_PRE_PMD: + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + return 0; + default: + break; + } + } + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, DCR1) & BIT(AOL_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, DCR1) & BIT(AOR_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, DCR1) & BIT(AOL_EN)), 0); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, DCR1) & BIT(AOR_EN)), 0); + + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK +static int ear_switch_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: + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_EAR); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_EAR); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#endif + +static int adc_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_LEFT, + (!(snd_soc_read(codec, AACR2) & BIT(ADCPGAL_PD))), 1); + + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_RIGHT, + (!(snd_soc_read(codec, AACR2) & BIT(ADCPGAR_PD))), 1); + + return 0; +} + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(w->reg); + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + int ret = 0; + int min = sprd_codec_pga_cfg[id].min; + + sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__, + sprd_codec_pga_debug_str[id], pga->pgaval, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + pga->set = sprd_codec_pga_cfg[id].set; + ret = pga->set(codec, pga->pgaval); + break; + case SND_SOC_DAPM_PRE_PMD: + pga->set = 0; + ret = sprd_codec_pga_cfg[id].set(codec, min); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int ana_loop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(w->reg); + struct sprd_codec_mixer *mixer; + int ret = 0; + static int s_need_wait = 1; + + sp_asoc_pr_dbg("Entering %s event is %s\n", __func__, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* NOTES: reduce linein pop noise must open ADCL/R ->HP/SPK MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. + */ + if (s_need_wait == 1) { + /* NOTES: reduce linein pop noise must delay 250ms + after linein mixer switch on. + actually this function perform after + adc_switch_event function for + both ADCL/ADCR switch complete. + */ + sprd_codec_wait(250); + s_need_wait++; + sp_asoc_pr_dbg("ADC Switch ON delay\n"); + } + mixer = &(sprd_codec->mixer[id]); + if (mixer->set) { + mixer->set(codec, mixer->on); + } + break; + case SND_SOC_DAPM_PRE_PMD: + s_need_wait = 1; + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(w->reg); + int ret = 0; + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + struct regulator **regu; + + sp_asoc_pr_dbg("%s %s Event is %s\n", __func__, + mic_bias_name[id], get_event_name(event)); + + switch (id) { + case SPRD_CODEC_MIC_BIAS: + regu = &sprd_codec->main_mic; + break; + case SPRD_CODEC_AUXMIC_BIAS: + regu = &sprd_codec->aux_mic; + break; + default: + BUG(); + ret = -EINVAL; + } + + ret = sprd_codec_mic_bias_set(regu, on); + + return ret; +} + +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(w->reg); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + sp_asoc_pr_dbg("%s event is %s\n", sprd_codec_mixer_debug_str[id], + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mixer->on = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + mixer->on = 0; + break; + default: + BUG(); + ret = -EINVAL; + } + if (ret >= 0) + _mixer_setting_one(codec, id, mixer->on, 1); + + return ret; +} + +static int mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(mc->reg); + ucontrol->value.integer.value[0] = sprd_codec->mixer[id].on; + return 0; +} + +static int mixer_need_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, bool need_set) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(mc->reg); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + if (mixer->on == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("Set %s Switch %s\n", sprd_codec_mixer_debug_str[id], + STR_ON_OFF(ucontrol->value.integer.value[0])); + + /*notice the sequence */ + snd_soc_dapm_put_volsw(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); */ + mixer->on = ucontrol->value.integer.value[0]; + + if (mixer->set && need_set) + ret = mixer->set(codec, mixer->on); + + return ret; +} + +static int mixer_set_mem(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 0); +} + +static int mixer_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 1); +} + +#define SPRD_CODEC_MIXER(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set) + +/*Just for LINE IN path, mixer_set not really set mixer (ADCL/R -> HP/SPK L/R) here but +setting in ana_loop_event, just remeber state here*/ +#define SPRD_CODEC_MIXER_NOSET(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set_mem) + +/* ADCL Mixer */ +static const struct snd_kcontrol_new adcl_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCL Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AIRADCL Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("MainMICADCL Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AuxMICADCL Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("HPMICADCL Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_LEFT)), +}; + +/* ADCR Mixer */ +static const struct snd_kcontrol_new adcr_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCR Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AIRADCR Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("MainMICADCR Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AuxMICADCR Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("HPMICADCR Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_RIGHT)), +}; + +/* HPL Mixer */ +static const struct snd_kcontrol_new hpl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), +}; + +/* HPR Mixer */ +static const struct snd_kcontrol_new hpr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), +}; + +/* SPKL Mixer */ +static const struct snd_kcontrol_new spkl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), +}; + +/* SPKR Mixer */ +static const struct snd_kcontrol_new spkr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), +}; + +/*ANA LOOP SWITCH*/ +#define SPRD_CODEC_LOOP_SWITCH(xname, xreg)\ + SND_SOC_DAPM_PGA_S(xname, 7, FUN_REG(xreg), 0, 0, ana_loop_event,\ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD) + +#ifndef SND_SOC_DAPM_MICBIAS_E +#define SND_SOC_DAPM_MICBIAS_E(wname, wreg, wshift, winvert, wevent, wflags) \ +{ .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \ + .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags} +#endif + +static const struct snd_soc_dapm_widget sprd_codec_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("VBO", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VCOM_BUF", 0, 0), + SND_SOC_DAPM_SUPPLY_S("Digital Power", 1, SND_SOC_NOPM, 0, 0, + digital_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("Analog Power", 2, SND_SOC_NOPM, 0, 0, + analog_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("DA Clk", 3, SOC_REG(CCR), DAC_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DRV Clk", 4, SOC_REG(CCR), DRV_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("AD IBUF", 3, SOC_REG(AACR1), ADC_IBUF_PD, + 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD Clk", 4, SOC_REG(CCR), ADC_CLK_PD, 1, NULL, + 0), + + SND_SOC_DAPM_PGA_S("Digital DACL Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital DACR Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital DACL Switch", 5, SND_SOC_NOPM, 0, 0, + adie_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADie Digital DACR Switch", 5, SND_SOC_NOPM, 0, 0, + adie_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("DACL Switch", 6, SOC_REG(DACR), DACL_EN, 0, NULL, + 0), + SND_SOC_DAPM_PGA_S("DACR Switch", 6, SOC_REG(DACR), DACR_EN, 0, NULL, + 0), + SND_SOC_DAPM_DAC_E("DAC", "Playback-Vaudio", + FUN_REG(SPRD_CODEC_PLAYBACK), 0, + 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DFM-OUT", "DFM-Output", + SND_SOC_NOPM, 0, + 0, + dfm_out_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + SND_SOC_DAPM_PGA_S("HP POP", 9, SND_SOC_NOPM, 0, 0, hp_pop_event, + SND_SOC_DAPM_POST_PMU), +#else + SND_SOC_DAPM_SUPPLY_S("HP POP", 5, SND_SOC_NOPM, 0, 0, hp_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +#endif + SND_SOC_DAPM_PGA_S("HPL Switch", 5, SOC_REG(DCR1), HPL_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Switch", 5, SOC_REG(DCR1), HPR_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPL Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER("HPL Mixer", SND_SOC_NOPM, 0, 0, + &hpl_mixer_controls[0], + ARRAY_SIZE(hpl_mixer_controls)), + SND_SOC_DAPM_MIXER("HPR Mixer", SND_SOC_NOPM, 0, 0, + &hpr_mixer_controls[0], + ARRAY_SIZE(hpr_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKL Mixer", SND_SOC_NOPM, 0, 0, + &spkl_mixer_controls[0], + ARRAY_SIZE(spkl_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKR Mixer", SND_SOC_NOPM, 0, 0, + &spkr_mixer_controls[0], + ARRAY_SIZE(spkr_mixer_controls)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), + SND_SOC_DAPM_PGA_S("SPKL Switch", 5, SOC_REG(DCR1), AOL_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Switch", 5, SOC_REG(DCR1), AOR_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("SPKL Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("EAR Mixer", 5, + FUN_REG(ID_FUN + (SPRD_CODEC_EAR_DACL, SPRD_CODEC_LEFT)), 0, + 0, mixer_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(DCR1), EAR_EN, 0, + ear_switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#else + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(DCR1), EAR_EN, 0, NULL, 0), +#endif + + SND_SOC_DAPM_PGA_S("EAR Mute", 7, FUN_REG(SPRD_CODEC_PGA_EAR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MIXER("ADCL Mixer", SND_SOC_NOPM, 0, 0, + &adcl_mixer_controls[0], + ARRAY_SIZE(adcl_mixer_controls)), + SND_SOC_DAPM_MIXER("ADCR Mixer", SND_SOC_NOPM, 0, 0, + &adcr_mixer_controls[0], + ARRAY_SIZE(adcr_mixer_controls)), + SND_SOC_DAPM_PGA_S("Digital ADCL Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADCR Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital ADCL Switch", 2, SND_SOC_NOPM, 0, 0, + adie_adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADie Digital ADCR Switch", 2, SND_SOC_NOPM, 0, 0, + adie_adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADCL Switch", 1, SOC_REG(AACR1), ADCL_PD, 1, NULL, + 0), + SND_SOC_DAPM_PGA_S("ADCR Switch", 1, SOC_REG(AACR1), ADCR_PD, 1, NULL, + 0), + SND_SOC_DAPM_PGA_E("ADCL PGA", SOC_REG(AACR2), ADCPGAL_PD, 1, NULL, 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ADCR PGA", SOC_REG(AACR2), ADCPGAR_PD, 1, NULL, 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADCL Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("ADCR Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC", "Main-Capture-Vaudio", + FUN_REG(SPRD_CODEC_CAPTRUE), + 0, 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS_E("Mic Bias", FUN_REG(SPRD_CODEC_MIC_BIAS), 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("AuxMic Bias", FUN_REG(SPRD_CODEC_AUXMIC_BIAS), + 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA_S("Spk PA Switch", 15, SND_SOC_NOPM, 0, 0, + spk_pa_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_OUTPUT("HEAD_P_L"), + SND_SOC_DAPM_OUTPUT("HEAD_P_R"), + SND_SOC_DAPM_OUTPUT("AOL"), + SND_SOC_DAPM_OUTPUT("AOR"), + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_OUTPUT("HP PA"), + SND_SOC_DAPM_OUTPUT("Spk PA"), + SND_SOC_DAPM_OUTPUT("Spk2 PA"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("AUXMIC"), + SND_SOC_DAPM_INPUT("HPMIC"), + SND_SOC_DAPM_INPUT("AIL"), + SND_SOC_DAPM_INPUT("AIR"), +}; + +/* sprd_codec supported interconnection*/ +static const struct snd_soc_dapm_route sprd_codec_intercon[] = { + /* Power */ + {"Analog Power", NULL, "VCOM_BUF"}, + {"Analog Power", NULL, "VBO"}, + {"DA Clk", NULL, "Analog Power"}, + {"DA Clk", NULL, "Digital Power"}, + {"DAC", NULL, "DA Clk"}, + {"DFM-OUT", NULL, "DA Clk"}, + + {"AD IBUF", NULL, "Analog Power"}, + {"AD Clk", NULL, "Digital Power"}, + {"AD Clk", NULL, "AD IBUF"}, + {"ADC", NULL, "AD Clk"}, + + {"ADCL PGA", NULL, "AD IBUF"}, + {"ADCR PGA", NULL, "AD IBUF"}, + + {"HP POP", NULL, "DRV Clk"}, + {"HPL Switch", NULL, "DRV Clk"}, + {"HPR Switch", NULL, "DRV Clk"}, + {"SPKL Switch", NULL, "DRV Clk"}, + {"SPKR Switch", NULL, "DRV Clk"}, + {"EAR Switch", NULL, "DRV Clk"}, + + /* Playback */ + {"Digital DACL Switch", NULL, "DFM-OUT"}, + {"Digital DACR Switch", NULL, "DFM-OUT"}, + {"Digital DACL Switch", NULL, "DAC"}, + {"Digital DACR Switch", NULL, "DAC"}, + {"ADie Digital DACL Switch", NULL, "Digital DACL Switch"}, + {"ADie Digital DACR Switch", NULL, "Digital DACR Switch"}, + {"DACL Switch", NULL, "ADie Digital DACL Switch"}, + {"DACR Switch", NULL, "ADie Digital DACR Switch"}, + + /* Output */ + {"HPL Mixer", "DACLHPL Switch", "DACL Switch"}, + {"HPL Mixer", "DACRHPL Switch", "DACR Switch"}, + {"HPR Mixer", "DACLHPR Switch", "DACL Switch"}, + {"HPR Mixer", "DACRHPR Switch", "DACR Switch"}, + + {"ADCLHPL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLHPR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRHPL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRHPR Loop Switch", NULL, "ADCR PGA"}, + + {"HPL Mixer", "ADCLHPL Switch", "ADCLHPL Loop Switch"}, + {"HPL Mixer", "ADCRHPL Switch", "ADCRHPL Loop Switch"}, + {"HPR Mixer", "ADCLHPR Switch", "ADCLHPR Loop Switch"}, + {"HPR Mixer", "ADCRHPR Switch", "ADCRHPR Loop Switch"}, + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + {"HEAD_P_L", NULL, "HP POP"}, + {"HEAD_P_R", NULL, "HP POP"}, + {"HP POP", NULL, "HPL Switch"}, + {"HP POP", NULL, "HPR Switch"}, +#else + {"HPL Switch", NULL, "HP POP"}, + {"HPR Switch", NULL, "HP POP"}, +#endif + {"HPL Switch", NULL, "HPL Mixer"}, + {"HPR Switch", NULL, "HPR Mixer"}, + {"HPL Mute", NULL, "HPL Switch"}, + {"HPR Mute", NULL, "HPR Switch"}, + {"HEAD_P_L", NULL, "HPL Mute"}, + {"HEAD_P_R", NULL, "HPR Mute"}, + + {"SPKL Mixer", "DACLSPKL Switch", "DACL Switch"}, + {"SPKL Mixer", "DACRSPKL Switch", "DACR Switch"}, + {"SPKR Mixer", "DACLSPKR Switch", "DACL Switch"}, + {"SPKR Mixer", "DACRSPKR Switch", "DACR Switch"}, + + {"ADCLSPKL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLSPKR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRSPKL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRSPKR Loop Switch", NULL, "ADCR PGA"}, + + {"SPKL Mixer", "ADCLSPKL Switch", "ADCLSPKL Loop Switch"}, + {"SPKL Mixer", "ADCRSPKL Switch", "ADCRSPKL Loop Switch"}, + {"SPKR Mixer", "ADCLSPKR Switch", "ADCLSPKR Loop Switch"}, + {"SPKR Mixer", "ADCRSPKR Switch", "ADCRSPKR Loop Switch"}, + + {"SPKL Switch", NULL, "SPKL Mixer"}, + {"SPKR Switch", NULL, "SPKR Mixer"}, + {"SPKL Mute", NULL, "SPKL Switch"}, + {"SPKR Mute", NULL, "SPKR Switch"}, + {"AOL", NULL, "SPKL Mute"}, + {"AOR", NULL, "SPKR Mute"}, + + {"Spk PA", NULL, "Spk PA Switch"}, + {"Spk PA Switch", NULL, "SPKL Mute"}, + + {"EAR Mixer", NULL, "DACL Switch"}, + {"EAR Switch", NULL, "EAR Mixer"}, + {"EAR Mute", NULL, "EAR Switch"}, + {"EAR", NULL, "EAR Mute"}, + + {"ADCL Mute", NULL, "ADCL Mixer"}, + {"ADCR Mute", NULL, "ADCR Mixer"}, + {"ADCL PGA", NULL, "ADCL Mute"}, + {"ADCR PGA", NULL, "ADCR Mute"}, + /* LineIN */ + {"ADCL Mixer", "AILADCL Switch", "AIL"}, + {"ADCR Mixer", "AILADCR Switch", "AIL"}, + {"ADCL Mixer", "AIRADCL Switch", "AIR"}, + {"ADCR Mixer", "AIRADCR Switch", "AIR"}, + /* Input */ + {"ADCL Mixer", "MainMICADCL Switch", "Mic Bias"}, + {"ADCR Mixer", "MainMICADCR Switch", "Mic Bias"}, + {"ADCL Mixer", "AuxMICADCL Switch", "AuxMic Bias"}, + {"ADCR Mixer", "AuxMICADCR Switch", "AuxMic Bias"}, + {"ADCL Mixer", "HPMICADCL Switch", "HPMIC"}, + {"ADCR Mixer", "HPMICADCR Switch", "HPMIC"}, + /* Captrue */ + {"ADCL Switch", NULL, "ADCL PGA"}, + {"ADCR Switch", NULL, "ADCR PGA"}, + {"ADie Digital ADCL Switch", NULL, "ADCL Switch"}, + {"ADie Digital ADCR Switch", NULL, "ADCR Switch"}, + {"Digital ADCL Switch", NULL, "ADie Digital ADCL Switch"}, + {"Digital ADCR Switch", NULL, "ADie Digital ADCR Switch"}, + {"ADC", NULL, "Digital ADCL Switch"}, + {"ADC", NULL, "Digital ADCR Switch"}, + + {"Mic Bias", NULL, "MIC"}, + {"AuxMic Bias", NULL, "AUXMIC"}, + /* Bias independent */ + {"Mic Bias", NULL, "Analog Power"}, + {"AuxMic Bias", NULL, "Analog Power"}, +}; + +static int sprd_codec_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[reg]); + int ret = 0; + + sp_asoc_pr_info("Set PGA[%s] to %ld\n", sprd_codec_pga_debug_str[reg], + ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + pga->pgaval = val; + if (pga->set) { + ret = pga->set(codec, pga->pgaval); + } + return ret; +} + +static int sprd_codec_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int invert = mc->invert; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[reg]); + + ucontrol->value.integer.value[0] = pga->pgaval; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_inter_pa_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + + sp_asoc_pr_info("Config inter PA 0x%08x\n", + (int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->inter_pa_mutex); + sprd_codec->inter_pa.value = (u32) val; + if (sprd_codec->inter_pa.set) { + mutex_unlock(&sprd_codec->inter_pa_mutex); + sprd_inter_speaker_pa(codec, 1); + } else { + mutex_unlock(&sprd_codec->inter_pa_mutex); + } + return ret; +} + +static int sprd_codec_inter_pa_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_pa_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_pa.value; + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_mic_bias_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + int id = reg; + + val = (ucontrol->value.integer.value[0] & mask); + + if (invert) + val = max - val; + if (sprd_codec->mic_bias[id] == val) { + return 0; + } + + sp_asoc_pr_info("%s Switch %s\n", mic_bias_name[id], STR_ON_OFF(val)); + + sprd_codec->mic_bias[id] = val; + if (val) { + ret = + snd_soc_dapm_force_enable_pin(&codec->card->dapm, + mic_bias_name[id]); + } else { + ret = + snd_soc_dapm_disable_pin(&codec->card->dapm, + mic_bias_name[id]); + } + + /* signal a DAPM event */ + snd_soc_dapm_sync(&codec->card->dapm); + + return ret; +} + +static int sprd_codec_mic_bias_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int invert = mc->invert; + int id = reg; + + ucontrol->value.integer.value[0] = sprd_codec->mic_bias[id]; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -600, 300, 0); +static const DECLARE_TLV_DB_SCALE(hp_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(ear_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -2400, 300, 1); + +#define SPRD_CODEC_PGA(xname, xreg, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, FUN_REG(xreg), 0, 15, 0, \ + sprd_codec_vol_get, sprd_codec_vol_put, tlv_array) + +#define SPRD_CODEC_MIC_BIAS(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, \ + sprd_codec_mic_bias_get, sprd_codec_mic_bias_put) + +static const struct snd_kcontrol_new sprd_codec_snd_controls[] = { + SPRD_CODEC_PGA("SPKL Playback Volume", SPRD_CODEC_PGA_SPKL, spk_tlv), + SPRD_CODEC_PGA("SPKR Playback Volume", SPRD_CODEC_PGA_SPKR, spk_tlv), + SPRD_CODEC_PGA("HPL Playback Volume", SPRD_CODEC_PGA_HPL, hp_tlv), + SPRD_CODEC_PGA("HPR Playback Volume", SPRD_CODEC_PGA_HPR, hp_tlv), + SPRD_CODEC_PGA("EAR Playback Volume", SPRD_CODEC_PGA_EAR, ear_tlv), + + SPRD_CODEC_PGA("ADCL Capture Volume", SPRD_CODEC_PGA_ADCL, adc_tlv), + SPRD_CODEC_PGA("ADCR Capture Volume", SPRD_CODEC_PGA_ADCR, adc_tlv), + + SOC_SINGLE_EXT("Inter PA Config", 0, 0, LONG_MAX, 0, + sprd_codec_inter_pa_get, sprd_codec_inter_pa_put), + + SPRD_CODEC_MIC_BIAS("MIC Bias Switch", SPRD_CODEC_MIC_BIAS), + + SPRD_CODEC_MIC_BIAS("AUXMIC Bias Switch", SPRD_CODEC_AUXMIC_BIAS), +}; + +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + /* Because snd_soc_update_bits reg is 16 bits short type, + so muse do following convert + */ + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + return arch_audio_codec_read(reg); + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + return __raw_readl((void __iomem *)reg); + } else if (IS_SPRD_CODEC_MIXER_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = FUN_REG(reg); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + return mixer->on; + } + + sp_asoc_pr_dbg("read the register is not codec's reg = 0x%x\n", reg); + return 0; +} + +static int sprd_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + int ret = 0; + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + ret = arch_audio_codec_write(reg, val); + sp_asoc_pr_reg("A[0x%04x] W:[0x%08x] R:[0x%08x]\n", + reg & 0xFFFF, val, arch_audio_codec_read(reg)); + return ret; + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + __raw_writel(val, (void __iomem *)reg); + sp_asoc_pr_reg("D[0x%04x] W:[0x%08x] R:[0x%08x]\n", + reg & 0xFFFF, val, + __raw_readl((void __iomem *)reg)); + return ret; + } + sp_asoc_pr_dbg("write the register is not codec's reg = 0x%x\n", reg); + return ret; +} + +static int sprd_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask = 0x0F; + int shift = 0; + int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sprd_codec->da_sample_val = rate; + sp_asoc_pr_info("Playback rate is [%d]\n", rate); + sprd_codec_set_sample_rate(codec, rate, mask, shift); + } else { + sprd_codec->ad_sample_val = rate; + sp_asoc_pr_info("Capture rate is [%d]\n", rate); + sprd_codec_set_ad_sample_rate(codec, rate, mask, shift); + } + + return 0; +} + +static int sprd_codec_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +#if 0 +static void sprd_codec_dac_mute_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(DAC_MUTE_D); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, mask); +} +#endif + +static irqreturn_t sprd_codec_dp_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = snd_soc_read(codec, AUD_AUD_STS0); + sp_asoc_pr_dbg("dac mute irq mask = 0x%x\n", mask); + if (BIT(DAC_MUTE_D_MASK) & mask) { + mask = BIT(DAC_MUTE_D); + complete(&sprd_codec->completion_dac_mute); + } + if (BIT(DAC_MUTE_U_MASK) & mask) { + mask = BIT(DAC_MUTE_U); + } + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, 0); + return IRQ_HANDLED; +} + +#if 0 +static int sprd_codec_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sp_asoc_pr_dbg("mute %i\n", mute); + + ret = + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), + BIT(DAC_MUTE_START), + mute ? BIT(DAC_MUTE_START) : 0); +#if 0 + if (mute && ret) { + int dac_mute_complete; + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("Dac Mute IRQ enable\n"); + sprd_codec_dac_mute_irq_enable(codec); + init_completion(&sprd_codec->completion_dac_mute); + dac_mute_complete = + wait_for_completion_timeout + (&sprd_codec->completion_dac_mute, + msecs_to_jiffies(SPRD_CODEC_DAC_MUTE_TIMEOUT)); + sp_asoc_pr_dbg("Dac Mute Completion %d\n", + dac_mute_complete); + if (!dac_mute_complete) { + pr_err("Dac Mute Timeout\n"); + } + } +#endif + + } + + return ret; +} +#endif + +static struct snd_soc_dai_ops sprd_codec_dai_ops = { + .hw_params = sprd_codec_pcm_hw_params, + .hw_free = sprd_codec_pcm_hw_free, +}; + +#ifdef CONFIG_PM +static int sprd_codec_soc_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_soc_resume(struct snd_soc_codec *codec) +{ + return 0; +} +#else +#define sprd_codec_soc_suspend NULL +#define sprd_codec_soc_resume NULL +#endif + +/* + * proc interface + */ + +#ifdef CONFIG_PROC_FS +static void sprd_codec_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct sprd_codec_priv *sprd_codec = entry->private_data; + struct snd_soc_codec *codec = sprd_codec->codec; + int reg; + + snd_iprintf(buffer, "%s digital part\n", codec->name); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_DP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } + snd_iprintf(buffer, "%s analog part\n", codec->name); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_AP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } +} + +static void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ + struct snd_info_entry *entry; + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!snd_card_proc_new(codec->card->snd_card, "sprd-codec", &entry)) + snd_info_set_text_ops(entry, sprd_codec, sprd_codec_proc_read); +} +#else /* !CONFIG_PROC_FS */ +static inline void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ +} +#endif + +static void sprd_codec_power_changed(struct power_supply *psy) +{ + struct sprd_codec_priv *sprd_codec = 0; + sp_asoc_pr_dbg("%s %s\n", __func__, psy->name); +#if 1 + sprd_codec = container_of(psy, struct sprd_codec_priv, audio_ldo); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (sprd_codec->inter_pa.set && sprd_codec->inter_pa.setting.is_LDO_mode + && sprd_codec->inter_pa.setting.is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(sprd_codec->codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 0); + } +#endif +} + +static int sprd_codec_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + return -EINVAL; +} + +static int sprd_codec_audio_ldo(struct sprd_codec_priv *sprd_codec) +{ + int ret = 0; + struct snd_soc_codec *codec = sprd_codec->codec; + sprd_codec->audio_ldo.name = "audio-ldo"; + sprd_codec->audio_ldo.get_property = sprd_codec_power_get_property; + sprd_codec->audio_ldo.external_power_changed = sprd_codec_power_changed; + ret = power_supply_register(codec->dev, &sprd_codec->audio_ldo); + if (ret) { + pr_err("ERR:Register power supply error!\n"); + return -EFAULT; + } + return ret; +} + +#define SPRD_CODEC_PCM_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define SPRD_CODEC_PCM_AD_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_48000) + +/* PCM Playing and Recording default in full duplex mode */ +static struct snd_soc_dai_driver sprd_codec_dai[] = { + { + .name = "sprd-codec-v1-i2s", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "sprd-codec-v1-vaudio", + .playback = { + .stream_name = "Playback-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#endif + { + .name = "sprd-codec-v1-fm", + .playback = { + .stream_name = "DFM-Output", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +}; + +static int sprd_codec_soc_probe(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + codec->dapm.idle_bias_off = 1; + + sprd_codec->codec = codec; + + sprd_codec_proc_init(sprd_codec); + + sprd_codec_audio_ldo(sprd_codec); + + return ret; +} + +/* power down chip */ +static int sprd_codec_soc_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_power_regulator_init(struct sprd_codec_priv *sprd_codec, + struct device *dev) +{ + sprd_codec_power_get(dev, &sprd_codec->main_mic, "MICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->aux_mic, "AUXMICBIAS"); + return 0; +} + +static void sprd_codec_power_regulator_exit(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec_power_put(&sprd_codec->main_mic); + sprd_codec_power_put(&sprd_codec->aux_mic); +} + +static struct snd_soc_codec_driver soc_codec_dev_sprd_codec = { + .probe = sprd_codec_soc_probe, + .remove = sprd_codec_soc_remove, + .suspend = sprd_codec_soc_suspend, + .resume = sprd_codec_soc_resume, + .read = sprd_codec_read, + .write = sprd_codec_write, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = sprd_codec_intercon, + .num_dapm_routes = ARRAY_SIZE(sprd_codec_intercon), + .controls = sprd_codec_snd_controls, + .num_controls = ARRAY_SIZE(sprd_codec_snd_controls), +}; + +static int sprd_codec_probe(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec; + int ret; + struct device_node *node = pdev->dev.of_node; + + sp_asoc_pr_dbg("%s\n", __func__); + + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + + sprd_codec = + devm_kzalloc(&pdev->dev, sizeof(struct sprd_codec_priv), + GFP_KERNEL); + if (sprd_codec == NULL) + return -ENOMEM; + + sprd_codec->da_sample_val = 44100; /*inital value for FM route */ + + if (node) { + u32 val; + if (!of_property_read_u32(node, "sprd,ap_irq", &val)) { + sprd_codec->ap_irq = val; + sp_asoc_pr_dbg("Set AP IRQ is %d!\n", val); + } else { + pr_err("ERR:Must give me the AP IRQ!\n"); + return -EINVAL; + } + if (!of_property_read_u32(node, "sprd,dp_irq", &val)) { + sprd_codec->dp_irq = val; + sp_asoc_pr_dbg("Set DP IRQ is %d!\n", val); + } else { + pr_err("ERR:Must give me the DP IRQ!\n"); + return -EINVAL; + } + if (!of_property_read_u32(node, "sprd,def_da_fs", &val)) { + sprd_codec->da_sample_val = val; + sp_asoc_pr_dbg("Change DA default fs to %d!\n", val); + } + } else { + sprd_codec->ap_irq = CODEC_AP_IRQ; + sprd_codec->dp_irq = CODEC_DP_IRQ; + } + + platform_set_drvdata(pdev, sprd_codec); + + atomic_set(&sprd_codec->adie_adc_refcount, 0); + atomic_set(&sprd_codec->adie_dac_refcount, 0); + + ret = + request_irq(sprd_codec->ap_irq, sprd_codec_ap_irq, 0, + "sprd_codec_ap", sprd_codec); + if (ret) { + pr_err("ERR:Request irq ap failed!\n"); + goto err_irq; + } + + INIT_DELAYED_WORK(&sprd_codec->ovp_delayed_work, + sprd_codec_ovp_delay_worker); + + ret = + request_irq(sprd_codec->dp_irq, sprd_codec_dp_irq, 0, + "sprd_codec_dp", sprd_codec); + if (ret) { + pr_err("ERR:Request irq dp failed!\n"); + goto dp_err_irq; + } + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_sprd_codec, + sprd_codec_dai, + ARRAY_SIZE(sprd_codec_dai)); + if (ret != 0) { + pr_err("ERR:Failed to register CODEC: %d\n", ret); + return ret; + } + + sprd_codec_inter_pa_init(sprd_codec); + + sprd_codec_power_regulator_init(sprd_codec, &pdev->dev); + + mutex_init(&sprd_codec->inter_pa_mutex); + spin_lock_init(&sprd_codec->sprd_codec_fun_lock); + spin_lock_init(&sprd_codec->sprd_codec_pa_sw_lock); + + /* SC8825C need change the v_map */ + sprd_codec_vcom_ldo_cfg(sprd_codec, ldo_vcom_v_map, + ARRAY_SIZE(ldo_vcom_v_map)); + sprd_codec_pa_ldo_cfg(sprd_codec, ldo_v_map, ARRAY_SIZE(ldo_v_map)); + + return 0; + +dp_err_irq: + free_irq(sprd_codec->ap_irq, sprd_codec); +err_irq: + return -EINVAL; +} + +static int sprd_codec_remove(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec = platform_get_drvdata(pdev); + sprd_codec_power_regulator_exit(sprd_codec); + free_irq(sprd_codec->ap_irq, sprd_codec); + free_irq(sprd_codec->dp_irq, sprd_codec); + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id codec_of_match[] = { + {.compatible = "sprd,sprd-codec-v1",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, codec_of_match); +#endif + +static struct platform_driver sprd_codec_codec_driver = { + .driver = { + .name = "sprd-codec-v1", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(codec_of_match), + }, + .probe = sprd_codec_probe, + .remove = sprd_codec_remove, +}; + +module_platform_driver(sprd_codec_codec_driver); + +MODULE_DESCRIPTION("SPRD-CODEC ALSA SoC codec driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:sprd-codec"); diff --git a/sound/soc/sprd/codec/sprd/v1/sprd-codec.h b/sound/soc/sprd/codec/sprd/v1/sprd-codec.h new file mode 100644 index 00000000..9ff3760a --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v1/sprd-codec.h @@ -0,0 +1,375 @@ +/* + * sound/soc/sprd/codec/sprd/v1/sprd-codec.h + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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 __SPRD_CODEC_H +#define __SPRD_CODEC_H + +#include <mach/hardware.h> +#include <mach/globalregs.h> +#include <mach/sprd-audio.h> +#include <mach/adi.h> +#include <asm/io.h> + +#ifndef CONFIG_SPRD_CODEC_USE_INT +/* #define CONFIG_SPRD_CODEC_USE_INT */ +#endif +#ifndef CONFIG_CODEC_DAC_MUTE_WAIT +/* #define CONFIG_CODEC_DAC_MUTE_WAIT */ +#endif + +/* unit: ms */ +#define SPRD_CODEC_LDO_WAIT_TIME (5) +#define SPRD_CODEC_LDO_VCM_TIME (2) +#ifdef CONFIG_SPRD_CODEC_USE_INT +#define SPRD_CODEC_DAC_MUTE_TIMEOUT (600) +#else +#define SPRD_CODEC_DAC_MUTE_WAIT_TIME (40) +#endif + +#ifdef CONFIG_SPRD_CODEC_USE_INT +#define SPRD_CODEC_HP_POP_TIMEOUT (1000) +#else +#define SPRD_CODEC_HP_POP_TIME_STEP (10) +#define SPRD_CODEC_HP_POP_TIME_COUNT (80) /* max 800ms will timeout */ +#endif + +#define SPRD_CODEC_RATE_8000 (10) +#define SPRD_CODEC_RATE_9600 ( 9) +#define SPRD_CODEC_RATE_11025 ( 8) +#define SPRD_CODEC_RATE_12000 ( 7) +#define SPRD_CODEC_RATE_16000 ( 6) +#define SPRD_CODEC_RATE_22050 ( 5) +#define SPRD_CODEC_RATE_24000 ( 4) +#define SPRD_CODEC_RATE_32000 ( 3) +#define SPRD_CODEC_RATE_44100 ( 2) +#define SPRD_CODEC_RATE_48000 ( 1) +#define SPRD_CODEC_RATE_96000 ( 0) + +/* AUD_TOP_CTL */ +#define DAC_EN_L (0) +#define ADC_EN_L (1) +#define DAC_EN_R (2) +#define ADC_EN_R (3) + +/* AUD_DAC_CTL */ +#define DAC_MUTE_START (14) +#define DAC_MUTE_EN (15) + +/* AUD_AUD_STS0 */ +#define DAC_MUTE_U_MASK (5) +#define DAC_MUTE_D_MASK (4) +#define DAC_MUTE_U_RAW (3) +#define DAC_MUTE_D_RAW (2) +#define DAC_MUTE_ST (0) +#define DAC_MUTE_ST_MASK (0x3) + +/* AUD_INT_CLR */ +/* AUD_INT_EN */ +#define DAC_MUTE_U (1) +#define DAC_MUTE_D (0) + +/* AUDIF_ENB */ +#define AUDIFA_DAC_EN (0) +#define AUDIFA_ADC_EN (1) + +/* AUDIF_INT_EN */ +/* AUDIF_INT_CLR */ +/* AUDIF_INT_RAW */ +/* AUDIF_INT_MASK */ +#define AUDIO_POP_IRQ (7) +#define OVP_IRQ (6) +#define OTP_IRQ (5) +#define PA_OCP_IRQ (4) +#define LOR_OCP_IRQ (3) +#define LOL_OCP_IRQ (2) +#define EAR_OCP_IRQ (1) +#define HP_OCP_IRQ (0) + +/* PMUR1 */ +#define VB_EN (7) +#define BG_EN (6) +#define BG_IBIAS_EN (5) +#define VCM_EN (4) +#define VCM_BUF_EN (3) +#define VBO_EN (2) +#define MICBIAS_EN (1) +#define AUXMICBIAS_EN (0) + +/* PMUR2 */ +#define PA_SW_EN (7) +#define PA_LDO_EN (6) +#define PA_EN (5) +#define PAR_SW_EN (4) +#define PAR_LDO_EN (3) +#define PAR_EN (2) +#define OVP_PD (1) +#define OVP_LDO_EN (0) + +#define LDO_V_29 (0) +#define LDO_V_31 (1) +#define LDO_V_32 (2) +#define LDO_V_33 (3) +#define LDO_V_34 (4) +#define LDO_V_35 (5) +#define LDO_V_36 (6) +#define LDO_V_38 (7) + +#define MIC_LDO_V_21 (0) +#define MIC_LDO_V_19 (1) +#define MIC_LDO_V_23 (2) +#define MIC_LDO_V_25 (3) + +/* PMUR3 */ +#define VCM_V (5) +#define VCM_V_MASK (0x7) +#define MICBIAS_V (3) +#define MICBIAS_V_MASK (0x3) +#define AUXMICBIAS_V (1) +#define AUXMICBIAS_V_MASK (0x3) + +/* PMUR4 */ +#define PA_SWOCP_PD (7) +#define PA_LDOOCP_PD (6) +#define PA_LDO_V (3) +#define PA_LDO_V_MASK (0x7) +#define BG_RST (2) +#define BG_I (0) +#define BG_I_MASK (0x3) + +/* PMUR6 */ +#define PA_OVP_V (5) +#define PA_OVP_V_MASK (0x7) +#define PA_OVP_542 (0) +#define PA_OVP_527 (1) +#define PA_OVP_512 (2) +#define PA_OVP_496 (3) +#define PA_OVP_481 (4) +#define PA_OVP_465 (5) +#define PA_OVP_450 (6) +#define PA_OVP_435 (7) + +/* AACR1 */ +#define ADC_IBUF_PD (7) +#define ADC_VREF1P5 (6) +#define ADCL_PD (5) +#define ADCL_RST (4) +#define ADCR_PD (3) +#define ADCR_RST (2) +#define FM_REC_EN (1) + +/* AACR2 */ +#define ADCPGAL_PD (7) +#define ADCPGAL_BYP (6) +#define ADCPGAR_PD (5) +#define ADCPGAR_BYP (4) +#define ADCPGAL_P_EN (3) +#define ADCPGAL_N_EN (2) +#define ADCPGAR_P_EN (1) +#define ADCPGAR_N_EN (0) + +/* AAICR1 */ +#define MICP_ADCL (7) +#define MICN_ADCL (6) +#define MICP_ADCR (5) +#define MICN_ADCR (4) +#define HPMICP_ADCL (3) +#define HPMICN_ADCL (2) +#define HPMICP_ADCR (1) +#define HPMICN_ADCR (0) + +/* AAICR2 */ +#define AUXMICP_ADCL (7) +#define AUXMICN_ADCL (6) +#define AUXMICP_ADCR (5) +#define AUXMICN_ADCR (4) + +/* AAICR3 */ +#define AIL_ADCL (7) +#define AIR_ADCL (6) +#define VCM_ADCL (5) +#define AIL_ADCR (4) +#define AIR_ADCR (3) +#define VCM_ADCR (2) + +/* DACR */ +#define DACL_EN (7) +#define DACR_EN (6) +#define DACBUF_I (5) + +/* DAOCR1 */ +#define ADCL_P_HPL (7) +#define ADCL_N_HPR (6) +#define ADCR_P_HPL (5) +#define ADCR_P_HPR (4) +#define DACL_P_HPL (3) +#define DACL_N_HPR (2) +#define DACR_P_HPL (1) +#define DACR_P_HPR (0) + +/* DAOCR2 */ +#define ADCL_P_AOLP (7) +#define ADCL_N_AOLN (6) +#define ADCR_P_AOLP (5) +#define ADCR_N_AOLN (4) +#define DACL_P_AOLP (3) +#define DACL_N_AOLN (2) +#define DACR_P_AOLP (1) +#define DACR_N_AOLN (0) + +/* DAOCR3 */ +#define ADCL_P_AORP (7) +#define ADCL_N_AORN (6) +#define ADCR_P_AORP (5) +#define ADCR_N_AORN (4) +#define DACL_P_AORP (3) +#define DACL_N_AORN (2) +#define DACR_P_AORP (1) +#define DACR_N_AORN (0) + +/* DAOCR4 */ +#define DACL_P_EARP (7) +#define DACL_N_EARN (6) + +/* DCR1 */ +#define HPL_EN (7) +#define HPR_EN (6) +#define EAR_EN (5) +#define AOL_EN (4) +#define AOR_EN (3) +#define DIFF_EN (2) + +/* DCR2 */ +#define PA_D_EN (7) +#define PA_DTRI_F (5) +#define PA_DTRI_F_MASK (0x03) +#define PA_DEMI_EN (4) +#define PA_SS_EN (3) +#define PA_SS_RST (2) +#define DRV_STOP_EN (1) + +/* DCR3 */ +#define DRV_OCP_AOL_PD (7) +#define DRV_OCP_AOR_PD (6) +#define DRV_OCP_EAR_PD (5) +#define DRV_OCP_HP_PD (4) + +/* PNRCR1 */ +#define HP_POP_CTL (6) +#define HP_POP_CTL_MASK (0x03) +#define HP_POP_CTL_DIS (0) +#define HP_POP_CTL_UP (1) +#define HP_POP_CTL_DOWN (2) +#define HP_POP_CTL_HOLD (3) + +#define HP_POP_STEP (3) +#define HP_POP_STEP_MASK (0x07) +#define HP_POP_STEP_012 (0) +#define HP_POP_STEP_025 (1) +#define HP_POP_STEP_05 (2) +#define HP_POP_STEP_1 (3) +#define HP_POP_STEP_2 (4) +#define HP_POP_STEP_4 (5) +#define HP_POP_STEP_8 (6) +#define HP_POP_STEP_16 (7) + +/* CCR */ +#define CLK_REVERSE (7) +#define ADC_CLK_PD (6) +#define ADC_CLK_RST (5) +#define DAC_CLK_EN (4) +#define DRV_CLK_EN (3) + +/* IFR1 */ +#define HP_POP_FLG (4) +#define HP_POP_FLG_MASK (0x03) +#define HP_POP_FLG_NEAR_CMP (3) +#define OVP_FLAG (2) + +#define SPRD_CODEC_DP_BASE (CODEC_DP_BASE) + +#define AUD_TOP_CTL (SPRD_CODEC_DP_BASE + 0x0000) +#define AUD_AUD_CTL (SPRD_CODEC_DP_BASE + 0x0004) +#define AUD_I2S_CTL (SPRD_CODEC_DP_BASE + 0x0008) +#define AUD_DAC_CTL (SPRD_CODEC_DP_BASE + 0x000C) +#define AUD_SDM_CTL0 (SPRD_CODEC_DP_BASE + 0x0010) +#define AUD_SDM_CTL1 (SPRD_CODEC_DP_BASE + 0x0014) +#define AUD_ADC_CTL (SPRD_CODEC_DP_BASE + 0x0018) +#define AUD_LOOP_CTL (SPRD_CODEC_DP_BASE + 0x001C) +#define AUD_AUD_STS0 (SPRD_CODEC_DP_BASE + 0x0020) +#define AUD_INT_CLR (SPRD_CODEC_DP_BASE + 0x0024) +#define AUD_INT_EN (SPRD_CODEC_DP_BASE + 0x0028) + +#define SPRD_CODEC_DP_END (SPRD_CODEC_DP_BASE + 0x002C) +#define IS_SPRD_CODEC_DP_RANG(reg) (((reg) >= SPRD_CODEC_DP_BASE) && ((reg) < SPRD_CODEC_DP_END)) + +#define SPRD_CODEC_AP_BASE (CODEC_AP_BASE) + +#define AUDIF_ENB (SPRD_CODEC_AP_BASE + 0x0000) +#define AUDIF_CLR (SPRD_CODEC_AP_BASE + 0x0004) +#define AUDIF_SYNC_CTL (SPRD_CODEC_AP_BASE + 0x0008) +#define AUDIF_OCPTMR_CTL (SPRD_CODEC_AP_BASE + 0x000C) +#define AUDIF_OTPTMR_CTL (SPRD_CODEC_AP_BASE + 0x0010) +#define AUDIF_SHUTDOWN_CTL (SPRD_CODEC_AP_BASE + 0x0014) +#define AUDIF_INT_EN (SPRD_CODEC_AP_BASE + 0x0018) +#define AUDIF_INT_CLR (SPRD_CODEC_AP_BASE + 0x001C) +#define AUDIF_INT_RAW (SPRD_CODEC_AP_BASE + 0x0020) +#define AUDIF_INT_MASK (SPRD_CODEC_AP_BASE + 0x0024) +#define AUDIF_OVPTMR_CTL (SPRD_CODEC_AP_BASE + 0x0028) +/* 0x002C ~ 0x003C is reserved for ADIE digital part */ + +#define PMUR1 (SPRD_CODEC_AP_BASE + 0x0040) +#define PMUR2 (SPRD_CODEC_AP_BASE + 0x0044) +#define PMUR3 (SPRD_CODEC_AP_BASE + 0x0048) +#define PMUR4 (SPRD_CODEC_AP_BASE + 0x004C) +#define PMUR5 (SPRD_CODEC_AP_BASE + 0x0050) +#define PMUR6 (SPRD_CODEC_AP_BASE + 0x0054) + +#define HIBDR (SPRD_CODEC_AP_BASE + 0x0058) + +#define AACR1 (SPRD_CODEC_AP_BASE + 0x005C) +#define AACR2 (SPRD_CODEC_AP_BASE + 0x0060) +#define AAICR1 (SPRD_CODEC_AP_BASE + 0x0064) +#define AAICR2 (SPRD_CODEC_AP_BASE + 0x0068) +#define AAICR3 (SPRD_CODEC_AP_BASE + 0x006C) +#define ACGR (SPRD_CODEC_AP_BASE + 0x0070) + +#define DACR (SPRD_CODEC_AP_BASE + 0x0074) +#define DAOCR1 (SPRD_CODEC_AP_BASE + 0x0078) +#define DAOCR2 (SPRD_CODEC_AP_BASE + 0x007C) +#define DAOCR3 (SPRD_CODEC_AP_BASE + 0x0080) +#define DAOCR4 (SPRD_CODEC_AP_BASE + 0x00BC) +#define DCR1 (SPRD_CODEC_AP_BASE + 0x0084) +#define DCR2 (SPRD_CODEC_AP_BASE + 0x0088) +#define DCR3 (SPRD_CODEC_AP_BASE + 0x008C) +#define DCR4 (SPRD_CODEC_AP_BASE + 0x0090) +#define DCGR1 (SPRD_CODEC_AP_BASE + 0x0094) +#define DCGR2 (SPRD_CODEC_AP_BASE + 0x0098) +#define DCGR3 (SPRD_CODEC_AP_BASE + 0x009C) + +#define PNRCR1 (SPRD_CODEC_AP_BASE + 0x00A0) +#define PNRCR2 (SPRD_CODEC_AP_BASE + 0x00A4) +#define PNRCR3 (SPRD_CODEC_AP_BASE + 0x00A8) + +#define CCR (SPRD_CODEC_AP_BASE + 0x00AC) + +#define IFR1 (SPRD_CODEC_AP_BASE + 0x00B0) +#define IFR2 (SPRD_CODEC_AP_BASE + 0x00B4) +#define IFR3 (SPRD_CODEC_AP_BASE + 0x00B8) + +#define SPRD_CODEC_AP_END (SPRD_CODEC_AP_BASE + 0x00C0) +#define IS_SPRD_CODEC_AP_RANG(reg) (((reg) >= SPRD_CODEC_AP_BASE) && ((reg) < SPRD_CODEC_AP_END)) + +#endif /* __SPRD_CODEC_H */ diff --git a/sound/soc/sprd/codec/sprd/v3/Kconfig b/sound/soc/sprd/codec/sprd/v3/Kconfig new file mode 100755 index 00000000..588501bb --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/Kconfig @@ -0,0 +1,15 @@ +config SND_SOC_SPRD_CODEC_V3 + tristate # "Sprd-codec-v3" + +config SND_SOC_SPRD_AUDIO_USE_INTER_HP_PA + depends on SND_SOC_SPRD_CODEC_V3 + bool "Use spreadtrum internal HeadPhone PA" + help + Say Y if you want use sprdtrume internal headphone pa + +config SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + depends on SND_SOC_SPRD_CODEC_V3 + bool "Use spreadtrum ear jack type 1 or 3" + help + Say Y if you want to use sprdtrume ear jack type 1 or 3 + diff --git a/sound/soc/sprd/codec/sprd/v3/Makefile b/sound/soc/sprd/codec/sprd/v3/Makefile new file mode 100644 index 00000000..16244710 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/Makefile @@ -0,0 +1,7 @@ +#SPRD CODEC + +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/codec + +snd-soc-sprd-sprd-codec-v3-objs := sprd-codec.o sprd-audio-power.o +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V3) += snd-soc-sprd-sprd-codec-v3.o diff --git a/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.c b/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.c new file mode 100644 index 00000000..73d86615 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.c @@ -0,0 +1,654 @@ +/* + * sound/soc/sprd/codec/sprd/v3/sprd-audio-power.c + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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("POWER") fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#include <soc/sprd/sprd-audio.h> +#include "sprd-audio-power.h" +#include "sprd-asoc-common.h" + +#ifdef CONFIG_AUDIO_POWER_ALLOW_UNSUPPORTED +#define UNSUP_MASK 0x0000 +#else +#define UNSUP_MASK 0x8000 +#endif + +#define UNSUP(x) (UNSUP_MASK | (x)) +#define IS_UNSUP(x) (UNSUP_MASK & (x)) +#define LDO_MV(x) (~UNSUP_MASK & (x)) + +struct sprd_audio_power_info { + int id; + /* enable/disable register information */ + int en_reg; + int en_bit; + int (*power_enable) (void); + int (*power_disable) (void); + /* configure deepsleep cut off or not */ + int (*sleep_ctrl) (void *, int); + /* voltage level register information */ + int v_reg; + int v_mask; + int v_shift; + int v_table_len; + const u16 *v_table; + int min_uV; + int max_uV; + + /* unit: us */ + int on_delay; + int off_delay; + + /* used by regulator core */ + struct regulator_desc desc; + + void *data; +}; + +#define SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, d_id, \ +e_reg, e_bit, p_enable, p_disable, s_ctrl, \ +reg, mask, shift, table, table_size, d_on_delay, d_off_delay) \ +static struct regulator_consumer_supply label[] = { \ + { .supply = #label },\ +}; \ +static struct regulator_init_data label##_data = { \ + .supply_regulator = r_supply, \ + .constraints = { \ + .name = "SRG_" #label, \ + }, \ + .num_consumer_supplies = ARRAY_SIZE(label), \ + .consumer_supplies = label, \ +}; \ +static struct sprd_audio_power_info SPRD_AUDIO_POWER_INFO_##label = { \ + .id = d_id, \ + .en_reg = e_reg, \ + .en_bit = e_bit, \ + .power_enable = p_enable, \ + .power_disable = p_disable, \ + .sleep_ctrl = s_ctrl, \ + .v_reg = reg, \ + .v_mask = mask, \ + .v_shift = shift, \ + .v_table_len = table_size, \ + .v_table = table, \ + .on_delay = d_on_delay, \ + .off_delay = d_off_delay, \ + .desc = { \ + .name = #label, \ + .id = SPRD_AUDIO_POWER_##label, \ + .n_voltages = table_size, \ + .ops = &sprd_audio_power_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ +} + +#define SPRD_AUDIO_POWER_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, ARRAY_SIZE(v_table), on_delay, off_delay) + +#define SPRD_AUDIO_POWER_SIMPLE_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + 0, 0, 0, 0, 0, on_delay, off_delay) + +#define SPRD_AUDIO_POWER_REG_LDO(label, id, power_enable, power_disable) \ +SPRD_AUDIO_POWER_LDO_LOW(label, 0, id, 0, 0, power_enable, power_disable, \ + 0, 0, 0, 0, 0, 0, 0, 0) + +static inline int sprd_power_read(struct sprd_audio_power_info *info, int reg) +{ + return arch_audio_power_read(reg); +} + +static inline int +sprd_power_write(struct sprd_audio_power_info *info, int reg, int val) +{ + int ret = 0; + ret = arch_audio_power_write(reg, val); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + arch_audio_power_read(reg)); + return ret; +} + +static inline int +sprd_power_u_bits(struct sprd_audio_power_info *info, + unsigned int reg, unsigned int mask, unsigned int value) +{ + int ret = 0; + ret = arch_audio_power_write_mask(reg, value, mask); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, value, + arch_audio_power_read(reg)); + return ret; +} + +static int sprd_audio_power_is_enabled(struct regulator_dev *rdev) +{ + int is_enable; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Is Enabled\n", rdev->desc->name); + + if (info->en_reg) { + is_enable = sprd_power_read(info, info->en_reg) & info->en_bit; + } else { + is_enable = info->en_bit; + } + + return ! !is_enable; +} + +static int audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + BUG_ON(!info->v_table); + + for (vsel = 0; vsel < info->v_table_len; vsel++) { + int mV = info->v_table[vsel]; + int uV; + + if (IS_UNSUP(mV)) + continue; + uV = LDO_MV(mV) * 1000; + + /* use the first in-range value */ + if (min_uV <= uV && uV <= max_uV) { + sp_asoc_pr_dbg("%s: %dus\n", __func__, uV); + sprd_power_u_bits(info, + info->v_reg, + info->v_mask << info->v_shift, + vsel << info->v_shift); + + return vsel; + } + } + return -EDOM; +} + +static int sprd_audio_power_enable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Enable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, + info->en_reg, info->en_bit, + info->en_bit); + } else { + if (info->power_enable) + ret = info->power_enable(); + info->en_bit = 1; + } + + udelay(info->on_delay); + + if (info->v_table) { + if (info->min_uV || info->max_uV) + audio_power_set_voltage(rdev, info->min_uV, + info->max_uV); + } + return ret; +} + +static int sprd_audio_power_disable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Disable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, info->en_reg, info->en_bit, 0); + } else { + if (info->power_disable) + ret = info->power_disable(); + info->en_bit = 0; + } + + udelay(info->off_delay); + + return ret; +} + +static int sprd_audio_power_get_status(struct regulator_dev *rdev) +{ + sp_asoc_pr_dbg("%s Get Status\n", rdev->desc->name); + + if (!sprd_audio_power_is_enabled(rdev)) + return REGULATOR_STATUS_OFF; + return REGULATOR_STATUS_NORMAL; +} + +static int sprd_audio_power_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + int ret = 0; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Set Mode 0x%x\n", rdev->desc->name, mode); + + if (!info->sleep_ctrl) { + return ret; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = info->sleep_ctrl(info, 0); + break; + case REGULATOR_MODE_STANDBY: + ret = info->sleep_ctrl(info, 1); + break; + default: + return -EINVAL; + } + return ret; +} + +static const u16 VCOM_VSEL_table[] = { + 2900, 3100, 3200, 3300, + 3400, 3500, 3600, 3800, +}; + +static const u16 VPA_VSEL_table[] = { + 2900, 3100, 3200, 3300, + 3400, 3500, 3600, 3800, +}; + +static const u16 VMIC_VSEL_table[] = { + 1700, 2210, 1900, 2470, + 2100, 2730, 2300, 3000, +}; + +static const u16 VBG_IBAIS_VSEL_table[] = { + 150, 200, 250, 300, +}; + +static const u16 HIB_VSEL_table[] = { + 1000, 950, 900, 850, 800, 750, + 700, 650, 600, 550, 500, 450, 400, +}; + +static int sprd_audio_power_list_voltage(struct regulator_dev *rdev, + unsigned index) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int mV; + + sp_asoc_pr_dbg("%s List Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + if (index >= info->v_table_len) { + return 0; + } + + mV = info->v_table[index]; + return IS_UNSUP(mV) ? 0 : (LDO_MV(mV) * 1000); +} + +static int +sprd_audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_info("%s Set Voltage %d--%d\n", rdev->desc->name, min_uV, + max_uV); + + if (!info->v_table) { + return 0; + } + info->min_uV = min_uV; + info->max_uV = max_uV; + + vsel = audio_power_set_voltage(rdev, min_uV, max_uV); + if (vsel != -EDOM) + *selector = vsel; + + return vsel; +} + +static int sprd_audio_power_get_voltage(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_dbg("%s Get Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + vsel = (sprd_power_read(info, info->v_reg) & info->v_mask) + >> info->v_shift; + return LDO_MV(info->v_table[vsel]) * 1000; +} + +static struct regulator_ops sprd_audio_power_ops = { + .list_voltage = sprd_audio_power_list_voltage, + + .set_voltage = sprd_audio_power_set_voltage, + .get_voltage = sprd_audio_power_get_voltage, + + .enable = sprd_audio_power_enable, + .disable = sprd_audio_power_disable, + .is_enabled = sprd_audio_power_is_enabled, + + .set_mode = sprd_audio_power_set_mode, + + .get_status = sprd_audio_power_get_status, +}; + +static int sprd_audio_power_sleep_ctrl(void *data, int en) +{ + struct sprd_audio_power_info *info = data; + int reg, mask, value; + switch (info->desc.id) { + case SPRD_AUDIO_POWER_HEADMICBIAS: + reg = PMUR2_PMUR1; + mask = BIT(HEADMIC_SLEEP_EN); + value = en ? mask : 0; + return sprd_power_u_bits(info, reg, mask, value); + break; + default: + BUG_ON(0); + } + return 0; +} + +static inline int vreg_enable(void) +{ + int ret = 0; + static int i = 0; + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + arch_audio_codec_analog_reg_enable(); + if (i == 0) { + arch_audio_codec_analog_reset(); + /*set initial value from xun.zhang*/ + arch_audio_codec_write_mask(PMUR4_PMUR3, BIT(ICM_PLUS_EN), + BIT(ICM_PLUS_EN)); + i = 1; + } + return ret; +} + +static inline int vreg_disable(void) +{ + /*arch_audio_codec_reset();*/ + arch_audio_codec_analog_reg_disable(); + return 0; +} + +/* + * We list regulators here if systems need some level of + * software control over them after boot. + */ +SPRD_AUDIO_POWER_REG_LDO(VREG, 1, vreg_enable, vreg_disable); +SPRD_AUDIO_POWER_SIMPLE_LDO(VB, "VREG", 2, PMUR2_PMUR1, BIT(VB_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(BG, "VB", 3, PMUR4_PMUR3, BIT(BG_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(BG_IBIAS, "BG", 4, PMUR4_PMUR3, BIT(BG_IBIAS_EN), NULL, + PMUR4_PMUR3, BG_I_MASK, BG_I, VBG_IBAIS_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_LDO(VCOM, "BG_IBIAS", 5, PMUR4_PMUR3, BIT(VCM_EN), NULL, + PMUR4_PMUR3, VCM_V_MASK, VCM_V, VCOM_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VCOM_BUF, "VCOM", 6, PMUR4_PMUR3, BIT(VCM_BUF_EN), + NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VBO, "VB", 7, PMUR2_PMUR1, BIT(VBO_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(VMICBIAS, "VCOM", 8, 0, 0, NULL, + PMUR2_PMUR1, MICBIAS_V_MASK, MICBIAS_V, VMIC_VSEL_table, 0, + 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(MICBIAS, "VMICBIAS", 9, PMUR4_PMUR3, + BIT(MICBIAS_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(AUXMICBIAS, "VMICBIAS", 10, PMUR4_PMUR3, + BIT(AUXMICBIAS_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(HEADMICBIAS, "VMICBIAS", 11, PMUR2_PMUR1, + BIT(HEADMICBIAS_EN), sprd_audio_power_sleep_ctrl, + HIBDR2_HIBDR1, HIB_SBUT_MASK, HIB_SBUT, HIB_VSEL_table, 0, + 0); + +#define SPRD_OF_MATCH(comp, label) \ + { \ + .compatible = comp, \ + .data = &SPRD_AUDIO_POWER_INFO_##label, \ + } + +static const struct of_device_id sprd_audio_power_of_match[] = { + SPRD_OF_MATCH("sp,audio-vreg", VREG), + SPRD_OF_MATCH("sp,audio-vb", VB), + SPRD_OF_MATCH("sp,audio-bg", BG), + SPRD_OF_MATCH("sp,audio-bg-ibias", BG_IBIAS), + SPRD_OF_MATCH("sp,audio-vcom", VCOM), + SPRD_OF_MATCH("sp,audio-vcom-buf", VCOM_BUF), + SPRD_OF_MATCH("sp,audio-vbo", VBO), + SPRD_OF_MATCH("sp,audio-vmicbias", VMICBIAS), + SPRD_OF_MATCH("sp,audio-micbias", MICBIAS), + SPRD_OF_MATCH("sp,audio-auxmicbias", AUXMICBIAS), + SPRD_OF_MATCH("sp,audio-headmicbias", HEADMICBIAS), +}; + +static int sprd_audio_power_probe(struct platform_device *pdev) +{ + int i, id; + struct sprd_audio_power_info *info; + const struct sprd_audio_power_info *template; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + struct regulator_config config = { }; + int min_index = 0; + int max_index = 0; + + id = pdev->id; + + sp_asoc_pr_info("Probe %d\n", id); + + initdata = pdev->dev.platform_data; + for (i = 0, template = NULL; i < ARRAY_SIZE(sprd_audio_power_of_match); + i++) { + template = sprd_audio_power_of_match[i].data; + if (!template || template->desc.id != id) + continue; + break; + } + + if (!template) + return -ENODEV; + + if (!initdata) + return -EINVAL; + + info = kmemdup(template, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask |= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS; + + if (info->v_table) { + for (i = 0; i < info->v_table_len; i++) { + if (info->v_table[i] < info->v_table[min_index]) + min_index = i; + if (info->v_table[i] > info->v_table[max_index]) + max_index = i; + } + c->min_uV = LDO_MV((info->v_table)[min_index]) * 1000; + c->max_uV = LDO_MV((info->v_table)[max_index]) * 1000; + sp_asoc_pr_info("min_uV:%d, max_uV:%d\n", c->min_uV, c->max_uV); + } + info->min_uV = 0; + info->max_uV = 0; + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = info; + config.of_node = pdev->dev.of_node; + + rdev = regulator_register(&info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "ERR:Can't Register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + kfree(info); + return PTR_ERR(rdev); + } + sp_asoc_pr_info("Register %s Success!\n", info->desc.name); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int sprd_audio_power_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct twlreg_info *info = rdev->reg_data; + + regulator_unregister(platform_get_drvdata(pdev)); + kfree(info); + return 0; +} + +static struct platform_driver sprd_audio_power_driver = { + .driver = { + .name = "sprd-audio-power", + .owner = THIS_MODULE, + }, + .probe = sprd_audio_power_probe, + .remove = sprd_audio_power_remove, +}; + +static int __init sprd_audio_power_regulator_init(void) +{ + return platform_driver_register(&sprd_audio_power_driver); +} + +postcore_initcall(sprd_audio_power_regulator_init); + +static void __exit sprd_audio_power_regulator_exit(void) +{ + platform_driver_unregister(&sprd_audio_power_driver); +} + +module_exit(sprd_audio_power_regulator_exit); + +MODULE_DESCRIPTION("SPRD audio power regulator driver"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_ALIAS("platform:sprd-audio-power"); +MODULE_LICENSE("GPL"); + +#define SPRD_AUDIO_DEVICE(label) \ +{ \ + .name = "sprd-audio-power", \ + .id = SPRD_AUDIO_POWER_##label, \ + .dev = { \ + .platform_data = &label##_data, \ + }, \ +} + +static struct platform_device sprd_audio_regulator_devices[] = { + SPRD_AUDIO_DEVICE(VREG), + SPRD_AUDIO_DEVICE(VB), + SPRD_AUDIO_DEVICE(BG), + SPRD_AUDIO_DEVICE(BG_IBIAS), + SPRD_AUDIO_DEVICE(VCOM), + SPRD_AUDIO_DEVICE(VCOM_BUF), + SPRD_AUDIO_DEVICE(VBO), + SPRD_AUDIO_DEVICE(VMICBIAS), + SPRD_AUDIO_DEVICE(MICBIAS), + SPRD_AUDIO_DEVICE(AUXMICBIAS), + SPRD_AUDIO_DEVICE(HEADMICBIAS), +}; + +#ifdef CONFIG_OF +static int sprd_audio_power_version(void) +{ + struct device_node *node = of_find_node_by_path("/sprd-audio-devices"); + struct device_node *child; + if (node) { + u32 val; + for_each_child_of_node(node, child) { + if (!of_property_read_u32 + (child, "sprd,audio_power_ver", &val)) { + sp_asoc_pr_dbg + ("Configure Audio Power Version is %d\n", + val); + return val; + } + } + pr_err("ERR:Not found sprd,audio_power_ver!\n"); + } else { + pr_err("ERR:No sprd-audio-devices Node!\n"); + } + return 0; +} +#else +int sprd_audio_power_version(void) + __attribute__ ((weak, alias("__sprd_audio_power_version"))); + +static int __sprd_audio_power_version(void) +{ + return 3; +} +#endif + +static int sprd_audio_power_add_devices(void) +{ + int ret = 0; + int i; + if (3 != sprd_audio_power_version()) { + return 0; + } + for (i = 0; i < ARRAY_SIZE(sprd_audio_regulator_devices); i++) { + ret = + platform_device_register(&sprd_audio_regulator_devices[i]); + if (ret < 0) { + pr_err("ERR:Add Regulator %d Failed %d\n", + sprd_audio_regulator_devices[i].id, ret); + return ret; + } + } + return ret; +} + +static int __init sprd_audio_power_init(void) +{ + return sprd_audio_power_add_devices(); +} + +device_initcall(sprd_audio_power_init); diff --git a/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.h b/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.h new file mode 100755 index 00000000..ecdfac5b --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/sprd-audio-power.h @@ -0,0 +1,35 @@ +/* + * sound/soc/sprd/codec/sprd/v3/sprd-audio-power.h + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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. + */ + +#ifndef __SPRD_AUDIO_POWER_H +#define __SPRD_AUDIO_POWER_H + +#include "sprd-codec.h" + +#define SPRD_AUDIO_POWER_VREG 1 +#define SPRD_AUDIO_POWER_VB 2 +#define SPRD_AUDIO_POWER_BG 3 +#define SPRD_AUDIO_POWER_BG_IBIAS 4 +#define SPRD_AUDIO_POWER_VCOM 5 +#define SPRD_AUDIO_POWER_VCOM_BUF 6 +#define SPRD_AUDIO_POWER_VBO 7 +#define SPRD_AUDIO_POWER_VMICBIAS 8 +#define SPRD_AUDIO_POWER_MICBIAS 9 +#define SPRD_AUDIO_POWER_AUXMICBIAS 10 +#define SPRD_AUDIO_POWER_HEADMICBIAS 11 + +#endif diff --git a/sound/soc/sprd/codec/sprd/v3/sprd-codec.c b/sound/soc/sprd/codec/sprd/v3/sprd-codec.c new file mode 100755 index 00000000..8d778aaf --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/sprd-codec.c @@ -0,0 +1,3858 @@ +/* + * sound/soc/sprd/codec/sprd/v3/sprd-codec.c + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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("CDCV3") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/atomic.h> +#include <linux/regulator/consumer.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/power_supply.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/sprd-audio-hook.h> + +#include <soc/sprd/sprd-audio.h> +#include <soc/sprd/globalregs.h> +#include "sprd-codec.h" +#include "sprd-asoc-common.h" +#include <soc/sprd/arch_misc.h> +#include <linux/of_irq.h> + +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE +#if (CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE != 32000) && (CONFIG_SND_SOC_CODEC_SRC_SAMPLE_RATE != 48000) +#error "We hope the CODEC sample rate is 32KHz or 48KHz!" +#endif +#define SRC_SUPPORT_RATE (SNDRV_PCM_RATE_44100) +#else /* !CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE */ +#define SRC_SUPPORT_RATE (0) +#endif + +int hp_register_notifier(struct notifier_block *nb); +int hp_unregister_notifier(struct notifier_block *nb); +int get_hp_plug_state(void); + +#define SOC_REG(r) ((unsigned short)(r)) +#define FUN_REG(f) ((unsigned short)(-((f) + 1))) +#define ID_FUN(id, lr) ((unsigned short)(((id) << 1) | (lr))) + +#define SPRD_CODEC_AP_BASE_HI (SPRD_CODEC_AP_BASE & 0xFFFF0000) +#define SPRD_CODEC_DP_BASE_HI (SPRD_CODEC_DP_BASE & 0xFFFF0000) + +enum { + SPRD_CODEC_HP_PA_ORDER = 100, + SPRD_CODEC_HP_SWITCH_ORDER = 101, + SPRD_CODEC_ANA_MIXER_ORDER = 102, /*must be later with switch order*/ + SPRD_CODEC_HP_PA_POST_ORDER = 103, + SPRD_CODEC_HP_POP_ORDER = 104, + SPRD_CODEC_PA_ORDER = 104, /*last power up, later with ANA_MIXER order*/ +}; + +enum { + SPRD_CODEC_PLAYBACK, + SPRD_CODEC_CAPTRUE, + SPRD_CODEC_CAPTRUE1, + SPRD_CODEC_CHAN_MAX, +}; + +static const char *sprd_codec_chan_name[SPRD_CODEC_CHAN_MAX] = { + "DAC", + "ADC", + "ADC1", +}; + +static inline const char *sprd_codec_chan_get_name(int chan_id) +{ + return sprd_codec_chan_name[chan_id]; +} + +enum { + SPRD_CODEC_PGA_START = 0, + SPRD_CODEC_PGA_SPKL = SPRD_CODEC_PGA_START, + SPRD_CODEC_PGA_SPKR, + SPRD_CODEC_PGA_HPL, + SPRD_CODEC_PGA_HPR, + SPRD_CODEC_PGA_EAR, + SPRD_CODEC_PGA_ADCL, + SPRD_CODEC_PGA_ADCR, + SPRD_CODEC_PGA_DACL, + SPRD_CODEC_PGA_DACR, + SPRD_CODEC_PGA_MIC, + SPRD_CODEC_PGA_AUXMIC, + SPRD_CODEC_PGA_HEADMIC, + SPRD_CODEC_PGA_AIL, + SPRD_CODEC_PGA_AIR, + + SPRD_CODEC_PGA_END +}; + +#define GET_PGA_ID(x) ((x)-SPRD_CODEC_PGA_START) +#define SPRD_CODEC_PGA_MAX (SPRD_CODEC_PGA_END - SPRD_CODEC_PGA_START) +static const char *sprd_codec_pga_debug_str[SPRD_CODEC_PGA_MAX] = { + "SPKL", + "SPKR", + "HPL", + "HPR", + "EAR", + "ADCL", + "ADCR", + "DACL", + "DACR", + "MIC", + "AUXMIC", + "HEADMIC", + "AIL", + "AIR", +}; + +typedef int (*sprd_codec_pga_set) (struct snd_soc_codec * codec, int pgaval); + +struct sprd_codec_pga { + sprd_codec_pga_set set; + int min; +}; + +struct sprd_codec_pga_op { + int pgaval; + sprd_codec_pga_set set; +}; + +enum { + SPRD_CODEC_LEFT = 0, + SPRD_CODEC_RIGHT = 1, +}; + +enum { + SPRD_CODEC_MIXER_START = SPRD_CODEC_PGA_END + 20, + SPRD_CODEC_AIL = SPRD_CODEC_MIXER_START, + SPRD_CODEC_AIR, + SPRD_CODEC_MAIN_MIC, + SPRD_CODEC_AUX_MIC, + SPRD_CODEC_HP_MIC, + SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACL = SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACR, + SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCL = SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCR, + SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACL = SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACR, + SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCL = SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCR, + SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_DACL = SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_MIXER_MAX, + + SPRD_CODEC_MIXER_END +}; + +#define GET_MIXER_ID(x) ((unsigned short)(x)-ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) +#define SPRD_CODEC_MIXER_MAX (ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT) -ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) +static const char *sprd_codec_mixer_debug_str[SPRD_CODEC_MIXER_MAX] = { + "AIL->ADCL", + "AIL->ADCR", + "AIR->ADCL", + "AIR->ADCR", + "MAIN MIC->ADCL", + "MAIN MIC->ADCR", + "AUX MIC->ADCL", + "AUX MIC->ADCR", + "HP MIC->ADCL", + "HP MIC->ADCR", + "DACL->HPL", + "DACL->HPR", + "DACR->HPL", + "DACR->HPR", + "ADCL->HPL", + "ADCL->HPR", + "ADCR->HPL", + "ADCR->HPR", + "DACL->SPKL", + "DACL->SPKR", + "DACR->SPKL", + "DACR->SPKR", + "ADCL->SPKL", + "ADCL->SPKR", + "ADCR->SPKL", + "ADCR->SPKR", + "DACL->EAR", + "DACR->EAR(bug)" +}; + +#define IS_SPRD_CODEC_MIXER_RANG(reg) (((reg) >= ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) && ((reg) <= ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT))) +#define IS_SPRD_CODEC_PGA_RANG(reg) (((reg) >= SPRD_CODEC_PGA_START) && ((reg) <= SPRD_CODEC_PGA_END)) +#define IS_SPRD_CODEC_MIC_BIAS_RANG(reg) (((reg) >= SPRD_CODEC_MIC_BIAS_START) && ((reg) <= SPRD_CODEC_MIC_BIAS_END)) + +typedef int (*sprd_codec_mixer_set) (struct snd_soc_codec * codec, int on); +struct sprd_codec_mixer { + int on; + sprd_codec_mixer_set set; +}; + +uint32_t sprd_get_vbat_voltage(void) + __attribute__ ((weak, alias("__sprd_get_vbat_voltage"))); +static uint32_t __sprd_get_vbat_voltage(void) +{ + pr_err("ERR: Can't get vbat!\n"); + return 3800; +} + +struct sprd_codec_ldo_v_map { + int ldo_v_level; + int volt; +}; + +const static struct sprd_codec_ldo_v_map ldo_v_map[] = { + /*{ , 3400}, */ + {LDO_V_29, 3600}, + {LDO_V_31, 3700}, + {LDO_V_32, 3800}, + {LDO_V_33, 3900}, + {LDO_V_34, 4000}, + {LDO_V_35, 4100}, + {LDO_V_36, 4200}, + {LDO_V_38, 4300}, +}; + +const static struct sprd_codec_ldo_v_map ldo_vcom_v_map[] = { + /*{ , 3400}, */ + {LDO_V_29, 3800}, + {LDO_V_31, 3900}, + {LDO_V_32, 4000}, + {LDO_V_33, 4100}, + {LDO_V_34, 4200}, + {LDO_V_35, 4300}, + {LDO_V_36, 4400}, + {LDO_V_38, 4500}, +}; + +struct sprd_codec_ldo_cfg { + const struct sprd_codec_ldo_v_map *v_map; + int v_map_size; +}; + +struct sprd_codec_inter_pa { + /* FIXME little endian */ + u32 LDO_V_sel:4; + u32 DTRI_F_sel:4; + u32 is_DEMI_mode:1; + u32 is_classD_mode:1; + u32 is_LDO_mode:1; + u32 is_auto_LDO_mode:1; + u32 RESV:20; +}; + +struct sprd_codec_pa_setting { + union { + struct sprd_codec_inter_pa setting; + u32 value; + }; + int set; +}; + +struct sprd_codec_inter_hp_pa { + /* FIXME little endian */ + u32 class_g_osc:2; + u32 class_g_mode:2; + u32 class_g_low_power:2; + u32 class_g_cgcal:2; + u32 class_g_unmute_delay_100ms:3; + u32 class_g_cgcal_delay_10ms:3; + u32 class_g_close_delay_10ms:2; + u32 class_g_open_delay_10ms:4; + u32 class_g_vdd_delay_10ms:6; + u32 class_g_chp_delay_30ms:4; + u32 class_g_all_close_delay_100ms:2; + //u32 RESV:0; +}; +struct sprd_codec_inter_hp_pa_delay { + u32 class_g_delay1:4; + u32 class_g_delay2:4; +}; + +struct sprd_codec_hp_pa_setting { + union { + struct sprd_codec_inter_hp_pa setting; + u32 value; + }; + union{ + struct sprd_codec_inter_hp_pa_delay delay_setting; + u32 delay_value; + }; + int set; +}; + +enum { + SPRD_CODEC_MIC_BIAS_START = + ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT) + 100, + SPRD_CODEC_MIC_BIAS = SPRD_CODEC_MIC_BIAS_START, + SPRD_CODEC_AUXMIC_BIAS, + SPRD_CODEC_HEADMIC_BIAS, + + SPRD_CODEC_MIC_BIAS_END +}; + +#define GET_MIC_BIAS_ID(x) ((x)-SPRD_CODEC_MIC_BIAS_START) +#define SPRD_CODEC_MIC_BIAS_MAX (SPRD_CODEC_MIC_BIAS_END -SPRD_CODEC_MIC_BIAS_START) +static const char *mic_bias_name[SPRD_CODEC_MIC_BIAS_MAX] = { + "Mic Bias", + "AuxMic Bias", + "HeadMic Bias", +}; + +enum { + SPRD_CODEC_HP_PA_VER_1, + SPRD_CODEC_HP_PA_VER_2, + SPRD_CODEC_HP_PA_VER_MAX, +}; + +static unsigned long sprd_codec_dp_base = 0; +/* codec private data */ +struct sprd_codec_priv { + struct snd_soc_codec *codec; + int da_sample_val; + int ad_sample_val; + int ad1_sample_val; + struct sprd_codec_mixer mixer[SPRD_CODEC_MIXER_MAX]; + struct sprd_codec_pga_op pga[SPRD_CODEC_PGA_MAX]; + int mic_bias[SPRD_CODEC_MIC_BIAS_MAX]; + + int ap_irq; + struct completion completion_hp_pop; + + int dp_irq; + struct completion completion_dac_mute; + struct power_supply audio_ldo; + + struct sprd_codec_pa_setting inter_pa; + struct mutex inter_pa_mutex; + + int hp_ver; + struct sprd_codec_hp_pa_setting inter_hp_pa; + int class_g_delay1; + int class_g_delay2; + struct mutex inter_hp_pa_mutex; + struct mutex inter_hp_pa_delay_mutex; + + struct regulator *main_mic; + struct regulator *aux_mic; + struct regulator *head_mic; + atomic_t ldo_refcount; + struct delayed_work ovp_delayed_work; + + struct sprd_codec_ldo_cfg s_vcom; + struct sprd_codec_ldo_cfg s_vpa; + +#define SPRD_CODEC_PA_SW_AOL (BIT(0)) +#define SPRD_CODEC_PA_SW_EAR (BIT(1)) +#define SPRD_CODEC_PA_SW_FUN (SPRD_CODEC_PA_SW_AOL | SPRD_CODEC_PA_SW_EAR) + int sprd_codec_fun; + spinlock_t sprd_codec_fun_lock; + spinlock_t sprd_codec_pa_sw_lock; + + struct notifier_block nb; +}; + +static int sprd_codec_power_get(struct device *dev, struct regulator **regu, + const char *id) +{ + if (!*regu) { + *regu = regulator_get(dev, id); + if (IS_ERR(*regu)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(*regu), id); + *regu = 0; + return PTR_ERR(*regu); + } + } + return 0; +} + +static int sprd_codec_power_put(struct regulator **regu) +{ + if (*regu) { + regulator_set_mode(*regu, REGULATOR_MODE_NORMAL); + regulator_disable(*regu); + regulator_put(*regu); + *regu = 0; + } + return 0; +} + +static int sprd_codec_mic_bias_set(struct regulator **regu, int on) +{ + int ret = 0; + if (*regu) { + if (on) { + ret = regulator_enable(*regu); + if (ret) { + sprd_codec_power_put(regu); + return ret; + } + } else { + ret = regulator_disable(*regu); + } + } + return ret; +} + +#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 void sprd_codec_set_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun |= fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static void sprd_codec_clr_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun &= ~fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static int sprd_codec_test_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + int ret; + spin_lock(&sprd_codec->sprd_codec_fun_lock); + ret = sprd_codec->sprd_codec_fun & fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); + return ret; +} + +static void sprd_codec_wait(u32 wait_time) +{ + if (wait_time) + schedule_timeout_uninterruptible(msecs_to_jiffies(wait_time)); +} + +#if 0 /* sometime we will use this print function */ +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg); +static void sprd_codec_print_regs(struct snd_soc_codec *codec) +{ + int reg; + pr_warn("sprd_codec register digital part\n"); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_DP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } + pr_warn("sprd_codec register analog part\n"); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_AP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } +} +#endif + +static inline int _sprd_codec_ldo_cfg_set(struct sprd_codec_ldo_cfg *p_v_cfg, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + p_v_cfg->v_map = v_map; + p_v_cfg->v_map_size = v_map_size; + return 0; +} + +static inline int _sprd_codec_hold(int index, int volt, + const struct sprd_codec_ldo_v_map *v_map) +{ + int scope = 40; /* unit: mv */ + int ret = ((v_map[index].volt - volt) <= scope); + if (index >= 1) { + return (ret || ((volt - v_map[index - 1].volt) <= scope)); + } + return ret; +} + +static int sprd_codec_auto_ldo_volt(struct snd_soc_codec *codec, + void (*set_level) (struct snd_soc_codec *, + int), + const struct sprd_codec_ldo_v_map *v_map, + const int v_map_size, int init) +{ + int i; + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("Get vbat voltage %d\n", volt); + for (i = 0; i < v_map_size; i++) { + if (volt <= ldo_v_map[i].volt) { + sp_asoc_pr_dbg("Hold %d\n", + _sprd_codec_hold(i, volt, v_map)); + if (init || !_sprd_codec_hold(i, volt, v_map)) { + set_level(codec, v_map[i].ldo_v_level); + } + return 0; + } + } + return -EFAULT; +} + +static inline void sprd_codec_vcm_v_sel(struct snd_soc_codec *codec, int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("VCM Set %d\n", v_sel); + mask = VCM_V_MASK << VCM_V; + val = (v_sel << VCM_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR4_PMUR3), mask, val); +} + +static inline void sprd_codec_auxadc_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("AUXADC Set %d\n", on); + mask = BIT(AUXADC_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR4_PMUR3), mask, val); +} + +static int sprd_codec_pga_spk_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR3; + val = (pgaval & 0xF) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_spkr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR3; + val = pgaval & 0xF; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0F, val); +} + +static int sprd_codec_pga_hpl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR2_DCGR1; + val = (pgaval & 0xF) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF0, val); +} + +static int sprd_codec_pga_hpr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR2_DCGR1; + val = pgaval & 0xF; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0F, val); +} + +static int sprd_codec_pga_ear_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DCGR2_DCGR1; + val = ((pgaval & 0xF) << 12); + return snd_soc_update_bits(codec, SOC_REG(reg), 0xF000, val); +} + +static int sprd_codec_pga_adcl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR3_ACGR2; + val = pgaval & 0x3F; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x3F, val); +} + +static int sprd_codec_pga_adcr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR3_ACGR2; + val = (pgaval & 0x3F) << 8; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x3F00, val); +} + +static int sprd_codec_pga_dacl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DACGR_DACR; + val = (pgaval & 0x07) << 12; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x7000, val); +} + +static int sprd_codec_pga_dacr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = DACGR_DACR; + val = (pgaval & 0x07) << 8; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0700, val); +} + +static int sprd_codec_pga_mic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR1; + val = (pgaval & 0x03) << 6; + return snd_soc_update_bits(codec, SOC_REG(reg), 0xC0, val); +} + +static int sprd_codec_pga_auxmic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR1; + val = (pgaval & 0x03) << 4; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x30, val); +} + +static int sprd_codec_pga_headmic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR1; + val = (pgaval & 0x03) << 2; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x0C, val); +} + +static int sprd_codec_pga_ailr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val; + reg = ACGR1; + val = pgaval & 0x03; + return snd_soc_update_bits(codec, SOC_REG(reg), 0x03, val); +} + +static struct sprd_codec_pga sprd_codec_pga_cfg[SPRD_CODEC_PGA_MAX] = { + {sprd_codec_pga_spk_set, 0}, + {sprd_codec_pga_spkr_set, 0}, + {sprd_codec_pga_hpl_set, 0}, + {sprd_codec_pga_hpr_set, 0}, + {sprd_codec_pga_ear_set, 0}, + + {sprd_codec_pga_adcl_set, 0}, + {sprd_codec_pga_adcr_set, 0}, + + {sprd_codec_pga_dacl_set, 0}, + {sprd_codec_pga_dacr_set, 0}, + {sprd_codec_pga_mic_set, 0}, + {sprd_codec_pga_auxmic_set, 0}, + {sprd_codec_pga_headmic_set, 0}, + {sprd_codec_pga_ailr_set, 0}, + {sprd_codec_pga_ailr_set, 0}, +}; + +/* adc mixer */ + +static int ailadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(AIL_ADCL), + on << AIL_ADCL); +} + +static int ailadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(AIL_ADCR), + on << AIL_ADCR); +} + +static int airadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(AIR_ADCL), + on << AIR_ADCL); +} + +static int airadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(AIR_ADCR), + on << AIR_ADCR); +} + +static int mainmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(MIC_ADCL), + on << MIC_ADCL); +} + +static int mainmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), BIT(MIC_ADCR), + on << MIC_ADCR); +} + +static int auxmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), + BIT(AUXMIC_ADCL), on << AUXMIC_ADCL); +} + +static int auxmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), + BIT(AUXMIC_ADCR), on << AUXMIC_ADCR); +} + +static int hpmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), + BIT(HEADMIC_ADCL), on << HEADMIC_ADCL); +} + +static int hpmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(AAICR2_AAICR1), + BIT(HEADMIC_ADCR), on << HEADMIC_ADCR); +} + +/* hp mixer */ + +static int daclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(DACL_P_HPL), on << DACL_P_HPL); +} + +static int daclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(DACL_N_HPR), on << DACL_N_HPR); +} + +static int dacrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(DACR_P_HPL), on << DACR_P_HPL); +} + +static int dacrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(DACR_P_HPR), on << DACR_P_HPR); +} + +static int adclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(ADCL_P_HPL), on << ADCL_P_HPL); +} + +static int adclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(ADCL_N_HPR), on << ADCL_N_HPR); +} + +static int adcrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(ADCR_P_HPL), on << ADCR_P_HPL); +} + +static int adcrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), + BIT(ADCR_P_HPR), on << ADCR_P_HPR); +} + +/* spkl mixer */ + +static int daclspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(DACL_AOL), + on << DACL_AOL); +} + +static int dacrspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(DACR_AOL), + on << DACR_AOL); +} + +static int adclspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(ADCL_AOL), + on << ADCL_AOL); +} + +static int adcrspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(ADCR_AOL), + on << ADCR_AOL); +} + +/* spkr mixer */ + +static int daclspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(DACL_AOR), + on << DACL_AOR); +} + +static int dacrspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(DACR_AOR), + on << DACR_AOR); +} + +static int adclspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(ADCL_AOR), + on << ADCL_AOR); +} + +static int adcrspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR2), BIT(ADCR_AOR), + on << ADCR_AOR); +} + +/* ear mixer */ + +static int daclear_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(DAOCR3_DAOCR1), BIT(DACL_EAR), + on << DACL_EAR); +} + +static sprd_codec_mixer_set mixer_setting[SPRD_CODEC_MIXER_MAX] = { + /* adc mixer */ + ailadcl_set, ailadcr_set, + airadcl_set, airadcr_set, + mainmicadcl_set, mainmicadcr_set, + auxmicadcl_set, auxmicadcr_set, + hpmicadcl_set, hpmicadcr_set, + /* hp mixer */ + daclhpl_set, daclhpr_set, + dacrhpl_set, dacrhpr_set, + adclhpl_set, adclhpr_set, + adcrhpl_set, adcrhpr_set, + /* spk mixer */ + daclspkl_set, daclspkr_set, + dacrspkl_set, dacrspkr_set, + adclspkl_set, adclspkr_set, + adcrspkl_set, adcrspkr_set, + /* ear mixer */ + daclear_set, 0, +}; + +/* DO NOT USE THIS FUNCTION */ +static inline void __sprd_codec_pa_sw_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + mask = BIT(PA_SW_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR2_PMUR1), mask, val); +} + +static inline void sprd_codec_pa_sw_set(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_set_fun(sprd_codec, fun); + __sprd_codec_pa_sw_en(codec, 1); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_pa_sw_clr(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_clr_fun(sprd_codec, fun); + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + __sprd_codec_pa_sw_en(codec, 0); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +/* inter PA */ + +static inline void sprd_codec_pa_d_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_D_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), mask, val); +} + +static inline void sprd_codec_pa_demi_en(struct snd_soc_codec *codec, int on) +{ + int mask; + + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + if (on) + /*when emi is on, need to set this register from xun && junjie.chen*/ + snd_soc_update_bits(codec, SOC_REG(DCR4_DCR3), 0xffff, 0x4); + else + snd_soc_update_bits(codec, SOC_REG(DCR4_DCR3), 0xffff, 0); + + mask = BIT(PA_DEMI_EN); + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), mask, on?mask:0); + + /*open ocp from xun && weifeng*/ + mask = BIT(DRV_OCP_AOL_PD) | BIT(DRV_OCP_AOR_PD); + snd_soc_update_bits(codec, SOC_REG(DCR4_DCR3), mask, 0); +} + +static inline void sprd_codec_pa_ldo_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_LDO_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR2_PMUR1), mask, val); + if (on) { + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + } +} + +static inline void sprd_codec_pa_ldo_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_LDO_V_MASK << PA_LDO_V; + val = (v_sel << PA_LDO_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR6_PMUR5), mask, val); +} + +static inline void sprd_codec_pa_ovp_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_OVP_V_MASK << PA_OVP_V; + val = (v_sel << PA_OVP_V) & mask; + snd_soc_update_bits(codec, SOC_REG(PMUR8_PMUR7), mask, val); +} + +static inline void sprd_codec_pa_dtri_f_sel(struct snd_soc_codec *codec, + int f_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, f_sel); + mask = PA_DTRI_F_MASK << PA_DTRI_F; + val = (f_sel << PA_DTRI_F) & mask; + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), mask, val); +} + +static inline void sprd_codec_pa_en(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + if (on) { + mask = BIT(PA_EN); + val = mask; + } else { + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + mask = BIT(PA_EN) | BIT(PA_SW_EN) | BIT(PA_LDO_EN); + else + mask = BIT(PA_EN) | BIT(PA_LDO_EN); + val = 0; + } + snd_soc_update_bits(codec, SOC_REG(PMUR2_PMUR1), mask, val); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_ovp_ldo_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(OVP_LDO_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR2_PMUR1), mask, val); +} + +static inline void sprd_codec_inter_pa_init(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec->inter_pa.setting.LDO_V_sel = 0x03; + sprd_codec->inter_pa.setting.DTRI_F_sel = 0x01; +} + +static void sprd_codec_ovp_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(OVP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, mask); +} + +static void sprd_codec_ovp_start(struct sprd_codec_priv *sprd_codec, int ms) +{ + schedule_delayed_work(&sprd_codec->ovp_delayed_work, + msecs_to_jiffies(ms)); +} + +static int sprd_ovp_irq(struct snd_soc_codec *codec, int is_ovp) +{ + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("OVP %d curr_volt %d\n", is_ovp, volt); + if (is_ovp) { + sprd_codec_pa_en(codec, 0); + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(AOL_EN), 0); + } else { + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(AOL_EN), + BIT(AOL_EN)); + sprd_codec_pa_en(codec, 1); + } + return 0; +} + +#define to_sprd_codec(x) container_of((x), struct sprd_codec_priv, ovp_delayed_work) +#define to_delay_worker(x) container_of((x), struct delayed_work, work) +static void sprd_codec_ovp_delay_worker(struct work_struct *work) +{ + struct sprd_codec_priv *sprd_codec = + to_sprd_codec(to_delay_worker(work)); + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!(snd_soc_read(codec, IFR2_IFR1) & BIT(OVP_FLAG))) { + } else { + sprd_codec_pa_ldo_v_sel(codec, 0x7); + } +} + +static inline int sprd_codec_pa_ldo_auto(struct snd_soc_codec *codec, int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt + (codec, sprd_codec_pa_ldo_v_sel, sprd_codec->s_vpa.v_map, + sprd_codec->s_vpa.v_map_size, init); +} + +static inline int sprd_codec_pa_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vpa, v_map, v_map_size); +} + +static int sprd_inter_speaker_pa(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_pa *p_setting = &sprd_codec->inter_pa.setting; + sp_asoc_pr_info("inter PA Switch %s\n", STR_ON_OFF(on)); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (on) { + if (sprd_codec->hp_ver != SPRD_CODEC_HP_PA_VER_2) + sprd_codec_pa_ovp_v_sel(codec, PA_OVP_435); + else + sprd_codec_pa_ovp_v_sel(codec, PA_OVP_435); + sprd_codec_pa_ldo_v_sel(codec, 0x7); + sprd_codec_ovp_ldo_en(codec,1); + sprd_codec_ovp_irq_enable(codec); + sprd_codec_pa_d_en(codec, p_setting->is_classD_mode); + sprd_codec_pa_demi_en(codec, p_setting->is_DEMI_mode); + sprd_codec_pa_ldo_en(codec, p_setting->is_LDO_mode); + if (p_setting->is_LDO_mode) { + if (p_setting->is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(codec, 1); + } else { + sprd_codec_pa_ldo_v_sel(codec, + p_setting->LDO_V_sel); + } + } else { + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + } + sprd_codec_pa_dtri_f_sel(codec, p_setting->DTRI_F_sel); + sprd_codec_pa_en(codec, 1); + sprd_codec->inter_pa.set = 1; + } else { + sprd_codec->inter_pa.set = 0; + sprd_codec_pa_en(codec, 0); + sprd_codec_wait(2); + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + sprd_codec_pa_ldo_en(codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + return 0; +} + +static int spk_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_speaker_pa(w->codec, on); +} + +#define SPRD_CODEC_HP_VER(bit) \ +do { \ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); \ + switch(sprd_codec->hp_ver) { \ + case SPRD_CODEC_HP_PA_VER_2: \ + mask = BIT(V2_##bit); \ + break; \ + case SPRD_CODEC_HP_PA_VER_1: \ + default: \ + mask = BIT(bit); \ + break; \ + } \ +} while(0) + +static inline void sprd_codec_hp_classg_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CLASSG_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_lpw(struct snd_soc_codec *codec, int lpw) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, lpw); + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + if (sci_get_ana_chip_ver() >= SC2711BA) { + mask = V2_AUDIO_CHP_LPW_MASK_BA << V2_AUDIO_CHP_LPW_BA; + val = (lpw << V2_AUDIO_CHP_LPW_BA) & mask; + } else { + mask = V2_AUDIO_CHP_LPW_MASK << V2_AUDIO_CHP_LPW; + val = (lpw << V2_AUDIO_CHP_LPW) & mask; + } + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + if (sci_get_ana_chip_ver() >= SC2713CA) { + mask = AUDIO_CHP_LPW_MASK_CA << AUDIO_CHP_LPW_CA; + val = (lpw << AUDIO_CHP_LPW_CA) & mask; + } else { + mask = AUDIO_CHP_LPW_MASK << AUDIO_CHP_LPW; + val = (lpw << AUDIO_CHP_LPW) & mask; + } + break; + } + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_mode(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + if (sci_get_ana_chip_ver() >= SC2711BA) + mask = BIT(V2_AUDIO_CHP_MODE_BA ); + else + mask = BIT(V2_AUDIO_CHP_MODE); + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + if (sci_get_ana_chip_ver() >= SC2713CA) + mask = BIT(AUDIO_CHP_MODE_CA ); + else + mask = BIT(AUDIO_CHP_MODE ); + break; + } + val = on? mask :0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_osc(struct snd_soc_codec *codec, int osc) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, osc); + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + mask = V2_AUDIO_CHP_OSC_MASK << V2_AUDIO_CHP_OSC; + val = (osc << V2_AUDIO_CHP_OSC) & mask; + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + mask = AUDIO_CHP_OSC_MASK << AUDIO_CHP_OSC; + val = (osc << AUDIO_CHP_OSC) & mask; + break; + } + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CHP_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_hpl_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CHP_HPL_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_hpr_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CHP_HPR_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_hpl_mute(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CHP_LMUTE); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_hpr_mute(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CHP_RMUTE); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_hp_pa_cgcal_en(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + SPRD_CODEC_HP_VER(AUDIO_CGCAL_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); +} + +static inline void sprd_codec_inter_hp_pa_init(struct sprd_codec_priv + *sprd_codec) +{ + sprd_codec->inter_hp_pa.setting.class_g_osc = 0x01; + sprd_codec->inter_hp_pa.setting.class_g_cgcal = 1; + sprd_codec->inter_hp_pa.setting.class_g_cgcal_delay_10ms = 3; + sprd_codec->inter_hp_pa.setting.class_g_unmute_delay_100ms = 5; + sprd_codec->inter_hp_pa.setting.class_g_close_delay_10ms = 0; + sprd_codec->inter_hp_pa.setting.class_g_open_delay_10ms = 0; + sprd_codec->inter_hp_pa.setting.class_g_vdd_delay_10ms = 0; + sprd_codec->inter_hp_pa.setting.class_g_chp_delay_30ms = 0; + sprd_codec->inter_hp_pa.setting.class_g_all_close_delay_100ms = 0; +} + + +static void sprd_inter_headphone_pa_pre(struct sprd_codec_priv *sprd_codec,int on) +{ + struct snd_soc_codec *codec = sprd_codec->codec; + static struct regulator *regulator = 0; + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + + static struct regulator *regulator_reg= 0; + +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + static struct regulator *regulator_vb= 0; + int classg_vol = 2200000; +#endif + + if (on) { + if (!regulator) { + sp_asoc_pr_dbg("%s set vddclsg on\n", __func__); + /*1.CG_EN */ + //open clk + regulator_reg = regulator_get(0, "VREG"); + if (IS_ERR(regulator_reg)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_reg), "VREG"); + BUG_ON(1); + } + regulator_set_mode(regulator_reg, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_reg) < 0) { + } else { +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + //open vb + regulator_vb = regulator_get(0, "VB"); + if (IS_ERR(regulator_vb)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_vb), "VB"); + BUG_ON(1); + } + regulator_set_mode(regulator_vb, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_vb) < 0) { + } else { +#endif + sprd_codec_hp_classg_en(codec, 1); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + } +#endif + } + //close clk + regulator_set_mode(regulator_reg, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator_reg); + regulator_put(regulator_reg); + regulator_reg = 0; + sprd_codec_wait(p_setting->class_g_open_delay_10ms * 10); + /*2. LDO PD */ + regulator = regulator_get(0, CLASS_G_LDO_ID); + if (IS_ERR(regulator)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator), CLASS_G_LDO_ID); + BUG_ON(1); + } + regulator_set_mode(regulator, REGULATOR_MODE_STANDBY); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + if (sprd_codec->hp_ver == SPRD_CODEC_HP_PA_VER_1) + classg_vol = 1200000; + regulator_set_voltage(regulator, classg_vol, classg_vol); +#endif + if (regulator_enable(regulator) < 0) { + regulator_set_mode(regulator, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator); + regulator_put(regulator); + regulator = 0; + } + } + } else{ + if (regulator) { + sp_asoc_pr_dbg("%s set vddclsg off\n", __func__); + /*LDO PD*/ + regulator_set_mode(regulator, REGULATOR_MODE_NORMAL); + regulator_disable(regulator); + regulator_put(regulator); + regulator = 0; + /*CG_EN*/ + //open clk + regulator_reg = regulator_get(0, "VREG"); + if (IS_ERR(regulator_reg)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_reg), "VREG"); + BUG_ON(1); + } + regulator_set_mode(regulator_reg, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_reg) < 0) { + } else { + sprd_codec_hp_classg_en(codec, 0); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + if(regulator_vb) { + //close vb + regulator_set_mode(regulator_vb, REGULATOR_MODE_NORMAL); + regulator_disable(regulator_vb); + regulator_put(regulator_vb); + regulator_vb = 0; + } +#endif + } + //close clk + regulator_set_mode(regulator_reg, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator_reg); + regulator_put(regulator_reg); + regulator_reg = 0; + } + } +} +static int hp_notifier_handler (struct notifier_block *nb, + unsigned long action, void *data) +{ + struct sprd_codec_priv *sprd_codec = container_of (nb, struct sprd_codec_priv, nb); + sprd_inter_headphone_pa_pre (sprd_codec, !!action); + return 0; +} + +static int sprd_inter_headphone_pa(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + /*static struct regulator *regulator = 0;*/ + sp_asoc_pr_info("inter HP PA Switch %s\n", STR_ON_OFF(on)); + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + if (on) { + /*mute */ + sprd_codec_hp_pa_hpl_mute(codec, 1); + sprd_codec_hp_pa_hpr_mute(codec, 1); + sprd_codec_auxadc_en(codec, 1); + sprd_codec_hp_pa_lpw(codec, p_setting->class_g_low_power); + sprd_codec_hp_pa_mode(codec, p_setting->class_g_mode); + sprd_codec_hp_pa_osc(codec, p_setting->class_g_osc); + sprd_codec_hp_pa_cgcal_en(codec, p_setting->class_g_cgcal); + sprd_codec_wait(sprd_codec->class_g_delay1 * 30); + /*3. CHP EN */ + sprd_codec_hp_pa_en(codec, 1); + sprd_codec_wait(p_setting->class_g_chp_delay_30ms * 30); + /*4. L/R EN */ + sprd_codec_hp_pa_hpl_en(codec, 1); + sprd_codec_hp_pa_hpr_en(codec, 1); + sprd_codec_wait(sprd_codec->class_g_delay2* 30); + if (p_setting->class_g_cgcal) { + sprd_codec_wait(p_setting->class_g_cgcal_delay_10ms *10); + } + } else { + sprd_codec_wait(p_setting->class_g_close_delay_10ms * 10); + sprd_codec_auxadc_en(codec, 0); + /*1. L/R disable */ + sprd_codec_hp_pa_hpl_en(codec, 0); + sprd_codec_hp_pa_hpr_en(codec, 0); + /*2. CHP disable */ + sprd_codec_hp_pa_en(codec, 0); + /*3. unmute */ + sprd_codec_hp_pa_hpl_mute(codec, 0); + sprd_codec_hp_pa_hpr_mute(codec, 0); + sprd_codec_wait(p_setting->class_g_all_close_delay_100ms* 100); + } + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + return 0; +} + +static int sprd_inter_headphone_pa_post(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + if (on) { + /*open classG mute delay time */ + sprd_codec_wait(p_setting->class_g_unmute_delay_100ms * 100); + /*unmute */ + sprd_codec_hp_pa_hpl_mute(codec, 0); + sprd_codec_hp_pa_hpr_mute(codec, 0); + + sprd_codec->inter_hp_pa.set = 1; + } else { + sprd_codec->inter_hp_pa.set = 0; + /*mute */ + sprd_codec_hp_pa_hpl_mute(codec, 1); + sprd_codec_hp_pa_hpr_mute(codec, 1); + sprd_codec_wait(10); + } + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_USE_INTER_HP_PA +static int hp_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_headphone_pa(w->codec, on); +} + +static int hp_pa_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_headphone_pa_post(w->codec, on); +} +#else +static int hp_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} + +static int hp_pa_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} +#endif + +static int sprd_codec_set_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + switch (rate) { + case 8000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_8000 << shift); + break; + case 11025: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_11025 << shift); + break; + case 16000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_16000 << shift); + break; + case 22050: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_22050 << shift); + break; + case 32000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_32000 << shift); + break; + case 44100: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_44100 << shift); + break; + case 48000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_48000 << shift); + break; + case 96000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_96000 << shift); + break; + default: + pr_err("ERR:SPRD-CODEC not support this rate %d\n", rate); + break; + } + sp_asoc_pr_dbg("Set Playback rate 0x%x\n", + snd_soc_read(codec, AUD_DAC_CTL)); + return 0; +} + +static int sprd_codec_set_ad_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + int set; +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE + if (rate == 44100) + rate = CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE; +#endif + set = rate / 4000; + if (set > 13) { + pr_err("ERR:SPRD-CODEC not support this ad rate %d\n", rate); + } + snd_soc_update_bits(codec, SOC_REG(AUD_ADC_CTL), mask, set << shift); + return 0; +} + +static int sprd_codec_sample_rate_setting(struct sprd_codec_priv *sprd_codec) +{ + sp_asoc_pr_dbg("%s AD %d DA %d AD1 %d\n", __func__, + sprd_codec->ad_sample_val, sprd_codec->da_sample_val, + sprd_codec->ad1_sample_val); + if (sprd_codec->ad_sample_val) { + sprd_codec_set_ad_sample_rate(sprd_codec->codec, + sprd_codec->ad_sample_val, 0x0F, + 0); + } + if (sprd_codec->ad1_sample_val) { + sprd_codec_set_ad_sample_rate(sprd_codec->codec, + sprd_codec->ad1_sample_val, 0xF0, + 4); + } + if (sprd_codec->da_sample_val) { + sprd_codec_set_sample_rate(sprd_codec->codec, + sprd_codec->da_sample_val, 0x0F, 0); + } + return 0; +} + +static inline int sprd_codec_vcom_ldo_auto(struct snd_soc_codec *codec, + int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt(codec, sprd_codec_vcm_v_sel, + sprd_codec->s_vcom.v_map, + sprd_codec->s_vcom.v_map_size, init); +} + +static inline int sprd_codec_vcom_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vcom, v_map, v_map_size); +} + +static DEFINE_MUTEX(ldo_mutex); +static int sprd_codec_ldo_on(struct sprd_codec_priv *sprd_codec) +{ + struct regulator **regu = &sprd_codec->head_mic; + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + atomic_inc(&sprd_codec->ldo_refcount); + if (atomic_read(&sprd_codec->ldo_refcount) == 1) { + sp_asoc_pr_dbg("LDO ON!\n"); + if (*regu) { + regulator_set_mode(*regu, REGULATOR_MODE_NORMAL); + } + arch_audio_codec_analog_enable(); + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 1); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_ldo_off(struct sprd_codec_priv *sprd_codec) +{ + struct regulator **regu = &sprd_codec->head_mic; + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + if (atomic_dec_and_test(&sprd_codec->ldo_refcount)) { + if (*regu && regulator_is_enabled(*regu)) { + regulator_set_mode(*regu, REGULATOR_MODE_STANDBY); + } + arch_audio_codec_analog_disable(); + sp_asoc_pr_dbg("LDO OFF!\n"); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_analog_open(struct snd_soc_codec *codec) +{ + int ret = 0; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + sprd_codec_sample_rate_setting(sprd_codec); + + /* SC7710/SC8830 ask from ASIC to set initial value */ + snd_soc_update_bits(codec, SOC_REG(PMUR4_PMUR3), BIT(SEL_VCMI), + BIT(SEL_VCMI)); + snd_soc_update_bits(codec, SOC_REG(PMUR4_PMUR3), BIT(VCMI_FAST_EN), + BIT(VCMI_FAST_EN)); + snd_soc_write(codec, AUD_SDM_CTL0, 0x400); + + return ret; +} + +static int sprd_codec_digital_open(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + /* FIXME : Some Clock SYNC bug will cause MUTE */ + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), BIT(DAC_MUTE_EN), 0); + + sprd_codec_sample_rate_setting(sprd_codec); + + return ret; +} + +static void sprd_codec_power_enable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + ret = sprd_codec_ldo_on(sprd_codec); + if (ret != 0) + pr_err("ERR:SPRD-CODEC open ldo error %d\n", ret); + + sprd_codec_analog_open(codec); +} + +static void sprd_codec_power_disable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + + sprd_codec_ldo_off(sprd_codec); +} + +static int digital_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: + arch_audio_codec_digital_reg_enable(); + arch_audio_codec_digital_enable(); + arch_audio_codec_digital_reset(); + sprd_codec_digital_open(w->codec); + break; + case SND_SOC_DAPM_POST_PMD: + /*maybe ADC module use it, so we cann't close it */ + /*arch_audio_codec_digital_disable(); */ + arch_audio_codec_digital_reg_disable(); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int analog_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + sprd_codec_power_enable(codec); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_power_disable(codec); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int chan_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int chan_id = FUN_REG(w->reg); + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + + sp_asoc_pr_info("%s %s\n", sprd_codec_chan_get_name(chan_id), + STR_ON_OFF(on)); + + return 0; +} + +static int dfm_out_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_info("DFM-OUT %s\n", STR_ON_OFF(on)); + sprd_codec->ad_sample_val = sprd_codec->da_sample_val; + sprd_codec_sample_rate_setting(sprd_codec); + + return 0; +} + +static int _mixer_set_mixer(struct snd_soc_codec *codec, int id, int lr, + int try_on, int need_set) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int reg = GET_MIXER_ID(ID_FUN(id, lr)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[reg]); + if (try_on) { + mixer->set = mixer_setting[reg]; + /* NOTES: reduce linein pop noise must open ADCL/R ->HP MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. */ + if (need_set) + return mixer->set(codec, mixer->on); + } else { + mixer_setting[reg] (codec, 0); + mixer->set = 0; + } + return 0; +} + +static inline int _mixer_setting(struct snd_soc_codec *codec, int start, + int end, int lr, int try_on, int need_set) +{ + int id; + for (id = start; id < end; id++) { + _mixer_set_mixer(codec, id, lr, try_on, need_set); + } + return 0; +} + +static inline int _mixer_setting_one(struct snd_soc_codec *codec, int id, + int try_on, int need_set) +{ + int lr = id & 0x1; + id = (id >>1) + SPRD_CODEC_MIXER_START; + return _mixer_setting(codec, id, id + 1, lr, try_on, need_set); +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static void sprd_codec_hp_pop_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(AUDIO_POP_IRQ); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), mask, mask); +} +#endif + +static irqreturn_t sprd_codec_ap_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = snd_soc_read(codec, AUDIF_INT_MASK); + sp_asoc_pr_dbg("HP POP IRQ Mask = 0x%x\n", mask); + if (BIT(AUDIO_POP_IRQ) & mask) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), BIT(AUDIO_POP_IRQ), 0); + complete(&sprd_codec->completion_hp_pop); + } + if (BIT(OVP_IRQ) & mask) { + snd_soc_update_bits(codec, SOC_REG(AUDIF_INT_EN), BIT(OVP_IRQ), 0); + sprd_codec_ovp_start(sprd_codec, 1); + sprd_codec_ovp_irq_enable(codec); + } + return IRQ_HANDLED; +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static inline int is_hp_pop_compelet(struct snd_soc_codec *codec) +{ + int val; + val = snd_soc_read(codec, IFR2_IFR1); + val = (val >> HP_POP_FLG) & HP_POP_FLG_MASK; + sp_asoc_pr_dbg("HP POP= 0x%x\n", val); + return HP_POP_FLG_NEAR_CMP == val; +} + +static inline int hp_pop_wait_for_compelet(struct snd_soc_codec *codec) +{ + int i; + int hp_pop_complete; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + hp_pop_complete = msecs_to_jiffies(SPRD_CODEC_HP_POP_TIMEOUT); + for (i = 0; i < 2; i++) { + sp_asoc_pr_dbg("HP POP %d IRQ Enable\n", i); + sprd_codec_hp_pop_irq_enable(codec); + init_completion(&sprd_codec->completion_hp_pop); + hp_pop_complete = + wait_for_completion_timeout(&sprd_codec->completion_hp_pop, + hp_pop_complete); + sp_asoc_pr_dbg("HP POP %d Completion %d\n", i, hp_pop_complete); + if (!hp_pop_complete) { + if (!is_hp_pop_compelet(codec)) { + pr_err("ERR:HP POP %d timeout not complete\n", + i); + } else { + pr_err("ERR:HP POP %d timeout but complete\n", + i); + } + } else { + /* 01 change to 10 maybe walk to 11 shortly, + so, check it double. */ + sprd_codec_wait(2); + if (is_hp_pop_compelet(codec)) { + return 0; + } + } + } + return 0; +} + +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int mask; + 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: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_STEP_2 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_UP << HP_POP_CTL); + sp_asoc_pr_dbg("U PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); + break; + case SND_SOC_DAPM_PRE_PMD: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_STEP_1 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_DOWN << HP_POP_CTL); + sp_asoc_pr_dbg("D PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); + break; + case SND_SOC_DAPM_POST_PMU: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); + break; + case SND_SOC_DAPM_POST_PMD: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#else +#ifndef CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME +#define CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME (0) +#endif +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; + sp_asoc_pr_dbg("%s Event is %s wait %dms\n", __func__, + get_event_name(event), + CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_wait(CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} +#endif + +static int hp_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + int mask; +#endif + 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: +#if 1 /* enable the diff function in shark from weifeng.ni */ + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(DIFF_EN), + BIT(DIFF_EN)); + /*enable from */ + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(HP_VCMI_EN), + BIT(HP_VCMI_EN)); + +#endif + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); +#endif + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(DIFF_EN), 0); + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(HP_VCMI_EN), 0); + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(PNRCR2_PNRCR1), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, PNRCR2_PNRCR1)); +#endif + goto _pre_pmd; + break; + case SND_SOC_DAPM_POST_PMD: + /* do nothing now */ + break; + default: + BUG(); + ret = -EINVAL; + } + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, DCR2_DCR1) & BIT(HPL_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, DCR2_DCR1) & BIT(HPR_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, DCR2_DCR1) & BIT(HPL_EN), 0); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, DCR2_DCR1) & BIT(HPR_EN), 0); + +_pre_pmd: + + return ret; +} + +static int spk_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + if (snd_soc_read(codec, DCR2_DCR1) & BIT(AOL_EN)) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + break; + case SND_SOC_DAPM_PRE_PMD: + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + return 0; + default: + break; + } + } + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, DCR2_DCR1) & BIT(AOL_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, DCR2_DCR1) & BIT(AOR_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, DCR2_DCR1) & BIT(AOL_EN)), 0); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, DCR2_DCR1) & BIT(AOR_EN)), 0); + + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK +static int ear_switch_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: + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_EAR); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_EAR); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#endif + +static int adcpgal_set(struct snd_soc_codec *codec, int on) +{ + int mask = ADCPGAL_EN_MASK << ADCPGAL_EN; + return snd_soc_update_bits(codec, SOC_REG(AACR2_AACR1), mask, + on ? mask : 0); +} + +static int adcpgar_set(struct snd_soc_codec *codec, int on) +{ + int mask = ADCPGAR_EN_MASK << ADCPGAR_EN; + return snd_soc_update_bits(codec, SOC_REG(AACR2_AACR1), mask, + on ? mask : 0); +} + +static int adc_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int is_right = (w->shift == SPRD_CODEC_RIGHT); + int on = (! !SND_SOC_DAPM_EVENT_ON(event)); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + if (is_right) { + adcpgar_set(codec, on); + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_RIGHT, on, 1); + } else { + adcpgal_set(codec, on); + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_LEFT, on, 1); + } + + return 0; +} + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_PGA_ID(FUN_REG(w->reg)); + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + int ret = 0; + int min = sprd_codec_pga_cfg[id].min; + + sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__, + sprd_codec_pga_debug_str[id], pga->pgaval, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + pga->set = sprd_codec_pga_cfg[id].set; + ret = pga->set(codec, pga->pgaval); + break; + case SND_SOC_DAPM_PRE_PMD: + pga->set = 0; + ret = sprd_codec_pga_cfg[id].set(codec, min); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} + +static int adcpgar_byp_set(struct snd_soc_codec *codec, int value) +{ + int mask = ADCPGAR_BYP_MASK << ADCPGAR_BYP; + return snd_soc_update_bits(codec, SOC_REG(AACR2_AACR1), mask, + value << ADCPGAR_BYP); +} + +static int adcpgar_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + adcpgar_byp_set(codec, 2); + sprd_codec_wait(100); + adcpgar_byp_set(codec, 0); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} + +static int ana_loop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(w->reg)); + struct sprd_codec_mixer *mixer; + int ret = 0; + static int s_need_wait = 1; + + sp_asoc_pr_dbg("Entering %s event is %s\n", __func__, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* NOTES: reduce linein pop noise must open ADCL/R ->HP/SPK MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. + */ + if (s_need_wait == 1) { + /* NOTES: reduce linein pop noise must delay 250ms + after linein mixer switch on. + actually this function perform after + adc_switch_event function for + both ADCL/ADCR switch complete. + */ + sprd_codec_wait(250); + s_need_wait++; + sp_asoc_pr_dbg("ADC Switch ON delay\n"); + } + mixer = &(sprd_codec->mixer[id]); + if (mixer->set) { + mixer->set(codec, mixer->on); + } + break; + case SND_SOC_DAPM_PRE_PMD: + s_need_wait = 1; + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int reg = FUN_REG(w->reg); + int ret = 0; + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + struct regulator **regu; + + sp_asoc_pr_dbg("%s %s Event is %s\n", __func__, + mic_bias_name[GET_MIC_BIAS_ID(reg)], + get_event_name(event)); + + switch (reg) { + case SPRD_CODEC_MIC_BIAS: + regu = &sprd_codec->main_mic; + break; + case SPRD_CODEC_AUXMIC_BIAS: + regu = &sprd_codec->aux_mic; + break; + case SPRD_CODEC_HEADMIC_BIAS: + regu = &sprd_codec->head_mic; + break; + default: + BUG(); + ret = -EINVAL; + } + + ret = sprd_codec_mic_bias_set(regu, on); + + return ret; +} + +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(w->reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + sp_asoc_pr_dbg("%s event is %s\n", sprd_codec_mixer_debug_str[id], + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mixer->on = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + mixer->on = 0; + break; + default: + BUG(); + ret = -EINVAL; + } + if (ret >= 0) + _mixer_setting_one(codec, id, mixer->on, 1); + + return ret; +} + +static int mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(mc->reg)); + ucontrol->value.integer.value[0] = sprd_codec->mixer[id].on; + return 0; +} + +static int mixer_need_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, bool need_set) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(mc->reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + if (mixer->on == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("Set %s Switch %s\n", sprd_codec_mixer_debug_str[id], + STR_ON_OFF(ucontrol->value.integer.value[0])); + + /*notice the sequence */ + snd_soc_dapm_put_volsw(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); */ + + if (mixer->set && need_set) + ret = mixer->set(codec, mixer->on); + + return ret; +} + +static int mixer_set_mem(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 0); +} + +static int mixer_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 1); +} + +#define SPRD_CODEC_MIXER(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set) + +/*Just for LINE IN path, mixer_set not really set mixer (ADCL/R -> HP/SPK L/R) here but +setting in ana_loop_event, just remeber state here*/ +#define SPRD_CODEC_MIXER_NOSET(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set_mem) + +/* ADCL Mixer */ +static const struct snd_kcontrol_new adcl_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCL Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AIRADCL Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("MainMICADCL Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AuxMICADCL Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("HPMICADCL Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_LEFT)), +}; + +/* ADCR Mixer */ +static const struct snd_kcontrol_new adcr_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCR Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AIRADCR Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("MainMICADCR Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AuxMICADCR Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("HPMICADCR Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_RIGHT)), +}; + +/* HPL Mixer */ +static const struct snd_kcontrol_new hpl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), +}; + +/* HPR Mixer */ +static const struct snd_kcontrol_new hpr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), +}; + +/* SPKL Mixer */ +static const struct snd_kcontrol_new spkl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), +}; + +/* SPKR Mixer */ +static const struct snd_kcontrol_new spkr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), +}; + +/*ANA LOOP SWITCH*/ +#define SPRD_CODEC_LOOP_SWITCH(xname, xreg)\ + SND_SOC_DAPM_PGA_S(xname, SPRD_CODEC_ANA_MIXER_ORDER, FUN_REG(xreg), 0, 0, ana_loop_event,\ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD) + +#ifndef SND_SOC_DAPM_MICBIAS_E +#define SND_SOC_DAPM_MICBIAS_E(wname, wreg, wshift, winvert, wevent, wflags) \ +{ .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \ + .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags} +#endif + +static const struct snd_soc_dapm_widget sprd_codec_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("VBO", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VCOM_BUF", 0, 0), + SND_SOC_DAPM_SUPPLY_S("Digital Power", 1, SND_SOC_NOPM, 0, 0, + digital_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("Analog Power", 2, SND_SOC_NOPM, 0, 0, + analog_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("DA Clk", 3, SOC_REG(CCR), DAC_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DRV Clk", 4, SOC_REG(CCR), DRV_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("AD IBUF", 3, SOC_REG(AACR2_AACR1), ADC_IBUF_PD, + 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD Clk", 3, SOC_REG(CCR), ADC_CLK_EN, 0, NULL, + 0), + + SND_SOC_DAPM_PGA_S("Digital DACL Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital DACR Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital DACL Switch", 5, SOC_REG(AUDIF_ENB), + AUDIFA_DACL_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital DACR Switch", 5, SOC_REG(AUDIF_ENB), + AUDIFA_DACR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("DACL Switch", 6, SOC_REG(DACGR_DACR), DACL_EN, 0, + NULL, + 0), + SND_SOC_DAPM_PGA_S("DACR Switch", 6, SOC_REG(DACGR_DACR), DACR_EN, 0, + NULL, + 0), + SND_SOC_DAPM_PGA_S("DACL Mute", 6, FUN_REG(SPRD_CODEC_PGA_DACL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("DACR Mute", 6, FUN_REG(SPRD_CODEC_PGA_DACR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("DAC", "Playback-Vaudio", + FUN_REG(SPRD_CODEC_PLAYBACK), 0, + 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DFM-OUT", "DFM-Output", + SND_SOC_NOPM, 0, + 0, + dfm_out_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + SND_SOC_DAPM_PGA_S("HP POP", SPRD_CODEC_HP_POP_ORDER, SND_SOC_NOPM, 0, + 0, hp_pop_event, + SND_SOC_DAPM_POST_PMU), +#else + SND_SOC_DAPM_SUPPLY_S("HP POP", 5, SND_SOC_NOPM, 0, 0, hp_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +#endif + SND_SOC_DAPM_PGA_S("HPL Switch", SPRD_CODEC_HP_SWITCH_ORDER, + SOC_REG(DCR2_DCR1), HPL_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Switch", SPRD_CODEC_HP_SWITCH_ORDER, + SOC_REG(DCR2_DCR1), HPR_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPL Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER("HPL Mixer", SND_SOC_NOPM, 0, 0, + &hpl_mixer_controls[0], + ARRAY_SIZE(hpl_mixer_controls)), + SND_SOC_DAPM_MIXER("HPR Mixer", SND_SOC_NOPM, 0, 0, + &hpr_mixer_controls[0], + ARRAY_SIZE(hpr_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKL Mixer", SND_SOC_NOPM, 0, 0, + &spkl_mixer_controls[0], + ARRAY_SIZE(spkl_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKR Mixer", SND_SOC_NOPM, 0, 0, + &spkr_mixer_controls[0], + ARRAY_SIZE(spkr_mixer_controls)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), + SND_SOC_DAPM_PGA_S("SPKL Switch", 5, SOC_REG(DCR2_DCR1), AOL_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Switch", 5, SOC_REG(DCR2_DCR1), AOR_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("SPKL Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("EAR Mixer", 5, + FUN_REG(ID_FUN + (SPRD_CODEC_EAR_DACL, SPRD_CODEC_LEFT)), 0, + 0, mixer_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(DCR2_DCR1), EAR_EN, 0, + ear_switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#else + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(DCR2_DCR1), EAR_EN, 0, NULL, + 0), +#endif + + SND_SOC_DAPM_PGA_S("EAR Mute", 7, FUN_REG(SPRD_CODEC_PGA_EAR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MIXER("ADCL Mixer", SND_SOC_NOPM, 0, 0, + &adcl_mixer_controls[0], + ARRAY_SIZE(adcl_mixer_controls)), + SND_SOC_DAPM_MIXER("ADCR Mixer", SND_SOC_NOPM, 0, 0, + &adcr_mixer_controls[0], + ARRAY_SIZE(adcr_mixer_controls)), + SND_SOC_DAPM_PGA_S("Digital ADCL Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADCR Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital ADCL Switch", 2, SOC_REG(AUDIF_ENB), + AUDIFA_ADCL_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital ADCR Switch", 2, SOC_REG(AUDIF_ENB), + AUDIFA_ADCR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADCL Switch", 1, SOC_REG(AACR2_AACR1), ADCL_PD, 1, + NULL, + 0), + SND_SOC_DAPM_PGA_S("ADCR Switch", 1, SOC_REG(AACR2_AACR1), ADCR_PD, 1, + adcpgar_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("ADCL PGA", SND_SOC_NOPM, SPRD_CODEC_LEFT, 0, NULL, + 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ADCR PGA", SND_SOC_NOPM, SPRD_CODEC_RIGHT, 0, NULL, + 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADCL Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("ADCR Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC", "Main-Capture-Vaudio", + FUN_REG(SPRD_CODEC_CAPTRUE), + 0, 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("MIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_MIC), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AUXMIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_AUXMIC), 0, + 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HEADMIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_HEADMIC), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AIL Boost", 3, FUN_REG(SPRD_CODEC_PGA_AIL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AIR Boost", 3, FUN_REG(SPRD_CODEC_PGA_AIR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MICBIAS_E("Mic Bias", FUN_REG(SPRD_CODEC_MIC_BIAS), 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("AuxMic Bias", FUN_REG(SPRD_CODEC_AUXMIC_BIAS), + 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("HeadMic Bias", FUN_REG(SPRD_CODEC_HEADMIC_BIAS), + 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("Digital ADC1L Switch", 5, SOC_REG(AUD_TOP_CTL), + ADC1_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADC1R Switch", 5, SOC_REG(AUD_TOP_CTL), + ADC1_EN_R, 0, NULL, 0), + SND_SOC_DAPM_ADC_E("ADC1", "Ext-Capture-Vaudio", + FUN_REG(SPRD_CODEC_CAPTRUE1), + 0, 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /*add DMIC */ + SND_SOC_DAPM_PGA_S("Digital ADC DMIC In", 4, SOC_REG(AUD_TOP_CTL), + ADC_DMIC_SEL, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADC1 DMIC In", 4, SOC_REG(AUD_TOP_CTL), + ADC1_DMIC1_SEL, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("DMIC Switch", 3, SOC_REG(AUD_DMIC_CTL), + ADC_DMIC_EN, 0, + NULL, 0), + SND_SOC_DAPM_PGA_S("DMIC1 Switch", 3, SOC_REG(AUD_DMIC_CTL), + ADC1_DMIC1_EN, 0, + NULL, 0), + + SND_SOC_DAPM_PGA_S("HP PA Switch", SPRD_CODEC_HP_PA_ORDER, SND_SOC_NOPM, + 0, 0, hp_pa_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("HP PA POST Switch", SPRD_CODEC_HP_PA_POST_ORDER, + SND_SOC_NOPM, 0, 0, hp_pa_post_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Spk PA Switch", SPRD_CODEC_PA_ORDER, SND_SOC_NOPM, + 0, 0, + spk_pa_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("DMIC"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_OUTPUT("HEAD_P_L"), + SND_SOC_DAPM_OUTPUT("HEAD_P_R"), + SND_SOC_DAPM_OUTPUT("AOL"), + SND_SOC_DAPM_OUTPUT("AOR"), + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_OUTPUT("HP PA"), + SND_SOC_DAPM_OUTPUT("Spk PA"), + SND_SOC_DAPM_OUTPUT("Spk2 PA"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("AUXMIC"), + SND_SOC_DAPM_INPUT("HPMIC"), + SND_SOC_DAPM_INPUT("AIL"), + SND_SOC_DAPM_INPUT("AIR"), +}; + +/* sprd_codec supported interconnection*/ +static const struct snd_soc_dapm_route sprd_codec_intercon[] = { + /* Power */ + {"Analog Power", NULL, "VCOM_BUF"}, + {"Analog Power", NULL, "VBO"}, + {"DA Clk", NULL, "Analog Power"}, + {"DA Clk", NULL, "Digital Power"}, + {"DAC", NULL, "DA Clk"}, + {"DFM-OUT", NULL, "DA Clk"}, + + {"AD IBUF", NULL, "Analog Power"}, + {"AD Clk", NULL, "Digital Power"}, + {"AD Clk", NULL, "AD IBUF"}, + {"ADC", NULL, "AD Clk"}, + + {"ADC1", NULL, "AD Clk"}, + + {"ADCL PGA", NULL, "AD IBUF"}, + {"ADCR PGA", NULL, "AD IBUF"}, + + {"HP POP", NULL, "DRV Clk"}, + {"HPL Switch", NULL, "DRV Clk"}, + {"HPR Switch", NULL, "DRV Clk"}, + {"SPKL Switch", NULL, "DRV Clk"}, + {"SPKR Switch", NULL, "DRV Clk"}, + {"EAR Switch", NULL, "DRV Clk"}, + + /* Playback */ + {"Digital DACL Switch", NULL, "DFM-OUT"}, + {"Digital DACR Switch", NULL, "DFM-OUT"}, + {"Digital DACL Switch", NULL, "DAC"}, + {"Digital DACR Switch", NULL, "DAC"}, + {"ADie Digital DACL Switch", NULL, "Digital DACL Switch"}, + {"ADie Digital DACR Switch", NULL, "Digital DACR Switch"}, + {"DACL Mute", NULL, "ADie Digital DACL Switch"}, + {"DACR Mute", NULL, "ADie Digital DACR Switch"}, + {"DACL Switch", NULL, "DACL Mute"}, + {"DACR Switch", NULL, "DACR Mute"}, + + /* Output */ + {"HPL Mixer", "DACLHPL Switch", "DACL Switch"}, + {"HPL Mixer", "DACRHPL Switch", "DACR Switch"}, + {"HPR Mixer", "DACLHPR Switch", "DACL Switch"}, + {"HPR Mixer", "DACRHPR Switch", "DACR Switch"}, + + {"ADCLHPL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLHPR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRHPL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRHPR Loop Switch", NULL, "ADCR PGA"}, + + {"HPL Mixer", "ADCLHPL Switch", "ADCLHPL Loop Switch"}, + {"HPL Mixer", "ADCRHPL Switch", "ADCRHPL Loop Switch"}, + {"HPR Mixer", "ADCLHPR Switch", "ADCLHPR Loop Switch"}, + {"HPR Mixer", "ADCRHPR Switch", "ADCRHPR Loop Switch"}, + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + {"HEAD_P_L", NULL, "HP POP"}, + {"HEAD_P_R", NULL, "HP POP"}, + {"HP POP", NULL, "HPL Switch"}, + {"HP POP", NULL, "HPR Switch"}, +#else + {"HPL Switch", NULL, "HP POP"}, + {"HPR Switch", NULL, "HP POP"}, +#endif + {"HPL Switch", NULL, "HPL Mixer"}, + {"HPR Switch", NULL, "HPR Mixer"}, + {"HPL Mute", NULL, "HPL Switch"}, + {"HPR Mute", NULL, "HPR Switch"}, + {"HEAD_P_L", NULL, "HPL Mute"}, + {"HEAD_P_R", NULL, "HPR Mute"}, + + {"HP PA", NULL, "HP PA POST Switch"}, + {"HP PA POST Switch", NULL, "HP PA Switch"}, + {"HP PA Switch", NULL, "HPL Mute"}, + {"HP PA Switch", NULL, "HPR Mute"}, + + {"SPKL Mixer", "DACLSPKL Switch", "DACL Switch"}, + {"SPKL Mixer", "DACRSPKL Switch", "DACR Switch"}, + {"SPKR Mixer", "DACLSPKR Switch", "DACL Switch"}, + {"SPKR Mixer", "DACRSPKR Switch", "DACR Switch"}, + + {"ADCLSPKL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLSPKR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRSPKL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRSPKR Loop Switch", NULL, "ADCR PGA"}, + + {"SPKL Mixer", "ADCLSPKL Switch", "ADCLSPKL Loop Switch"}, + {"SPKL Mixer", "ADCRSPKL Switch", "ADCRSPKL Loop Switch"}, + {"SPKR Mixer", "ADCLSPKR Switch", "ADCLSPKR Loop Switch"}, + {"SPKR Mixer", "ADCRSPKR Switch", "ADCRSPKR Loop Switch"}, + + {"SPKL Switch", NULL, "SPKL Mixer"}, + {"SPKR Switch", NULL, "SPKR Mixer"}, + {"SPKL Mute", NULL, "SPKL Switch"}, + {"SPKR Mute", NULL, "SPKR Switch"}, + {"AOL", NULL, "SPKL Mute"}, + {"AOR", NULL, "SPKR Mute"}, + + {"Spk PA", NULL, "Spk PA Switch"}, + {"Spk PA Switch", NULL, "SPKL Mute"}, + + {"EAR Mixer", NULL, "DACL Switch"}, + {"EAR Switch", NULL, "EAR Mixer"}, + {"EAR Mute", NULL, "EAR Switch"}, + {"EAR", NULL, "EAR Mute"}, + + {"ADCL Mute", NULL, "ADCL Mixer"}, + {"ADCR Mute", NULL, "ADCR Mixer"}, + {"ADCL PGA", NULL, "ADCL Mute"}, + {"ADCR PGA", NULL, "ADCR Mute"}, + /* LineIN */ + {"ADCL Mixer", "AILADCL Switch", "AIL Boost"}, + {"ADCR Mixer", "AILADCR Switch", "AIL Boost"}, + {"ADCL Mixer", "AIRADCL Switch", "AIR Boost"}, + {"ADCR Mixer", "AIRADCR Switch", "AIR Boost"}, + {"AIL Boost", NULL, "AIL"}, + {"AIR Boost", NULL, "AIR"}, + /* Input */ + {"ADCL Mixer", "MainMICADCL Switch", "MIC Boost"}, + {"ADCR Mixer", "MainMICADCR Switch", "MIC Boost"}, + {"MIC Boost", NULL, "Mic Bias"}, + {"ADCL Mixer", "AuxMICADCL Switch", "AUXMIC Boost"}, + {"ADCR Mixer", "AuxMICADCR Switch", "AUXMIC Boost"}, + {"AUXMIC Boost", NULL, "AuxMic Bias"}, + {"ADCL Mixer", "HPMICADCL Switch", "HEADMIC Boost"}, + {"ADCR Mixer", "HPMICADCR Switch", "HEADMIC Boost"}, + {"HEADMIC Boost", NULL, "HeadMic Bias"}, + /* Captrue */ + {"ADCL Switch", NULL, "ADCL PGA"}, + {"ADCR Switch", NULL, "ADCR PGA"}, + {"ADie Digital ADCL Switch", NULL, "ADCL Switch"}, + {"ADie Digital ADCR Switch", NULL, "ADCR Switch"}, + {"Digital ADCL Switch", NULL, "ADie Digital ADCL Switch"}, + {"Digital ADCR Switch", NULL, "ADie Digital ADCR Switch"}, + {"ADC", NULL, "Digital ADCL Switch"}, + {"ADC", NULL, "Digital ADCR Switch"}, + + {"Digital ADC1L Switch", NULL, "ADie Digital ADCL Switch"}, + {"Digital ADC1R Switch", NULL, "ADie Digital ADCR Switch"}, + {"ADC1", NULL, "Digital ADC1L Switch"}, + {"ADC1", NULL, "Digital ADC1R Switch"}, + + {"Mic Bias", NULL, "MIC"}, + {"AuxMic Bias", NULL, "AUXMIC"}, + {"HeadMic Bias", NULL, "HPMIC"}, + + /* DMIC0 */ + {"DMIC Switch", NULL, "DMIC"}, + {"Digital ADC DMIC In", NULL, "DMIC Switch"}, + {"Digital ADCL Switch", NULL, "Digital ADC DMIC In"}, + {"Digital ADCR Switch", NULL, "Digital ADC DMIC In"}, + /* DMIC1 */ + {"DMIC1 Switch", NULL, "DMIC1"}, + {"Digital ADC1 DMIC In", NULL, "DMIC1 Switch"}, + {"Digital ADC1L Switch", NULL, "Digital ADC1 DMIC In"}, + {"Digital ADC1R Switch", NULL, "Digital ADC1 DMIC In"}, + + /* Bias independent */ + {"Mic Bias", NULL, "Analog Power"}, + {"AuxMic Bias", NULL, "Analog Power"}, + {"HeadMic Bias", NULL, "Analog Power"}, +}; + +static int sprd_codec_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_PGA_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + int ret = 0; + + sp_asoc_pr_info("Set PGA[%s] to %ld\n", sprd_codec_pga_debug_str[id], + ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + pga->pgaval = val; + if (pga->set) { + ret = pga->set(codec, pga->pgaval); + } + return ret; +} + +static int sprd_codec_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_PGA_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int invert = mc->invert; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + + ucontrol->value.integer.value[0] = pga->pgaval; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_inter_pa_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + + sp_asoc_pr_info("Config inter PA 0x%08x\n", + (int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->inter_pa_mutex); + sprd_codec->inter_pa.value = (u32) val; + if (sprd_codec->inter_pa.set) { + mutex_unlock(&sprd_codec->inter_pa_mutex); + sprd_inter_speaker_pa(codec, 1); + } else { + mutex_unlock(&sprd_codec->inter_pa_mutex); + } + return ret; +} + +static int sprd_codec_inter_pa_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_pa_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_pa.value; + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_inter_hp_pa_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + + sp_asoc_pr_info("Config inter HP PA 0x%08x\n", + (int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + sprd_codec->inter_hp_pa.value = (u32) val; + if (sprd_codec->inter_hp_pa.set) { + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + sprd_codec_hp_pa_lpw(codec, p_setting->class_g_low_power); + sprd_codec_hp_pa_mode(codec, p_setting->class_g_mode); + sprd_codec_hp_pa_osc(codec, p_setting->class_g_osc); + sprd_codec_hp_pa_cgcal_en(codec, p_setting->class_g_cgcal); + } else { + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + } + return ret; +} + +static int sprd_codec_inter_hp_pa_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_hp_pa.value; + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_inter_hp_pa_delay_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + + sp_asoc_pr_info("Config inter HP PA DELAY 0x%08x\n", + (int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->inter_hp_pa_delay_mutex); + sprd_codec->inter_hp_pa.delay_value = (u32) val; + sprd_codec->class_g_delay1 = sprd_codec->inter_hp_pa.delay_setting.class_g_delay1; + sprd_codec->class_g_delay2 = sprd_codec->inter_hp_pa.delay_setting.class_g_delay2; + mutex_unlock(&sprd_codec->inter_hp_pa_delay_mutex); + return ret; +} + +static int sprd_codec_inter_hp_pa_delay_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_hp_pa_delay_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_hp_pa.delay_value; + mutex_unlock(&sprd_codec->inter_hp_pa_delay_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_mic_bias_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + int id = GET_MIC_BIAS_ID(reg); + + val = (ucontrol->value.integer.value[0] & mask); + + if (invert) + val = max - val; + if (sprd_codec->mic_bias[id] == val) { + return 0; + } + + sp_asoc_pr_info("%s Switch %s\n", mic_bias_name[id], STR_ON_OFF(val)); + + sprd_codec->mic_bias[id] = val; + if (val) { + ret = + snd_soc_dapm_force_enable_pin(&codec->card->dapm, + mic_bias_name[id]); + } else { + ret = + snd_soc_dapm_disable_pin(&codec->card->dapm, + mic_bias_name[id]); + } + + /* signal a DAPM event */ + snd_soc_dapm_sync(&codec->card->dapm); + + return ret; +} + +static int sprd_codec_mic_bias_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int invert = mc->invert; + int id = GET_MIC_BIAS_ID(reg); + + ucontrol->value.integer.value[0] = sprd_codec->mic_bias[id]; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -600, 75, 0); +static const DECLARE_TLV_DB_SCALE(hp_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(ear_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -2400, 300, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -350, 50, 1); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(auxmic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(headmic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(ailr_tlv, 0, 600, 0); + +#define SPRD_CODEC_PGA(xname, xreg, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, FUN_REG(xreg), 0, 15, 0, \ + sprd_codec_vol_get, sprd_codec_vol_put, tlv_array) + +#define SPRD_CODEC_PGA_MAX(xname, xreg, max, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, FUN_REG(xreg), 0, max, 0, \ + sprd_codec_vol_get, sprd_codec_vol_put, tlv_array) + +#define SPRD_CODEC_MIC_BIAS(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, \ + sprd_codec_mic_bias_get, sprd_codec_mic_bias_put) + +static const struct snd_kcontrol_new sprd_codec_snd_controls[] = { + SPRD_CODEC_PGA("SPKL Playback Volume", SPRD_CODEC_PGA_SPKL, spk_tlv), + SPRD_CODEC_PGA("SPKR Playback Volume", SPRD_CODEC_PGA_SPKR, spk_tlv), + SPRD_CODEC_PGA("HPL Playback Volume", SPRD_CODEC_PGA_HPL, hp_tlv), + SPRD_CODEC_PGA("HPR Playback Volume", SPRD_CODEC_PGA_HPR, hp_tlv), + SPRD_CODEC_PGA("EAR Playback Volume", SPRD_CODEC_PGA_EAR, ear_tlv), + + SPRD_CODEC_PGA_MAX("ADCL Capture Volume", SPRD_CODEC_PGA_ADCL, 63, + adc_tlv), + SPRD_CODEC_PGA_MAX("ADCR Capture Volume", SPRD_CODEC_PGA_ADCR, 63, + adc_tlv), + + SPRD_CODEC_PGA_MAX("DACL Playback Volume", SPRD_CODEC_PGA_DACL, 7, + dac_tlv), + SPRD_CODEC_PGA_MAX("DACR Playback Volume", SPRD_CODEC_PGA_DACR, 7, + dac_tlv), + SPRD_CODEC_PGA_MAX("MIC Boost", SPRD_CODEC_PGA_MIC, 3, mic_tlv), + SPRD_CODEC_PGA_MAX("AUXMIC Boost", SPRD_CODEC_PGA_AUXMIC, 3, + auxmic_tlv), + SPRD_CODEC_PGA_MAX("HEADMIC Boost", SPRD_CODEC_PGA_HEADMIC, 3, + headmic_tlv), + SPRD_CODEC_PGA_MAX("Linein Boost", SPRD_CODEC_PGA_AIL, 3, ailr_tlv), + + SOC_SINGLE_EXT("Inter PA Config", 0, 0, LONG_MAX, 0, + sprd_codec_inter_pa_get, sprd_codec_inter_pa_put), + + SOC_SINGLE_EXT("Inter HP PA Config", 0, 0, LONG_MAX, 0, + sprd_codec_inter_hp_pa_get, sprd_codec_inter_hp_pa_put), + + SOC_SINGLE_EXT("Inter HP PA Delay", 0, 0, LONG_MAX, 0, + sprd_codec_inter_hp_pa_delay_get, sprd_codec_inter_hp_pa_delay_put), + + SPRD_CODEC_MIC_BIAS("MIC Bias Switch", SPRD_CODEC_MIC_BIAS), + + SPRD_CODEC_MIC_BIAS("AUXMIC Bias Switch", SPRD_CODEC_AUXMIC_BIAS), + + SPRD_CODEC_MIC_BIAS("HEADMIC Bias Switch", SPRD_CODEC_HEADMIC_BIAS), +}; + +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + /* Because snd_soc_update_bits reg is 16 bits short type, + so muse do following convert + */ + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + return arch_audio_codec_read(reg); + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + return __raw_readl((void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base)); + } else if (IS_SPRD_CODEC_MIXER_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + return mixer->on; + } else if (IS_SPRD_CODEC_PGA_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_MIC_BIAS_RANG(FUN_REG(reg))) { + } else + sp_asoc_pr_dbg("read the register is not codec's reg = 0x%x\n", + reg); + return 0; +} + +static int sprd_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + int ret = 0; + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + ret = arch_audio_codec_write(reg, val); + sp_asoc_pr_reg("A[0x%04x] W:[0x%08x] R:[0x%08x]\n", + (reg - CODEC_AP_BASE) & 0xFFFF, val, arch_audio_codec_read(reg)); + return ret; + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + __raw_writel(val, (void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base)); + sp_asoc_pr_reg("D[0x%04x] W:[0x%08x] R:[0x%08x]\n", + (reg - CODEC_DP_BASE) & 0xFFFF, val, + __raw_readl((void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base))); + return ret; + } else if (IS_SPRD_CODEC_MIXER_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + mixer->on = val ? 1 : 0; + } else if (IS_SPRD_CODEC_PGA_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_MIC_BIAS_RANG(FUN_REG(reg))) { + } else + sp_asoc_pr_dbg("write the register is not codec's reg = 0x%x\n", + reg); + return ret; +} + +static int sprd_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask = 0x0F; + int shift = 0; + int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sprd_codec->da_sample_val = rate; + sp_asoc_pr_info("Playback rate is [%d]\n", rate); + sprd_codec_set_sample_rate(codec, rate, mask, shift); + } else { + sp_asoc_pr_info("Capture rate is [%d]\n", rate); + if (dai->id != SPRD_CODEC_IIS1_ID) { + sprd_codec->ad_sample_val = rate; + sprd_codec_set_ad_sample_rate(codec, rate, mask, shift); + } else { + sprd_codec->ad1_sample_val = rate; + sprd_codec_set_ad_sample_rate(codec, rate, + ADC1_SRC_N_MASK, + ADC1_SRC_N); + } + } + + return 0; +} + +static int sprd_codec_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +#if 0 +static void sprd_codec_dac_mute_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(DAC_MUTE_D); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, mask); +} +#endif + +static irqreturn_t sprd_codec_dp_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = snd_soc_read(codec, AUD_AUD_STS0); + sp_asoc_pr_dbg("dac mute irq mask = 0x%x\n", mask); + if (BIT(DAC_MUTE_D_MASK) & mask) { + mask = BIT(DAC_MUTE_D); + complete(&sprd_codec->completion_dac_mute); + } + if (BIT(DAC_MUTE_U_MASK) & mask) { + mask = BIT(DAC_MUTE_U); + } + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, 0); + return IRQ_HANDLED; +} + +#if 0 +static int sprd_codec_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + if (dai->id == SPRD_CODEC_IIS1_ID) /*codec_dai_1 has no playback */ + return 0; + + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sp_asoc_pr_dbg("mute %i\n", mute); + + ret = + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), + BIT(DAC_MUTE_START), + mute ? BIT(DAC_MUTE_START) : 0); +#if 0 + if (mute && ret) { + int dac_mute_complete; + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("Dac Mute IRQ enable\n"); + sprd_codec_dac_mute_irq_enable(codec); + init_completion(&sprd_codec->completion_dac_mute); + dac_mute_complete = + wait_for_completion_timeout + (&sprd_codec->completion_dac_mute, + msecs_to_jiffies(SPRD_CODEC_DAC_MUTE_TIMEOUT)); + sp_asoc_pr_dbg("Dac Mute Completion %d\n", + dac_mute_complete); + if (!dac_mute_complete) { + pr_err("Dac Mute Timeout\n"); + } + } +#endif + + } + + return ret; +} +#endif + +static struct snd_soc_dai_ops sprd_codec_dai_ops = { + .hw_params = sprd_codec_pcm_hw_params, + .hw_free = sprd_codec_pcm_hw_free, +}; + +#ifdef CONFIG_PM +static int sprd_codec_soc_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_soc_resume(struct snd_soc_codec *codec) +{ + return 0; +} +#else +#define sprd_codec_soc_suspend NULL +#define sprd_codec_soc_resume NULL +#endif + +/* + * proc interface + */ + +#ifdef CONFIG_PROC_FS +static void sprd_codec_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct sprd_codec_priv *sprd_codec = entry->private_data; + struct snd_soc_codec *codec = sprd_codec->codec; + int reg; + + snd_iprintf(buffer, "%s digital part\n", codec->name); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_DP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } + snd_iprintf(buffer, "%s analog part\n", codec->name); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_AP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } +} + +static void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ + struct snd_info_entry *entry; + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!snd_card_proc_new(codec->card->snd_card, "sprd-codec", &entry)) + snd_info_set_text_ops(entry, sprd_codec, sprd_codec_proc_read); +} +#else /* !CONFIG_PROC_FS */ +static inline void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ +} +#endif + +static void sprd_codec_power_changed(struct power_supply *psy) +{ + struct sprd_codec_priv *sprd_codec = 0; + sp_asoc_pr_dbg("%s %s\n", __func__, psy->name); +#if 1 + sprd_codec = container_of(psy, struct sprd_codec_priv, audio_ldo); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (sprd_codec->inter_pa.set && sprd_codec->inter_pa.setting.is_LDO_mode + && sprd_codec->inter_pa.setting.is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(sprd_codec->codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 0); + } +#endif +} + +static int sprd_codec_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + return -EINVAL; +} + +static int sprd_codec_audio_ldo(struct sprd_codec_priv *sprd_codec) +{ + int ret = 0; + struct snd_soc_codec *codec = sprd_codec->codec; + sprd_codec->audio_ldo.name = "audio-ldo"; + sprd_codec->audio_ldo.get_property = sprd_codec_power_get_property; + sprd_codec->audio_ldo.external_power_changed = sprd_codec_power_changed; + ret = power_supply_register(codec->dev, &sprd_codec->audio_ldo); + if (ret) { + pr_err("ERR:Register power supply error!\n"); + return -EFAULT; + } + return ret; +} + +#define SPRD_CODEC_PCM_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define SPRD_CODEC_PCM_AD_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SRC_SUPPORT_RATE | \ + SNDRV_PCM_RATE_48000) + +/* PCM Playing and Recording default in full duplex mode */ +static struct snd_soc_dai_driver sprd_codec_dai[] = { + { + .name = "sprd-codec-v3-i2s", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "sprd-codec-v3-vaudio", + .playback = { + .stream_name = "Playback-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#endif + /*digital ad1 */ + { + .id = SPRD_CODEC_IIS1_ID, + .name = "codec-i2s-ext", + .capture = { + .stream_name = "Ext-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + /*digital ad1 */ + { + .id = SPRD_CODEC_IIS1_ID, + .name = "codec-vaudio-ext", + .capture = { + .stream_name = "Ext-Capture-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#endif + { + .name = "sprd-codec-v3-fm", + .playback = { + .stream_name = "DFM-Output", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +}; + +static int sprd_codec_soc_probe(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + int hp_plug_state = get_hp_plug_state(); +#endif + + sp_asoc_pr_dbg("%s\n", __func__); + + codec->dapm.idle_bias_off = 1; + + sprd_codec->codec = codec; + + sprd_codec_proc_init(sprd_codec); + + sprd_codec_audio_ldo(sprd_codec); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + sprd_inter_headphone_pa_pre(sprd_codec, 1); +#else + sprd_inter_headphone_pa_pre(sprd_codec, hp_plug_state); +#endif + return ret; +} + +/* power down chip */ +static int sprd_codec_soc_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_power_regulator_init(struct sprd_codec_priv *sprd_codec, + struct device *dev) +{ + sprd_codec_power_get(dev, &sprd_codec->main_mic, "MICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->aux_mic, "AUXMICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->head_mic, "HEADMICBIAS"); + + return 0; +} + +static void sprd_codec_power_regulator_exit(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec_power_put(&sprd_codec->main_mic); + sprd_codec_power_put(&sprd_codec->aux_mic); + sprd_codec_power_put(&sprd_codec->head_mic); +} + +static struct snd_soc_codec_driver soc_codec_dev_sprd_codec = { + .probe = sprd_codec_soc_probe, + .remove = sprd_codec_soc_remove, + .suspend = sprd_codec_soc_suspend, + .resume = sprd_codec_soc_resume, + .read = sprd_codec_read, + .write = sprd_codec_write, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = sprd_codec_intercon, + .num_dapm_routes = ARRAY_SIZE(sprd_codec_intercon), + .controls = sprd_codec_snd_controls, + .num_controls = ARRAY_SIZE(sprd_codec_snd_controls), +}; + +static int sprd_codec_probe(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec; + int ret; + struct device_node *node = pdev->dev.of_node; + struct resource *res; + u32 ana_chip_id; + + sp_asoc_pr_dbg("%s\n", __func__); + + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + + sprd_codec = + devm_kzalloc(&pdev->dev, sizeof(struct sprd_codec_priv), + GFP_KERNEL); + if (sprd_codec == NULL) + return -ENOMEM; + + sprd_codec->da_sample_val = 44100; /*inital value for FM route */ + + if (node) { + u32 val; + u32 val_arr[2]; + if (!of_property_read_u32(node, "sprd,hp_pa_ver", &val)) { + if (val < SPRD_CODEC_HP_PA_VER_MAX) { + sprd_codec->hp_ver = val; + sp_asoc_pr_dbg("Set HP PA Ver is %d!\n", val); + } else { + pr_err + ("ERR:This driver just support less %d version!\n", + SPRD_CODEC_HP_PA_VER_MAX); + return -EINVAL; + } + } + if (!of_property_read_u32(node, "sprd,dp_irq", &val)) { + sprd_codec->dp_irq = val; + sp_asoc_pr_dbg("Set DP IRQ is %d!\n", val); + } else { + pr_err("ERR:Must give me the DP IRQ!\n"); + return -EINVAL; + } + if (!of_property_read_u32(node, "sprd,def_da_fs", &val)) { + sprd_codec->da_sample_val = val; + sp_asoc_pr_dbg("Change DA default fs to %d!\n", val); + } + if (!of_property_read_u32_array(node, "sprd,reg_dp", &val_arr[0], 2)) { + sprd_codec_dp_base = ioremap_nocache(val_arr[0], val_arr[1]); + if(!sprd_codec_dp_base) { + pr_err("ERR: cannot create iomap address for codec DP!\n"); + return -EINVAL; + } + } else { + pr_err("ERR:Must give me the codec DP reg address!\n"); + return -EINVAL; + } + } else { + sprd_codec->ap_irq = CODEC_AP_IRQ; + sprd_codec->dp_irq = CODEC_DP_IRQ; + sprd_codec_dp_base = ioremap_nocache(CODEC_DP_BASE_DEFAULT, CODEC_DP_SIZE_DEFAULT); + if(!sprd_codec_dp_base) { + pr_err("ERR: cannot create iomap address for codec DP with default address!\n"); + return -EINVAL; + } + } + if(node) { + sprd_codec->ap_irq = irq_of_parse_and_map(node, 0); + } + ana_chip_id = sci_get_ana_chip_id() >> 16; + if (ana_chip_id == 0x2713) + sprd_codec->hp_ver = SPRD_CODEC_HP_PA_VER_1; + else if (ana_chip_id == 0x2711) + sprd_codec->hp_ver = SPRD_CODEC_HP_PA_VER_2; + else { + pr_info("ana_chip_id is 0x%x\n", (unsigned int)sci_get_ana_chip_id); + } + platform_set_drvdata(pdev, sprd_codec); + + ret = + request_irq(sprd_codec->ap_irq, sprd_codec_ap_irq, 0, + "sprd_codec_ap", sprd_codec); + if (ret) { + pr_err("ERR:Request irq ap failed!\n"); + goto err_irq; + } + + INIT_DELAYED_WORK(&sprd_codec->ovp_delayed_work, + sprd_codec_ovp_delay_worker); + + ret = + request_irq(sprd_codec->dp_irq, sprd_codec_dp_irq, 0, + "sprd_codec_dp", sprd_codec); + if (ret) { + pr_err("ERR:Request irq dp failed!\n"); + goto dp_err_irq; + } + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_sprd_codec, + sprd_codec_dai, + ARRAY_SIZE(sprd_codec_dai)); + if (ret != 0) { + pr_err("ERR:Failed to register CODEC: %d\n", ret); + goto reg_err_irq; + } + + sprd_codec_inter_pa_init(sprd_codec); + sprd_codec_inter_hp_pa_init(sprd_codec); + + sprd_codec_power_regulator_init(sprd_codec, &pdev->dev); + + mutex_init(&sprd_codec->inter_pa_mutex); + mutex_init(&sprd_codec->inter_hp_pa_mutex); + mutex_init(&sprd_codec->inter_hp_pa_delay_mutex); + spin_lock_init(&sprd_codec->sprd_codec_fun_lock); + spin_lock_init(&sprd_codec->sprd_codec_pa_sw_lock); + + sprd_codec_vcom_ldo_cfg(sprd_codec, ldo_v_map, ARRAY_SIZE(ldo_v_map)); + sprd_codec_pa_ldo_cfg(sprd_codec, ldo_v_map, ARRAY_SIZE(ldo_v_map)); + + sprd_codec->nb.notifier_call = hp_notifier_handler; +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + hp_register_notifier(&sprd_codec->nb); +#endif + + return 0; +reg_err_irq: + free_irq(sprd_codec->dp_irq, sprd_codec); +dp_err_irq: + free_irq(sprd_codec->ap_irq, sprd_codec); +err_irq: + iounmap(sprd_codec_dp_base); + return -EINVAL; +} + +static int sprd_codec_remove(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec = platform_get_drvdata(pdev); + sprd_codec_power_regulator_exit(sprd_codec); + free_irq(sprd_codec->ap_irq, sprd_codec); + free_irq(sprd_codec->dp_irq, sprd_codec); +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + hp_unregister_notifier(&sprd_codec->nb); +#endif + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id codec_of_match[] = { + {.compatible = "sprd,sprd-codec-v3",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, codec_of_match); +#endif + +static struct platform_driver sprd_codec_codec_driver = { + .driver = { + .name = "sprd-codec-v3", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(codec_of_match), + }, + .probe = sprd_codec_probe, + .remove = sprd_codec_remove, +}; + +module_platform_driver(sprd_codec_codec_driver); + +MODULE_DESCRIPTION("SPRD-CODEC ALSA SoC codec driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:sprd-codec"); diff --git a/sound/soc/sprd/codec/sprd/v3/sprd-codec.h b/sound/soc/sprd/codec/sprd/v3/sprd-codec.h new file mode 100755 index 00000000..daa91865 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v3/sprd-codec.h @@ -0,0 +1,404 @@ +/* + * sound/soc/sprd/codec/sprd/v3/sprd-codec.h + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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 __SPRD_CODEC_H +#define __SPRD_CODEC_H + +#include <soc/sprd/hardware.h> +#include <soc/sprd/globalregs.h> +#include <soc/sprd/sprd-audio.h> +#include <soc/sprd/adi.h> +#include <asm/io.h> + + +/* unit: ms */ +#define SPRD_CODEC_LDO_WAIT_TIME (5) +#define SPRD_CODEC_LDO_VCM_TIME (2) +#define SPRD_CODEC_DAC_MUTE_TIMEOUT (600) + +#define SPRD_CODEC_HP_POP_TIMEOUT (1000) + +#define SPRD_CODEC_RATE_8000 (10) +#define SPRD_CODEC_RATE_9600 ( 9) +#define SPRD_CODEC_RATE_11025 ( 8) +#define SPRD_CODEC_RATE_12000 ( 7) +#define SPRD_CODEC_RATE_16000 ( 6) +#define SPRD_CODEC_RATE_22050 ( 5) +#define SPRD_CODEC_RATE_24000 ( 4) +#define SPRD_CODEC_RATE_32000 ( 3) +#define SPRD_CODEC_RATE_44100 ( 2) +#define SPRD_CODEC_RATE_48000 ( 1) +#define SPRD_CODEC_RATE_96000 ( 0) + +/* AUD_TOP_CTL */ +#define DAC_EN_L (0) +#define ADC_EN_L (1) +#define DAC_EN_R (2) +#define ADC_EN_R (3) +#define ADC_SINC_SEL (8) +#define ADC_SINC_SEL_MASK (0x3) +#define ADC_DMIC_SEL (9) + +#define ADC1_EN_L (10) +#define ADC1_EN_R (11) +#define ADC1_SINC_SEL (14) +#define ADC1_SINC_SEL_MASK (0x3) +#define ADC1_DMIC1_SEL (15) + +/* AUD_DAC_CTL */ +#define DAC_MUTE_START (14) +#define DAC_MUTE_EN (15) + +/* AUD_ADC_CTL */ +#define ADC_SRC_N (0) +#define ADC_SRC_N_MASK (0x0f) +#define ADC1_SRC_N (4) +#define ADC1_SRC_N_MASK (0xf0) + +/* AUD_AUD_STS0 */ +#define DAC_MUTE_U_MASK (5) +#define DAC_MUTE_D_MASK (4) +#define DAC_MUTE_U_RAW (3) +#define DAC_MUTE_D_RAW (2) +#define DAC_MUTE_ST (0) +#define DAC_MUTE_ST_MASK (0x3) + +/* AUD_INT_CLR */ +/* AUD_INT_EN */ +#define DAC_MUTE_U (1) +#define DAC_MUTE_D (0) + +/*AUD_DMIC_CTL*/ +#define ADC_DMIC_CLK_MODE (0) +#define ADC_DMIC_CLK_MODE_MASK (0x3) +#define ADC_DMIC_LR_SEL (2) +#define ADC1_DMIC_CLK_MODE (3) +#define ADC1_DMIC_CLK_MODE_MASK (0x3) +#define ADC1_DMIC_LR_SEL (5) +#define ADC_DMIC_EN (6) +#define ADC1_DMIC1_EN (7) + +/* AUDIF_ENB */ +#define AUDIFA_DACL_EN (0) +#define AUDIFA_ADCL_EN (1) +#define AUDIFA_DACR_EN (2) +#define AUDIFA_ADCR_EN (3) + +/* AUDIF_INT_EN */ +/* AUDIF_INT_CLR */ +/* AUDIF_INT_RAW */ +/* AUDIF_INT_MASK */ +#define AUDIO_POP_IRQ (7) +#define OVP_IRQ (6) +#define OTP_IRQ (5) +#define PA_OCP_IRQ (4) +#define LOR_OCP_IRQ (3) +#define LOL_OCP_IRQ (2) +#define EAR_OCP_IRQ (1) +#define HP_OCP_IRQ (0) + +#define LDO_V_29 (0) +#define LDO_V_31 (1) +#define LDO_V_32 (2) +#define LDO_V_33 (3) +#define LDO_V_34 (4) +#define LDO_V_35 (5) +#define LDO_V_36 (6) +#define LDO_V_38 (7) + +#define MIC_LDO_V_21 (0) +#define MIC_LDO_V_19 (1) +#define MIC_LDO_V_23 (2) +#define MIC_LDO_V_25 (3) + +/* PMUR2_PMUR1 */ +#define PA_SW_EN (15) +#define PA_LDO_EN (14) +#define PA_EN (13) +#define PAR_SW_EN (12) +#define PAR_LDO_EN (11) +#define PAR_EN (10) +#define OVP_LDO_EN (8) +#define VB_EN (7) +#define VBO_EN (6) +#define HEADMICBIAS_EN (5) +#define MICBIAS_V (2) +#define MICBIAS_V_MASK (0x7) +/* #define MICBIAS_HV_EN (2) */ +#define HEADMIC_SLEEP_EN (1) + +/* PMUR4_PMUR3 */ +#define MICBIAS_EN (15) +#define AUXMICBIAS_EN (14) +#define VCM_V (11) +#define VCM_V_MASK (0x7) +#define BG_I (9) +#define BG_I_MASK (0x3) +#define AUXADC_EN (8) +#define BG_EN (7) +#define BG_RST (6) +#define BG_IBIAS_EN (5) +#define VCM_EN (4) +#define VCM_BUF_EN (3) +#define ICM_PLUS_EN (2) +#define SEL_VCMI (1) +#define VCMI_FAST_EN (0) + +/* PMUR6_PMUR5 */ +#define PA_SWOCP_PD (15) +#define PA_LDOOCP_PD (14) +#define PA_LDO_V (11) +#define PA_LDO_V_MASK (0x7) +#define VCM_CAL (0) +#define VCM_CAL_MASK (0x1F) + +/* PMUR8_PMUR7 */ +#define PA_OVP_V (13) +#define PA_OVP_V_MASK (0x7) +#define PA_OVP_542 (0) //5.48V for 2711 +#define PA_OVP_527 (1) //5.33V for 2711 +#define PA_OVP_512 (2) //5.19V for 2711 +#define PA_OVP_496 (3) //5.05V for 2711 +#define PA_OVP_481 (4) //4.91V for 2711 +#define PA_OVP_465 (5) //4.76V for 2711 +#define PA_OVP_450 (6) //4.62V for 2711 +#define PA_OVP_435 (7) //4.48V for 2711 + +/* CCR */ +#define ADC_CLK_EN (7) +#define ADC_CLK_RST (6) +#define ADC_CLK_F (4) +#define ADC_CLK_F_MASK (0x3) +#define DAC_CLK_EN (3) +#define DAC_CLK_F (1) +#define DAC_CLK_F_MASK (0x3) +#define DRV_CLK_EN (0) + +/* AACR2_AACR1 */ +#define ADCPGAL_BYP (14) +#define ADCPGAL_BYP_MASK (0x3) +#define ADCPGAL_EN (12) +#define ADCPGAL_EN_MASK (0x3) +#define ADCPGAR_BYP (10) +#define ADCPGAR_BYP_MASK (0x3) +#define ADCPGAR_EN (8) +#define ADCPGAR_EN_MASK (0x3) +#define ADC_IBUF_PD (7) +#define ADC_VREF1P5 (6) +#define ADCL_PD (5) +#define ADCL_RST (4) +#define ADCR_PD (3) +#define ADCR_RST (2) + +/* AAICR2_AAICR1 */ +#define MIC_ADCR (15) +#define AUXMIC_ADCR (14) +#define HEADMIC_ADCR (13) +#define AIL_ADCR (12) +#define AIR_ADCR (11) +#define MIC_ADCL (7) +#define AUXMIC_ADCL (6) +#define HEADMIC_ADCL (5) +#define AIL_ADCL (4) +#define AIR_ADCL (3) + +/* DACGR_DACR */ +#define DACL_EN (7) +#define DACR_EN (6) +#define DACBUF_I_S (5) + +/* DAOCR2 */ +#define ADCL_AOL (7) +#define ADCR_AOL (6) +#define DACL_AOL (5) +#define DACR_AOL (4) +#define ADCL_AOR (3) +#define ADCR_AOR (2) +#define DACL_AOR (1) +#define DACR_AOR (0) + +/* DAOCR3_DAOCR1 */ +#define DACL_EAR (15) +#define ADCL_P_HPL (7) +#define ADCR_P_HPL (6) +#define DACL_P_HPL (5) +#define DACR_P_HPL (4) +#define ADCL_N_HPR (3) +#define ADCR_P_HPR (2) +#define DACL_N_HPR (1) +#define DACR_P_HPR (0) + +/* DCR2_DCR1 */ +#define PA_D_EN (15) +#define PA_DTRI_F (13) +#define PA_DTRI_F_MASK (0x03) +#define PA_DEMI_EN (12) +#define PA_SS_EN (11) +#define PA_SS_RST (10) +#define DRV_STOP_EN (9) + +#define HPL_EN (7) +#define HPR_EN (6) +#define EAR_EN (5) +#define AOL_EN (4) +#define AOR_EN (3) +#define DIFF_EN (2) +#define HP_VCMI_EN (1) + +/* DCR4_DCR3 */ +#define DRV_OCP_AOL_PD (7) +#define DRV_OCP_AOR_PD (6) +#define DRV_OCP_EAR_PD (5) +#define DRV_OCP_HP_PD (4) + +/* DCR6_DCR5 */ +#define AUD_NG_EN (7) +#define AUD_NG_DA_EN (6) +#define AUD_NG_PA_EN (5) +/* DCR8_DCR7 */ +/* HP PA V2 */ +#define V2_AUDIO_CLASSG_EN (15) +#define V2_AUDIO_CHP_EN (14) +#define V2_AUDIO_CHP_HPL_EN (13) +#define V2_AUDIO_CHP_HPR_EN (12) +#define V2_AUDIO_CHP_LMUTE (11) +#define V2_AUDIO_CHP_RMUTE (10) +#define V2_AUDIO_CHP_CLK_SEL (9) +#define V2_AUDIO_CHP_OSC (6) +#define V2_AUDIO_CHP_OSC_MASK (0x07) +//befor BA version +#define V2_AUDIO_CHP_LPW_MASK (0x1) +#define V2_AUDIO_CHP_LPW (5) +#define V2_AUDIO_CHP_MODE (4) +//after BA version +#define V2_AUDIO_CHP_LPW_MASK_BA (0x3) +#define V2_AUDIO_CHP_LPW_BA (4) +#define V2_AUDIO_CHP_MODE_BA (3) + +#define V2_AUDIO_CGCAL_EN (2) +#define V2_AUDIO_CGCAL_STOP_EN (1) +#define V2_AUDIO_CHP_OCP_PD (0) + + +/* HP PA V0 */ +//after CA version +#define AUDIO_CHP_LPW_MASK_CA (0x3) +#define AUDIO_CHP_LPW_CA (14) +#define AUDIO_CHP_MODE_CA (13) +//befor CA version +#define AUDIO_CHP_LPW_MASK (0x1) +#define AUDIO_CHP_LPW (15) +#define AUDIO_CHP_MODE (14) + +#define AUDIO_CLASSG_EN (7) +#define AUDIO_CHP_EN (6) +#define AUDIO_CHP_HPL_EN (5) +#define AUDIO_CHP_HPR_EN (4) +#define AUDIO_CHP_LMUTE (3) +#define AUDIO_CHP_RMUTE (2) +#define AUDIO_CHP_OSC (0) +#define AUDIO_CHP_OSC_MASK (0x03) +#define AUDIO_CGCAL_EN (12) +#define AUDIO_CGCAL_STOP_EN (11) +#define AUDIO_CHP_OCP_PD (10) + +/* PNRCR2_PNRCR1 */ +#define HP_POP_CTL (6) +#define HP_POP_CTL_MASK (0x03) +#define HP_POP_CTL_DIS (0) +#define HP_POP_CTL_UP (1) +#define HP_POP_CTL_DOWN (2) +#define HP_POP_CTL_HOLD (3) + +#define HP_POP_STEP (3) +#define HP_POP_STEP_MASK (0x07) +#define HP_POP_STEP_012 (0) +#define HP_POP_STEP_025 (1) +#define HP_POP_STEP_05 (2) +#define HP_POP_STEP_1 (3) +#define HP_POP_STEP_2 (4) +#define HP_POP_STEP_4 (5) +#define HP_POP_STEP_8 (6) +#define HP_POP_STEP_16 (7) + +/* IFR2_IFR1 */ +#define HP_POP_FLG (4) +#define HP_POP_FLG_MASK (0x03) +#define HP_POP_FLG_NEAR_CMP (3) +#define OVP_FLAG (2) + + /*HIB*/ +#define HIB_SBUT_MASK (0x0f) +#define HIB_SBUT (0) +#define SPRD_CODEC_DP_BASE (CODEC_DP_BASE) +#define AUD_TOP_CTL (SPRD_CODEC_DP_BASE + 0x0000) +#define AUD_AUD_CTR (SPRD_CODEC_DP_BASE + 0x0004) +#define AUD_I2S_CTL (SPRD_CODEC_DP_BASE + 0x0008) +#define AUD_DAC_CTL (SPRD_CODEC_DP_BASE + 0x000C) +#define AUD_SDM_CTL0 (SPRD_CODEC_DP_BASE + 0x0010) +#define AUD_SDM_CTL1 (SPRD_CODEC_DP_BASE + 0x0014) +#define AUD_ADC_CTL (SPRD_CODEC_DP_BASE + 0x0018) +#define AUD_LOOP_CTL (SPRD_CODEC_DP_BASE + 0x001C) +#define AUD_AUD_STS0 (SPRD_CODEC_DP_BASE + 0x0020) +#define AUD_INT_CLR (SPRD_CODEC_DP_BASE + 0x0024) +#define AUD_INT_EN (SPRD_CODEC_DP_BASE + 0x0028) +#define AUD_DMIC_CTL (SPRD_CODEC_DP_BASE + 0x0030) +#define AUD_ADC1_I2S_CTL (SPRD_CODEC_DP_BASE + 0x0034) +#define SPRD_CODEC_DP_END (SPRD_CODEC_DP_BASE + 0x0034) +#define IS_SPRD_CODEC_DP_RANG(reg) (((reg) >= SPRD_CODEC_DP_BASE) && ((reg) < SPRD_CODEC_DP_END)) +#define SPRD_CODEC_AP_BASE (CODEC_AP_BASE) +#define AUDIF_ENB (SPRD_CODEC_AP_BASE + 0x0000) +#define AUDIF_OCP_OTP_TMR_CTL (SPRD_CODEC_AP_BASE + 0x0004) +#define AUDIF_OVPTMR_SHUTDOWN_CTL (SPRD_CODEC_AP_BASE + 0x0008) +#define AUDIF_INT_CLR (SPRD_CODEC_AP_BASE + 0x000C) +#define AUDIF_INT_EN (SPRD_CODEC_AP_BASE + 0x0010) +#define AUDIF_INT_RAW (SPRD_CODEC_AP_BASE + 0x0014) +#define AUDIF_INT_MASK (SPRD_CODEC_AP_BASE + 0x0018) +/* 0x001C ~ 0x003C is reserved for ADIE digital part */ +#define PMUR2_PMUR1 (SPRD_CODEC_AP_BASE + 0x0040) +#define PMUR4_PMUR3 (SPRD_CODEC_AP_BASE + 0x0044) +#define PMUR6_PMUR5 (SPRD_CODEC_AP_BASE + 0x0048) +#define PMUR8_PMUR7 (SPRD_CODEC_AP_BASE + 0x004C) +/* 0x0050 ~ 0x005C is reserved for analog power part */ +#define CCR (SPRD_CODEC_AP_BASE + 0x0060) +#define AACR2_AACR1 (SPRD_CODEC_AP_BASE + 0x0064) +#define AAICR2_AAICR1 (SPRD_CODEC_AP_BASE + 0x0068) +#define ACGR1 (SPRD_CODEC_AP_BASE + 0x006C) +#define ACGR3_ACGR2 (SPRD_CODEC_AP_BASE + 0x0070) +#define DACGR_DACR (SPRD_CODEC_AP_BASE + 0x0074) +#define DAOCR2 (SPRD_CODEC_AP_BASE + 0x0078) +#define DAOCR3_DAOCR1 (SPRD_CODEC_AP_BASE + 0x007C) +#define DCR2_DCR1 (SPRD_CODEC_AP_BASE + 0x0080) +#define DCR4_DCR3 (SPRD_CODEC_AP_BASE + 0x0084) +#define DCR6_DCR5 (SPRD_CODEC_AP_BASE + 0x0088) +#define DCR8_DCR7 (SPRD_CODEC_AP_BASE + 0x008C) +#define DCGR2_DCGR1 (SPRD_CODEC_AP_BASE + 0x0090) +#define DCGR3 (SPRD_CODEC_AP_BASE + 0x0094) +#define PNRCR2_PNRCR1 (SPRD_CODEC_AP_BASE + 0x0098) +#define PNRCR3 (SPRD_CODEC_AP_BASE + 0x009C) +#define HIBDR2_HIBDR1 (SPRD_CODEC_AP_BASE + 0x00A0) +#define HIBDR3 (SPRD_CODEC_AP_BASE + 0x00A4) +/* 0x00A8 ~ 0x00BC is reserved */ +#define IFR2_IFR1 (SPRD_CODEC_AP_BASE + 0x00C0) +#define AUD_DANGL (SPRD_CODEC_AP_BASE + 0x00C4) +#define AUD_DANGR (SPRD_CODEC_AP_BASE + 0x00C8) +/* 0x00C4 ~ 0x00C8 is reserved */ +#define IFR4_IFR3 (SPRD_CODEC_AP_BASE + 0x00CC) +/* 0x00D0 ~ 0x00D8 is reserved */ +#define SPRD_CODEC_AP_END (SPRD_CODEC_AP_BASE + 0x00D0) +#define IS_SPRD_CODEC_AP_RANG(reg) (((reg) >= SPRD_CODEC_AP_BASE) && ((reg) < SPRD_CODEC_AP_END)) +#define SPRD_CODEC_IIS1_ID 111 +#endif /* __SPRD_CODEC_H */ diff --git a/sound/soc/sprd/codec/sprd/v4/Kconfig b/sound/soc/sprd/codec/sprd/v4/Kconfig new file mode 100644 index 00000000..0f089aab --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/Kconfig @@ -0,0 +1,15 @@ +config SND_SOC_SPRD_CODEC_V4 + tristate # "Sprd-codec-v4" + +config SND_SOC_SPRD_AUDIO_USE_INTER_HP_PA_V4 + depends on SND_SOC_SPRD_CODEC_V4 + bool "Use spreadtrum internal HeadPhone PA(V4)" + help + Say Y if you want use sprdtrume internal headphone pa + +config SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + depends on SND_SOC_SPRD_CODEC_V4 + bool "Use spreadtrum ear jack type 1 or 3" + help + Say Y if you want to use sprdtrume ear jack type 1 or 3 + diff --git a/sound/soc/sprd/codec/sprd/v4/Makefile b/sound/soc/sprd/codec/sprd/v4/Makefile new file mode 100644 index 00000000..6b64e9e2 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/Makefile @@ -0,0 +1,7 @@ +#SPRD CODEC + +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/codec + +snd-soc-sprd-sprd-codec-v4-objs := sprd-codec.o sprd-audio-power.o +obj-$(CONFIG_SND_SOC_SPRD_CODEC_V4) += snd-soc-sprd-sprd-codec-v4.o diff --git a/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.c b/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.c new file mode 100644 index 00000000..d443c67f --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.c @@ -0,0 +1,672 @@ +/* + * sound/soc/sprd/codec/sprd/v4/sprd-audio-power.c + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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("POWER") fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#include <soc/sprd/sprd-audio.h> +#include "sprd-audio-power.h" +#include "sprd-asoc-common.h" + +#ifdef CONFIG_AUDIO_POWER_ALLOW_UNSUPPORTED +#define UNSUP_MASK 0x0000 +#else +#define UNSUP_MASK 0x8000 +#endif + +#define UNSUP(x) (UNSUP_MASK | (x)) +#define IS_UNSUP(x) (UNSUP_MASK & (x)) +#define LDO_MV(x) (~UNSUP_MASK & (x)) + +struct sprd_audio_power_info { + int id; + /* enable/disable register information */ + int en_reg; + int en_bit; + int (*power_enable) (void); + int (*power_disable) (void); + /* configure deepsleep cut off or not */ + int (*sleep_ctrl) (void *, int); + /* voltage level register information */ + int v_reg; + int v_mask; + int v_shift; + int v_table_len; + const u16 *v_table; + int min_uV; + int max_uV; + + /* unit: us */ + int on_delay; + int off_delay; + + /* used by regulator core */ + struct regulator_desc desc; + + void *data; +}; + +#define SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, d_id, \ +e_reg, e_bit, p_enable, p_disable, s_ctrl, \ +reg, mask, shift, table, table_size, d_on_delay, d_off_delay) \ +static struct regulator_consumer_supply label[] = { \ + { .supply = #label },\ +}; \ +static struct regulator_init_data label##_data = { \ + .supply_regulator = r_supply, \ + .constraints = { \ + .name = "SRG_" #label, \ + }, \ + .num_consumer_supplies = ARRAY_SIZE(label), \ + .consumer_supplies = label, \ +}; \ +static struct sprd_audio_power_info SPRD_AUDIO_POWER_INFO_##label = { \ + .id = d_id, \ + .en_reg = e_reg, \ + .en_bit = e_bit, \ + .power_enable = p_enable, \ + .power_disable = p_disable, \ + .sleep_ctrl = s_ctrl, \ + .v_reg = reg, \ + .v_mask = mask, \ + .v_shift = shift, \ + .v_table_len = table_size, \ + .v_table = table, \ + .on_delay = d_on_delay, \ + .off_delay = d_off_delay, \ + .desc = { \ + .name = #label, \ + .id = SPRD_AUDIO_POWER_##label, \ + .n_voltages = table_size, \ + .ops = &sprd_audio_power_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ +} + +#define SPRD_AUDIO_POWER_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + v_reg, v_mask, v_shift, v_table, ARRAY_SIZE(v_table), on_delay, off_delay) + +#define SPRD_AUDIO_POWER_SIMPLE_LDO(label, r_supply, id, en_reg, en_bit, sleep_ctrl, \ + on_delay, off_delay) \ +SPRD_AUDIO_POWER_LDO_LOW(label, r_supply, id, en_reg, en_bit, 0, 0, sleep_ctrl, \ + 0, 0, 0, 0, 0, on_delay, off_delay) + +#define SPRD_AUDIO_POWER_REG_LDO(label, id, power_enable, power_disable) \ +SPRD_AUDIO_POWER_LDO_LOW(label, 0, id, 0, 0, power_enable, power_disable, \ + 0, 0, 0, 0, 0, 0, 0, 0) + +static inline int sprd_power_read(struct sprd_audio_power_info *info, int reg) +{ + return arch_audio_power_read(reg); +} + +static inline int +sprd_power_write(struct sprd_audio_power_info *info, int reg, int val) +{ + int ret = 0; + ret = arch_audio_power_write(reg, val); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + arch_audio_power_read(reg)); + return ret; +} + +static inline int +sprd_power_u_bits(struct sprd_audio_power_info *info, + unsigned int reg, unsigned int mask, unsigned int value) +{ + int ret = 0; + ret = arch_audio_power_write_mask(reg, value, mask); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x] MASK:0x%x NAME:%s\n", reg & 0xFFFF, value, + arch_audio_power_read(reg), info->v_mask, info->desc.name); + return ret; +} + +static int sprd_audio_power_is_enabled(struct regulator_dev *rdev) +{ + int is_enable; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Is Enabled\n", rdev->desc->name); + + if (info->en_reg) { + is_enable = sprd_power_read(info, info->en_reg) & info->en_bit; + } else { + is_enable = info->en_bit; + } + + return ! !is_enable; +} + +static int audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + BUG_ON(!info->v_table); + + for (vsel = 0; vsel < info->v_table_len; vsel++) { + int mV = info->v_table[vsel]; + int uV; + + if (IS_UNSUP(mV)) + continue; + uV = LDO_MV(mV) * 1000; + + /* use the first in-range value */ + if (min_uV <= uV && uV <= max_uV) { + sp_asoc_pr_dbg("%s: %dus\n", __func__, uV); + sprd_power_u_bits(info, + info->v_reg, + info->v_mask << info->v_shift, + vsel << info->v_shift); + + return vsel; + } + } + return -EDOM; +} + +static int sprd_audio_power_enable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Enable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, + info->en_reg, info->en_bit, + info->en_bit); + } else { + if (info->power_enable) + ret = info->power_enable(); + info->en_bit = 1; + } + + udelay(info->on_delay); + + if (info->v_table) { + if (info->min_uV || info->max_uV) + audio_power_set_voltage(rdev, info->min_uV, + info->max_uV); + } + return ret; +} + +static int sprd_audio_power_disable(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + sp_asoc_pr_dbg("%s Disable\n", rdev->desc->name); + + if (info->en_reg) { + ret = sprd_power_u_bits(info, info->en_reg, info->en_bit, 0); + } else { + if (info->power_disable) + ret = info->power_disable(); + info->en_bit = 0; + } + + udelay(info->off_delay); + + return ret; +} + +static int sprd_audio_power_get_status(struct regulator_dev *rdev) +{ + sp_asoc_pr_dbg("%s Get Status\n", rdev->desc->name); + + if (!sprd_audio_power_is_enabled(rdev)) + return REGULATOR_STATUS_OFF; + return REGULATOR_STATUS_NORMAL; +} + +static int sprd_audio_power_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + int ret = 0; + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + + sp_asoc_pr_dbg("%s Set Mode 0x%x\n", rdev->desc->name, mode); + + if (!info->sleep_ctrl) { + return ret; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + ret = info->sleep_ctrl(info, 0); + break; + case REGULATOR_MODE_STANDBY: + ret = info->sleep_ctrl(info, 1); + break; + default: + return -EINVAL; + } + return ret; +} + +static const u16 VCOM_VSEL_table[] = { + 2800, 2900, 3000, 3100, + 3200, 3300, 3400, 3600, +}; + +static const u16 VPA_VSEL_table[] = { + 2900, 3100, 3200, 3300, + 3400, 3500, 3600, 3800, +}; + +static const u16 VMIC_VSEL_table[] = { + 2200, 2600, 2800, 3000, +}; + +static const u16 VBG_IBAIS_VSEL_table[] = { + 150, 200, 250, 300, +}; + +static const u16 HIB_VSEL_table[] = { + 1000, 950, 900, 850, 800, 750, + 700, 650, 600, 550, 500, 450, 400, +}; + +static const u16 VCGLDO_VSEL_table[] = { + 1600, 1700, 1800, 1900, + 2000, 2100, 2200, 2300, + 2400, 2500, 2600, 2700, + 2800, 2900, 3000, 3100, + 3200, 3300, 3400, +}; + +static int sprd_audio_power_list_voltage(struct regulator_dev *rdev, + unsigned index) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int mV; + + sp_asoc_pr_dbg("%s List Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + if (index >= info->v_table_len) { + return 0; + } + + mV = info->v_table[index]; + return IS_UNSUP(mV) ? 0 : (LDO_MV(mV) * 1000); +} + +static int +sprd_audio_power_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_info("%s Set Voltage %d--%d\n", rdev->desc->name, min_uV, + max_uV); + + if (!info->v_table) { + return 0; + } + info->min_uV = min_uV; + info->max_uV = max_uV; + + vsel = audio_power_set_voltage(rdev, min_uV, max_uV); + if (vsel != -EDOM) + *selector = vsel; + + return vsel; +} + +static int sprd_audio_power_get_voltage(struct regulator_dev *rdev) +{ + struct sprd_audio_power_info *info = rdev_get_drvdata(rdev); + int vsel; + + sp_asoc_pr_dbg("%s Get Voltage\n", rdev->desc->name); + + if (!info->v_table) { + return 0; + } + + vsel = (sprd_power_read(info, info->v_reg) & info->v_mask) + >> info->v_shift; + return LDO_MV(info->v_table[vsel]) * 1000; +} + +static struct regulator_ops sprd_audio_power_ops = { + .list_voltage = sprd_audio_power_list_voltage, + + .set_voltage = sprd_audio_power_set_voltage, + .get_voltage = sprd_audio_power_get_voltage, + + .enable = sprd_audio_power_enable, + .disable = sprd_audio_power_disable, + .is_enabled = sprd_audio_power_is_enabled, + + .set_mode = sprd_audio_power_set_mode, + + .get_status = sprd_audio_power_get_status, +}; + +static int sprd_audio_power_sleep_ctrl(void *data, int en) +{ + struct sprd_audio_power_info *info = data; + int reg, mask, value; + switch (info->desc.id) { + case SPRD_AUDIO_POWER_HEADMICBIAS: + reg = ANA_PMU0; + mask = BIT(HEADMIC_SLEEP_EN); + value = en ? mask : 0; + return sprd_power_u_bits(info, reg, mask, value); + break; + default: + BUG_ON(0); + } + return 0; +} + +static inline int vreg_enable(void) +{ + int ret = 0; + static int i = 0; + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + arch_audio_codec_analog_reg_enable(); + if (i == 0) { + arch_audio_codec_analog_reset(); + /*set initial value from xun.zhang*/ + /* changed by jian.chen */ + /* + arch_audio_codec_write_mask(PMUR4_PMUR3, BIT(ICM_PLUS_EN), + BIT(ICM_PLUS_EN)); + */ + i = 1; + } + return ret; +} + +static inline int vreg_disable(void) +{ + /*arch_audio_codec_reset();*/ + arch_audio_codec_analog_reg_disable(); + return 0; +} + +/* + * We list regulators here if systems need some level of + * software control over them after boot. + */ +SPRD_AUDIO_POWER_REG_LDO(VREG, 1, vreg_enable, vreg_disable); +SPRD_AUDIO_POWER_SIMPLE_LDO(VB, "VREG", 2, ANA_PMU0, BIT(VB_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(BG, "VB", 3, ANA_PMU1, BIT(BG_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(BG_IBIAS, "BG", 4, ANA_PMU1, BIT(BG_IBIAS_EN), NULL, + ANA_PMU3, BG_I_MASK, BG_I, VBG_IBAIS_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_LDO(VCOM, "BG_IBIAS", 5, ANA_PMU1, BIT(VCM_EN), NULL, + ANA_PMU2, VCM_V_MASK, VCM_V, VCOM_VSEL_table, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VCOM_BUF, "VCOM", 6, ANA_PMU1, BIT(VCM_BUF_EN), + NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(VBO, "VB", 7, ANA_PMU0, BIT(VBO_EN), NULL, 0, 20*1000); +SPRD_AUDIO_POWER_LDO(VMICBIAS, "VCOM", 8, 0, 0, NULL, + ANA_PMU2, MICBIAS_V_MASK, MICBIAS_V, VMIC_VSEL_table, 0, + 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(MICBIAS, "VMICBIAS", 9, ANA_PMU1, + BIT(MICBIAS_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_SIMPLE_LDO(AUXMICBIAS, "VMICBIAS", 10, ANA_PMU1, + BIT(AUXMICBIAS_EN), NULL, 0, 0); +SPRD_AUDIO_POWER_LDO(HEADMICBIAS, "VMICBIAS", 11, ANA_PMU0, + BIT(HEADMICBIAS_EN), sprd_audio_power_sleep_ctrl, + ANA_HDT0, HIB_SBUT_MASK, HIB_SBUT, HIB_VSEL_table, 0, + 0); +SPRD_AUDIO_POWER_LDO(CGLDO, "VREG", 12, ANA_PMU0, + BIT(LDOCG_EN), NULL, + ANA_PMU3, CG_LDO_V_MASK, CG_LDO_V, VCGLDO_VSEL_table, 0, + 0); + + +#define SPRD_OF_MATCH(comp, label) \ + { \ + .compatible = comp, \ + .data = &SPRD_AUDIO_POWER_INFO_##label, \ + } + +static const struct of_device_id sprd_audio_power_of_match[] = { + SPRD_OF_MATCH("sp,audio-vreg", VREG), + SPRD_OF_MATCH("sp,audio-vb", VB), + SPRD_OF_MATCH("sp,audio-bg", BG), + SPRD_OF_MATCH("sp,audio-bg-ibias", BG_IBIAS), + SPRD_OF_MATCH("sp,audio-vcom", VCOM), + SPRD_OF_MATCH("sp,audio-vcom-buf", VCOM_BUF), + SPRD_OF_MATCH("sp,audio-vbo", VBO), + SPRD_OF_MATCH("sp,audio-vmicbias", VMICBIAS), + SPRD_OF_MATCH("sp,audio-micbias", MICBIAS), + SPRD_OF_MATCH("sp,audio-auxmicbias", AUXMICBIAS), + SPRD_OF_MATCH("sp,audio-headmicbias", HEADMICBIAS), + SPRD_OF_MATCH("sp,audio-classgldo", CGLDO), +}; + +static int sprd_audio_power_probe(struct platform_device *pdev) +{ + int i, id; + struct sprd_audio_power_info *info; + const struct sprd_audio_power_info *template; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_dev *rdev; + struct regulator_config config = { }; + int min_index = 0; + int max_index = 0; + + id = pdev->id; + + sp_asoc_pr_info("Probe %d\n", id); + + initdata = pdev->dev.platform_data; + for (i = 0, template = NULL; i < ARRAY_SIZE(sprd_audio_power_of_match); + i++) { + template = sprd_audio_power_of_match[i].data; + if (!template || template->desc.id != id) + continue; + break; + } + + if (!template) + return -ENODEV; + + if (!initdata) + return -EINVAL; + + info = kmemdup(template, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* Constrain board-specific capabilities according to what + * this driver and the chip itself can actually do. + */ + c = &initdata->constraints; + c->valid_modes_mask |= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_STATUS; + + if (info->v_table) { + for (i = 0; i < info->v_table_len; i++) { + if (info->v_table[i] < info->v_table[min_index]) + min_index = i; + if (info->v_table[i] > info->v_table[max_index]) + max_index = i; + } + c->min_uV = LDO_MV((info->v_table)[min_index]) * 1000; + c->max_uV = LDO_MV((info->v_table)[max_index]) * 1000; + sp_asoc_pr_info("min_uV:%d, max_uV:%d\n", c->min_uV, c->max_uV); + } + info->min_uV = 0; + info->max_uV = 0; + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = info; + config.of_node = pdev->dev.of_node; + + rdev = regulator_register(&info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "ERR:Can't Register %s, %ld\n", + info->desc.name, PTR_ERR(rdev)); + kfree(info); + return PTR_ERR(rdev); + } + sp_asoc_pr_info("Register %s Success!\n", info->desc.name); + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int sprd_audio_power_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct twlreg_info *info = rdev->reg_data; + + regulator_unregister(platform_get_drvdata(pdev)); + kfree(info); + return 0; +} + +static struct platform_driver sprd_audio_power_driver = { + .driver = { + .name = "sprd-audio-power", + .owner = THIS_MODULE, + }, + .probe = sprd_audio_power_probe, + .remove = sprd_audio_power_remove, +}; + +static int __init sprd_audio_power_regulator_init(void) +{ + return platform_driver_register(&sprd_audio_power_driver); +} + +//postcore_initcall(sprd_audio_power_regulator_init); +module_init(sprd_audio_power_regulator_init); + +static void __exit sprd_audio_power_regulator_exit(void) +{ + platform_driver_unregister(&sprd_audio_power_driver); +} + +module_exit(sprd_audio_power_regulator_exit); + +MODULE_DESCRIPTION("SPRD audio power regulator driver"); +MODULE_AUTHOR("Jian Chen <jian.chen@spreadtrum.com>"); +MODULE_ALIAS("platform:sprd-audio-power"); +MODULE_LICENSE("GPL"); + +#define SPRD_AUDIO_DEVICE(label) \ +{ \ + .name = "sprd-audio-power", \ + .id = SPRD_AUDIO_POWER_##label, \ + .dev = { \ + .platform_data = &label##_data, \ + }, \ +} + +static struct platform_device sprd_audio_regulator_devices[] = { + SPRD_AUDIO_DEVICE(VREG), + SPRD_AUDIO_DEVICE(VB), + SPRD_AUDIO_DEVICE(BG), + SPRD_AUDIO_DEVICE(BG_IBIAS), + SPRD_AUDIO_DEVICE(VCOM), + SPRD_AUDIO_DEVICE(VCOM_BUF), + SPRD_AUDIO_DEVICE(VBO), + SPRD_AUDIO_DEVICE(VMICBIAS), + SPRD_AUDIO_DEVICE(MICBIAS), + SPRD_AUDIO_DEVICE(AUXMICBIAS), + SPRD_AUDIO_DEVICE(HEADMICBIAS), + SPRD_AUDIO_DEVICE(CGLDO), +}; + +#ifdef CONFIG_OF +static int sprd_audio_power_version(void) +{ + struct device_node *node = of_find_node_by_path("/sprd-audio-devices"); + struct device_node *child; + if (node) { + u32 val; + for_each_child_of_node(node, child) { + if (!of_property_read_u32 + (child, "sprd,audio_power_ver", &val)) { + sp_asoc_pr_dbg + ("Configure Audio Power Version is %d\n", + val); + return val; + } + } + pr_err("ERR:Not found sprd,audio_power_ver!\n"); + } else { + pr_err("ERR:No sprd-audio-devices Node!\n"); + } + return 0; +} +#else +int sprd_audio_power_version(void) + __attribute__ ((weak, alias("__sprd_audio_power_version"))); + +static int __sprd_audio_power_version(void) +{ + return 4; +} +#endif + +static int sprd_audio_power_add_devices(void) +{ + int ret = 0; + int i; + if (4 != sprd_audio_power_version()) { + return 0; + } + for (i = 0; i < ARRAY_SIZE(sprd_audio_regulator_devices); i++) { + ret = + platform_device_register(&sprd_audio_regulator_devices[i]); + if (ret < 0) { + pr_err("ERR:Add Regulator %d Failed %d\n", + sprd_audio_regulator_devices[i].id, ret); + return ret; + } + } + return ret; +} + +static int __init sprd_audio_power_init(void) +{ + return sprd_audio_power_add_devices(); +} + +postcore_initcall(sprd_audio_power_init); diff --git a/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.h b/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.h new file mode 100755 index 00000000..330d92d0 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/sprd-audio-power.h @@ -0,0 +1,36 @@ +/* + * sound/soc/sprd/codec/sprd/v4/sprd-audio-power.h + * + * SPRD-AUDIO-POWER -- SpreadTrum intergrated audio power supply. + * + * 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. + */ + +#ifndef __SPRD_AUDIO_POWER_H +#define __SPRD_AUDIO_POWER_H + +#include "sprd-codec.h" + +#define SPRD_AUDIO_POWER_VREG 1 +#define SPRD_AUDIO_POWER_VB 2 +#define SPRD_AUDIO_POWER_BG 3 +#define SPRD_AUDIO_POWER_BG_IBIAS 4 +#define SPRD_AUDIO_POWER_VCOM 5 +#define SPRD_AUDIO_POWER_VCOM_BUF 6 +#define SPRD_AUDIO_POWER_VBO 7 +#define SPRD_AUDIO_POWER_VMICBIAS 8 +#define SPRD_AUDIO_POWER_MICBIAS 9 +#define SPRD_AUDIO_POWER_AUXMICBIAS 10 +#define SPRD_AUDIO_POWER_HEADMICBIAS 11 +#define SPRD_AUDIO_POWER_CGLDO 12 + +#endif diff --git a/sound/soc/sprd/codec/sprd/v4/sprd-codec.c b/sound/soc/sprd/codec/sprd/v4/sprd-codec.c new file mode 100644 index 00000000..e11b20c9 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/sprd-codec.c @@ -0,0 +1,4943 @@ +/* + * sound/soc/sprd/codec/sprd/v4/sprd-codec.c + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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("CDCV4") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/atomic.h> +#include <linux/regulator/consumer.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/power_supply.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/sprd-audio-hook.h> + +#include <soc/sprd/sprd-audio.h> +#include <soc/sprd/globalregs.h> +#include "sprd-codec.h" +#include "sprd-asoc-common.h" +#include <soc/sprd/arch_misc.h> + +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE +#if (CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE != 32000) && (CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE != 48000) && (CONFIG_SND_SOC_CODEC_SRC_SAMPLE_RATE != 48000) +#error "We hope the CODEC sample rate is 32KHz or 48KHz!" +#endif +#define SRC_SUPPORT_RATE (SNDRV_PCM_RATE_44100) +#else /* !CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE */ +#define SRC_SUPPORT_RATE (0) +#endif + +#ifdef CONFIG_INPUT_HEADSET_SPRD_SC2723 +int hp_register_notifier(struct notifier_block *nb); +int hp_unregister_notifier(struct notifier_block *nb); +int get_hp_plug_state(void); +#endif + +#define SOC_REG(r) ((unsigned short)(r)) +#define FUN_REG(f) ((unsigned short)(-((f) + 1))) +#define ID_FUN(id, lr) ((unsigned short)(((id) << 1) | (lr))) + +#define SPRD_CODEC_AP_BASE_HI (SPRD_CODEC_AP_BASE & 0xFFFF0000) +#define SPRD_CODEC_DP_BASE_HI (SPRD_CODEC_DP_BASE & 0xFFFF0000) + +enum { + SPRD_CODEC_HP_PA_ORDER = 100, + SPRD_CODEC_HP_SWITCH_ORDER = 101, + SPRD_CODEC_ANA_MIXER_ORDER = 102, /*must be later with switch order*/ + SPRD_CODEC_HP_PA_POST_ORDER = 103, + SPRD_CODEC_HP_POP_ORDER = 104, + SPRD_CODEC_PA_ORDER = 104, /*last power up, later with ANA_MIXER order*/ + SPRD_CODEC_CG_PGA_ORDER +}; + +enum { + SPRD_CODEC_AILADCL_SET = 0, + SPRD_CODEC_AILADCR_SET, + SPRD_CODEC_AIRADCL_SET, + SPRD_CODEC_AIRADCR_SET, + SPRD_CODEC_LINEIN_END, +}; + + +enum { + SPRD_CODEC_PLAYBACK, + SPRD_CODEC_CAPTRUE, + SPRD_CODEC_CAPTRUE1, + SPRD_CODEC_CHAN_MAX, +}; + +static const char *sprd_codec_chan_name[SPRD_CODEC_CHAN_MAX] = { + "DAC", + "ADC", + "ADC1", +}; + +static inline const char *sprd_codec_chan_get_name(int chan_id) +{ + return sprd_codec_chan_name[chan_id]; +} + +enum { + SPRD_CODEC_PGA_START = 0, + SPRD_CODEC_PGA_SPKL = SPRD_CODEC_PGA_START, + SPRD_CODEC_PGA_SPKR, + SPRD_CODEC_PGA_HPL, + SPRD_CODEC_PGA_HPR, + SPRD_CODEC_PGA_EAR, + SPRD_CODEC_PGA_ADCL, + SPRD_CODEC_PGA_ADCR, + SPRD_CODEC_PGA_DACL, + SPRD_CODEC_PGA_DACR, + SPRD_CODEC_PGA_MIC, + SPRD_CODEC_PGA_AUXMIC, + SPRD_CODEC_PGA_HEADMIC, + SPRD_CODEC_PGA_AIL, + SPRD_CODEC_PGA_AIR, + SPRD_CODEC_PGA_CG_HPL_1, + SPRD_CODEC_PGA_CG_HPR_1, + SPRD_CODEC_PGA_CG_HPL_2, + SPRD_CODEC_PGA_CG_HPR_2, + + SPRD_CODEC_PGA_END +}; + +#define GET_PGA_ID(x) ((x)-SPRD_CODEC_PGA_START) +#define SPRD_CODEC_PGA_MAX (SPRD_CODEC_PGA_END - SPRD_CODEC_PGA_START) +static const char *sprd_codec_pga_debug_str[SPRD_CODEC_PGA_MAX] = { + "SPKL", + "SPKR", + "HPL", + "HPR", + "EAR", + "ADCL", + "ADCR", + "DACL", + "DACR", + "MIC", + "AUXMIC", + "HEADMIC", + "AIL", + "AIR", + "CG_HPL_1", + "CG_HPR_1", + "CG_HPL_2", + "CG_HPR_2", +}; + +typedef int (*sprd_codec_pga_set) (struct snd_soc_codec * codec, int pgaval); + +struct sprd_codec_pga { + sprd_codec_pga_set set; + int min; +}; + +struct sprd_codec_pga_op { + int pgaval; + sprd_codec_pga_set set; +}; + +typedef int (*sprd_codec_switch_set) (struct snd_soc_codec * codec, int on); +struct sprd_codec_switch{ + sprd_codec_switch_set set; + int min; +}; + +struct sprd_codec_switch_op { + int on; + sprd_codec_switch_set set; +}; + + +enum { + SPRD_CODEC_LEFT = 0, + SPRD_CODEC_RIGHT = 1, +}; + +enum { + SPRD_CODEC_MIXER_START = SPRD_CODEC_PGA_END + 20, + SPRD_CODEC_AIL = SPRD_CODEC_MIXER_START, + SPRD_CODEC_AIR, + SPRD_CODEC_MAIN_MIC, + SPRD_CODEC_AUX_MIC, + SPRD_CODEC_HP_MIC, + SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACL = SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_HP_DACR, + SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCL = SPRD_CODEC_HP_DAC_MIXER_MAX, + SPRD_CODEC_HP_ADCR, + SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACL = SPRD_CODEC_HP_MIXER_MAX, + SPRD_CODEC_SPK_DACR, + SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCL = SPRD_CODEC_SPK_DAC_MIXER_MAX, + SPRD_CODEC_SPK_ADCR, + SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_DACL = SPRD_CODEC_SPK_MIXER_MAX, + SPRD_CODEC_EAR_MIXER_MAX, + + SPRD_CODEC_MIXER_END +}; + +#define GET_MIXER_ID(x) ((unsigned short)(x)-ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) +#define SPRD_CODEC_MIXER_MAX (ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT) -ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) +static const char *sprd_codec_mixer_debug_str[SPRD_CODEC_MIXER_MAX] = { + "AIL->ADCL", + "AIL->ADCR", + "AIR->ADCL", + "AIR->ADCR", + "MAIN MIC->ADCL", + "MAIN MIC->ADCR", + "AUX MIC->ADCL", + "AUX MIC->ADCR", + "HP MIC->ADCL", + "HP MIC->ADCR", + "DACL->HPL", + "DACL->HPR", + "DACR->HPL", + "DACR->HPR", + "ADCL->HPL", + "ADCL->HPR", + "ADCR->HPL", + "ADCR->HPR", + "DACL->SPKL", + "DACL->SPKR", + "DACR->SPKL", + "DACR->SPKR", + "ADCL->SPKL", + "ADCL->SPKR", + "ADCR->SPKL", + "ADCR->SPKR", + "DACL->EAR", + "DACR->EAR(bug)" +}; + +#define IS_SPRD_CODEC_MIXER_RANG(reg) (((reg) >= ID_FUN(SPRD_CODEC_MIXER_START, SPRD_CODEC_LEFT)) && ((reg) <= ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT))) +#define IS_SPRD_CODEC_PGA_RANG(reg) (((reg) >= SPRD_CODEC_PGA_START) && ((reg) <= SPRD_CODEC_PGA_END)) +#define IS_SPRD_CODEC_MIC_BIAS_RANG(reg) (((reg) >= SPRD_CODEC_MIC_BIAS_START) && ((reg) <= SPRD_CODEC_MIC_BIAS_END)) +#define IS_SPRD_CODEC_SWITCH_RANG(reg) (((reg) >= SPRD_CODEC_SWITCH_START) && ((reg) <= SPRD_CODEC_SWITCH_END)) +#define IS_SPRD_CODEC_ADC_LOOP_RANG(reg) (((reg) >= SPRD_CODEC_ADC_LOOP_START) && ((reg) <= SPRD_CODEC_ADC_LOOP_END)) + +typedef int (*sprd_codec_mixer_set) (struct snd_soc_codec * codec, int on); +struct sprd_codec_mixer { + int on; + sprd_codec_mixer_set set; +}; + +extern uint32_t sprd_get_vbat_voltage(void); + +uint32_t sprd_get_vbat_voltage(void) + __attribute__ ((weak, alias("__sprd_get_vbat_voltage"))); +static uint32_t __sprd_get_vbat_voltage(void) +{ + pr_err("ERR: Can't get vbat!\n"); + return 3800; +} + +struct sprd_codec_ldo_v_map { + int ldo_v_level; + int volt; +}; + +const static struct sprd_codec_ldo_v_map ldo_v_map[] = { + /*{ , 3400}, */ + {LDO_V_29, 3500}, + {LDO_V_30, 3600}, + {LDO_V_31, 3700}, + {LDO_V_31, 3800}, + {LDO_V_31, 3900}, + {LDO_V_31, 4000}, + {LDO_V_31, 4100}, + {LDO_V_31, 4300}, +}; + +struct sprd_codec_ldo_cfg { + const struct sprd_codec_ldo_v_map *v_map; + int v_map_size; +}; + +struct sprd_codec_inter_pa { + /* FIXME little endian */ + u32 LDO_V_sel:4; + u32 DTRI_F_sel:4; + u32 is_DEMI_mode:1; + u32 is_classD_mode:1; + u32 is_LDO_mode:1; + u32 is_auto_LDO_mode:1; + u32 RESV:20; +}; + +struct sprd_codec_pa_setting { + union { + struct sprd_codec_inter_pa setting; + u32 value; + }; + int set; +}; + +struct sprd_codec_inter_hp_pa { + /* FIXME little endian */ + u32 class_g_osc:2; + u32 class_g_mode:2; + u32 class_g_low_power:2; + u32 class_g_cgcal:2; + u32 class_g_pa_en_delay_10ms:4; + u32 class_g_hp_switch_delay_10ms:4; + u32 class_g_hp_on_delay_20ms:4; +/* + u32 class_g_unmute_delay_100ms:3; + u32 class_g_cgcal_delay_10ms:3; + u32 class_g_close_delay_10ms:2; + u32 class_g_open_delay_10ms:4; + u32 class_g_vdd_delay_10ms:6; + u32 class_g_chp_delay_30ms:4; + u32 class_g_all_close_delay_100ms:2; + //u32 RESV:0; +*/ +}; + +struct sprd_codec_hp_pa_setting { + union { + struct sprd_codec_inter_hp_pa setting; + u32 value; + }; + int set; +}; + +enum { + SPRD_CODEC_MIC_BIAS_START = + ID_FUN(SPRD_CODEC_MIXER_END, SPRD_CODEC_LEFT) + 100, + SPRD_CODEC_MIC_BIAS = SPRD_CODEC_MIC_BIAS_START, + SPRD_CODEC_AUXMIC_BIAS, + SPRD_CODEC_HEADMIC_BIAS, + + SPRD_CODEC_MIC_BIAS_END +}; + +#define GET_MIC_BIAS_ID(x) ((x)-SPRD_CODEC_MIC_BIAS_START) +#define SPRD_CODEC_MIC_BIAS_MAX (SPRD_CODEC_MIC_BIAS_END -SPRD_CODEC_MIC_BIAS_START) +static const char *mic_bias_name[SPRD_CODEC_MIC_BIAS_MAX] = { + "Mic Bias", + "AuxMic Bias", + "HeadMic Bias", +}; +enum { + SPRD_CODEC_SWITCH_START = SPRD_CODEC_MIC_BIAS_END + 20, + SPRD_CODEC_DACL = SPRD_CODEC_SWITCH_START, + SPRD_CODEC_DACR, + SPRD_CODEC_HPLCGL, + SPRD_CODEC_HPRCGR, + + SPRD_CODEC_SWITCH_END +}; + +#define GET_SWITCH_ID(x) ((x)-SPRD_CODEC_SWITCH_START) +#define SPRD_CODEC_SWITCH_MAX (SPRD_CODEC_SWITCH_END-SPRD_CODEC_SWITCH_START) +static const char *switch_name[SPRD_CODEC_SWITCH_MAX] = { + "DACL", + "DACR", + "HPLCGL", + "HPRCGR", +}; +enum { + SPRD_CODEC_ADC_LOOP_START = SPRD_CODEC_SWITCH_END + 20, + SPRD_CODEC_ADC_DAC_ADIE_LOOP = SPRD_CODEC_ADC_LOOP_START, + SPRD_CODEC_ADC1_DAC_ADIE_LOOP, + SPRD_CODEC_ADC_DAC_DIGITAL_LOOP, + SPRD_CODEC_ADC1_DAC_DIGITAL_LOOP, + + SPRD_CODEC_ADC_LOOP_END +}; +#define GET_ADC_LOOP_ID(x) ((x)-SPRD_CODEC_ADC_LOOP_START) +#define SPRD_CODEC_ADC_LOOP_MAX (SPRD_CODEC_ADC_LOOP_END-SPRD_CODEC_ADC_LOOP_START) +static const char *adc_loop_name[SPRD_CODEC_ADC_LOOP_MAX] = { + "ADC->DAC ADIE LOOP", + "ADC1->DAC ADIE LOOP", + "ADC->DAC DIGITAL LOOP", + "ADC1->DAC DIGITAL LOOP", +}; + +static unsigned long sprd_codec_dp_base = 0; + +enum { + SPRD_CODEC_HP_PA_VER_1, + SPRD_CODEC_HP_PA_VER_2, + SPRD_CODEC_HP_PA_VER_MAX, +}; + +/* codec private data */ +struct sprd_codec_priv { + struct snd_soc_codec *codec; + int da_sample_val; + int ad_sample_val; + int ad1_sample_val; + struct sprd_codec_mixer mixer[SPRD_CODEC_MIXER_MAX]; + struct sprd_codec_pga_op pga[SPRD_CODEC_PGA_MAX]; + struct sprd_codec_switch_op switcher[SPRD_CODEC_SWITCH_MAX]; + int mic_bias[SPRD_CODEC_MIC_BIAS_MAX]; + + int ap_irq; + struct completion completion_hp_pop; + + int dp_irq; + struct completion completion_dac_mute; + struct power_supply audio_ldo; + + struct sprd_codec_pa_setting inter_pa; + struct mutex inter_pa_mutex; + + int hp_ver; + struct sprd_codec_hp_pa_setting inter_hp_pa; + struct mutex inter_hp_pa_mutex; + + struct regulator *main_mic; + struct regulator *aux_mic; + struct regulator *head_mic; + struct regulator *cg_ldo; + atomic_t ldo_refcount; + struct delayed_work ovp_delayed_work; + + struct sprd_codec_ldo_cfg s_vcom; + struct sprd_codec_ldo_cfg s_vpa; + +#define SPRD_CODEC_PA_SW_AOL (BIT(0)) +#define SPRD_CODEC_PA_SW_EAR (BIT(1)) +#define SPRD_CODEC_PA_SW_FUN (SPRD_CODEC_PA_SW_AOL | SPRD_CODEC_PA_SW_EAR) + int sprd_codec_fun; + spinlock_t sprd_codec_fun_lock; + spinlock_t sprd_codec_pa_sw_lock; + + int sprd_linein_mute; + int sprd_linein_set; + struct mutex sprd_linein_mute_mutex; + struct notifier_block nb; + int ad_da_loop_en[SPRD_CODEC_ADC_LOOP_MAX]; + + int sprd_dacspkl_enable; + int sprd_dacspkl_set; + struct mutex sprd_dacspkl_mutex; +}; + +const u32 low_power_chip_arr[] = { + 0x2723, AUDIO_2723_VER_E, + 0x2723, AUDIO_2723_VER_T +}; + +static int is_low_power_support() +{ + u32 chip_id = 0; + int ver_id = 0; + int i = 0, j = 0; + + chip_id = sci_get_ana_chip_id() >> 16; + ver_id = sci_get_ana_chip_ver(); + + pr_err("%s, chip_id=0x%x, ver_id=0x%x\n", __func__, chip_id, ver_id); + + while (i < ARRAY_SIZE(low_power_chip_arr)) { + if (low_power_chip_arr[i] == (u32)chip_id + && low_power_chip_arr[i + 1] == ver_id) + return 1; + i += 2; + } + + return 0; +} + +static int sprd_codec_power_get(struct device *dev, struct regulator **regu, + const char *id) +{ + if (!*regu) { + *regu = regulator_get(dev, id); + if (IS_ERR(*regu)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(*regu), id); + *regu = 0; + return -1; + } + } + return 0; +} + +static int sprd_codec_power_put(struct regulator **regu) +{ + if (*regu) { + regulator_set_mode(*regu, REGULATOR_MODE_NORMAL); + regulator_disable(*regu); + regulator_put(*regu); + *regu = 0; + } + return 0; +} + +static int sprd_codec_regulator_set(struct regulator **regu, int on) +{ + int ret = 0; + if (*regu) { + if (on) { + ret = regulator_enable(*regu); + if (ret) { + sprd_codec_power_put(regu); + return ret; + } + } else { + ret = regulator_disable(*regu); + } + } + return ret; +} + +#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 void sprd_codec_set_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun |= fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static void sprd_codec_clr_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + spin_lock(&sprd_codec->sprd_codec_fun_lock); + sprd_codec->sprd_codec_fun &= ~fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); +} + +static int sprd_codec_test_fun(struct sprd_codec_priv *sprd_codec, int fun) +{ + int ret; + spin_lock(&sprd_codec->sprd_codec_fun_lock); + ret = sprd_codec->sprd_codec_fun & fun; + spin_unlock(&sprd_codec->sprd_codec_fun_lock); + return ret; +} + +static void sprd_codec_wait(u32 wait_time) +{ + if (wait_time) + schedule_timeout_uninterruptible(msecs_to_jiffies(wait_time)); +} + +#if 0 /* sometime we will use this print function */ +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg); +static void sprd_codec_print_regs(struct snd_soc_codec *codec) +{ + int reg; + pr_warn("sprd_codec register digital part\n"); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_DP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } + pr_warn("sprd_codec register analog part\n"); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + pr_warn("0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - SPRD_CODEC_AP_BASE) + , sprd_codec_read(codec, reg + 0x00) + , sprd_codec_read(codec, reg + 0x04) + , sprd_codec_read(codec, reg + 0x08) + , sprd_codec_read(codec, reg + 0x0C) + ); + } +} +#endif + +static inline int _sprd_codec_ldo_cfg_set(struct sprd_codec_ldo_cfg *p_v_cfg, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + p_v_cfg->v_map = v_map; + p_v_cfg->v_map_size = v_map_size; + return 0; +} + +static inline int _sprd_codec_hold(int index, int volt, + const struct sprd_codec_ldo_v_map *v_map) +{ + int scope = 40; /* unit: mv */ + int ret = ((v_map[index].volt - volt) <= scope); + if (index >= 1) { + return (ret || ((volt - v_map[index - 1].volt) <= scope)); + } + return ret; +} + +static int sprd_codec_auto_ldo_volt(struct snd_soc_codec *codec, + void (*set_level) (struct snd_soc_codec *, + int), + const struct sprd_codec_ldo_v_map *v_map, + const int v_map_size, int init) +{ + int i; + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("Get vbat voltage %d\n", volt); + for (i = 0; i < v_map_size; i++) { + if (volt <= ldo_v_map[i].volt) { + sp_asoc_pr_dbg("Hold %d\n", + _sprd_codec_hold(i, volt, v_map)); + if (init || !_sprd_codec_hold(i, volt, v_map)) { + set_level(codec, v_map[i].ldo_v_level); + } + return 0; + } + } + return -EFAULT; +} + +static inline void sprd_codec_vcm_v_sel(struct snd_soc_codec *codec, int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("VCM Set %d\n", v_sel); + mask = VCM_V_MASK << VCM_V; + val = (v_sel << VCM_V) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU2), mask, val); +} + +static inline void sprd_codec_auxadc_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("AUXADC Set %d\n", on); + /*jian.chen changed*/ + /* + mask = BIT(AUXADC_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(PMUR4_PMUR3), mask, val); + */ +} + +static int sprd_codec_pga_spk_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC11; + mask = AOL_G_MASK << AOL_G; + val = (pgaval << AOL_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_spkr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC11; + mask = AOR_G_MASK << AOR_G; + val = (pgaval << AOR_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_hpl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC12; + mask = HPL_G_MASK << HPL_G; + val = (pgaval << HPL_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_hpr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC12; + mask = HPR_G_MASK << HPR_G; + val = (pgaval << HPR_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_ear_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC12; + mask = EAR_G_MASK << EAR_G; + val = (pgaval << EAR_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_adcl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC9; + mask = ADC_PGA_L_MASK << ADC_PGA_L; + val = (pgaval << ADC_PGA_L) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_adcr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC9; + mask = ADC_PGA_R_MASK << ADC_PGA_R; + val = (pgaval << ADC_PGA_R) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_dacl_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC10; + if (is_low_power_support()) { + if (pgaval > 0) { + pgaval = pgaval >= 6 ? pgaval : 6; + pgaval -= 5; + } + } + mask = DACL_G_MASK << DACL_G; + val = (pgaval << DACL_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_dacr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC10; + if (is_low_power_support()) { + if (pgaval > 0) { + pgaval = pgaval >= 6 ? pgaval : 6; + pgaval -= 5; + } + } + mask = DACR_G_MASK << DACR_G; + val = (pgaval << DACR_G) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_mic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC8; + mask = ADC_MIC_PGA_MASK << ADC_MIC_PGA; + val = (pgaval << ADC_MIC_PGA) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_auxmic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC8; + mask = ADC_AUXMIC_PGA_MASK << ADC_AUXMIC_PGA; + val = (pgaval << ADC_AUXMIC_PGA) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_headmic_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC8; + mask = ADC_HEADMIC_PGA_MASK << ADC_HEADMIC_PGA; + val = (pgaval << ADC_HEADMIC_PGA) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_ailr_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC8; + mask = ADC_AILR_PGA_MASK << ADC_AILR_PGA; + val = (pgaval << ADC_AILR_PGA) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_cg_hpl_1_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC13; + mask = CG_HPL_G_1_MASK << CG_HPL_G_1; + val = (pgaval << CG_HPL_G_1) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_cg_hpr_1_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC14; + mask = CG_HPR_G_1_MASK << CG_HPR_G_1; + val = (pgaval << CG_HPR_G_1) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_cg_hpl_2_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC13; + mask = CG_HPL_G_2_MASK << CG_HPL_G_2; + val = (pgaval << CG_HPL_G_2) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} + +static int sprd_codec_pga_cg_hpr_2_set(struct snd_soc_codec *codec, int pgaval) +{ + int reg, val, mask; + reg = ANA_CDC14; + mask = CG_HPR_G_2_MASK << CG_HPR_G_2; + val = (pgaval << CG_HPR_G_2) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} +static int sprd_codec_switch_dacl_set(struct snd_soc_codec *codec, int on) +{ + int reg, val, mask; + reg = ANA_CDC2; + mask = 1 << DACL_EN; + val = (on << DACL_EN) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} +static int sprd_codec_switch_dacr_set(struct snd_soc_codec *codec, int on) +{ + int reg, val, mask; + reg = ANA_CDC2; + mask = 1 << DACR_EN; + val = (on << DACR_EN) & mask; + + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} +static int sprd_codec_switch_hplcgl_set(struct snd_soc_codec *codec, int on) +{ + int reg, val, mask; + reg = ANA_CDC7; + mask = BIT(HPL_CGL); + val = (on << HPL_CGL) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} +static int sprd_codec_switch_hprcgr_set(struct snd_soc_codec *codec, int on) +{ + int reg, val, mask; + reg = ANA_CDC7; + mask = BIT(HPR_CGR); + val = (on << HPR_CGR) & mask; + return snd_soc_update_bits(codec, SOC_REG(reg), mask, val); +} +static struct sprd_codec_pga sprd_codec_pga_cfg[SPRD_CODEC_PGA_MAX] = { + {sprd_codec_pga_spk_set, 0}, + {sprd_codec_pga_spkr_set, 0}, + {sprd_codec_pga_hpl_set, 0}, + {sprd_codec_pga_hpr_set, 0}, + {sprd_codec_pga_ear_set, 0}, + + {sprd_codec_pga_adcl_set, 0}, + {sprd_codec_pga_adcr_set, 0}, + + {sprd_codec_pga_dacl_set, 0}, + {sprd_codec_pga_dacr_set, 0}, + {sprd_codec_pga_mic_set, 0}, + {sprd_codec_pga_auxmic_set, 0}, + {sprd_codec_pga_headmic_set, 0}, + {sprd_codec_pga_ailr_set, 0}, + {sprd_codec_pga_ailr_set, 0}, + {sprd_codec_pga_cg_hpl_1_set, -1}, + {sprd_codec_pga_cg_hpr_1_set, -1}, + {sprd_codec_pga_cg_hpl_2_set, -1}, + {sprd_codec_pga_cg_hpr_2_set, -1}, +}; +static struct sprd_codec_switch sprd_codec_switch_cfg[SPRD_CODEC_SWITCH_MAX] = { + {sprd_codec_switch_dacl_set, 0}, + {sprd_codec_switch_dacr_set, 0}, + {sprd_codec_switch_hplcgl_set, 0}, + {sprd_codec_switch_hprcgr_set, 0}, +}; + +static void _mixer_adc_linein_mute_nolock(struct snd_soc_codec *codec, int need_mute) +{ + int val = 0; + val = !need_mute; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + if (sprd_codec->sprd_linein_set & BIT(SPRD_CODEC_AILADCL_SET)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(AIL_ADCL), val << AIL_ADCL); + } + if (sprd_codec->sprd_linein_set & BIT(SPRD_CODEC_AILADCR_SET)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(AIL_ADCR), val << AIL_ADCR); + } + if (sprd_codec->sprd_linein_set & BIT(SPRD_CODEC_AIRADCL_SET)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(AIR_ADCL), val << AIR_ADCL); + } + if (sprd_codec->sprd_linein_set & BIT(SPRD_CODEC_AIRADCR_SET)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(AIR_ADCR), val << AIR_ADCR); + } +} + +static int _mixer_adc_linein_set(struct snd_soc_codec *codec, int id, unsigned int mask, + int on, unsigned int shift) +{ + int ret = 0; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + mutex_lock(&sprd_codec->sprd_linein_mute_mutex); + if (on) { + sprd_codec->sprd_linein_set |= BIT(id); + } else { + sprd_codec->sprd_linein_set &= ~(BIT(id)); + } + if (!sprd_codec->sprd_linein_mute) { + ret = snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), mask, on << shift); + } + mutex_unlock(&sprd_codec->sprd_linein_mute_mutex); + return ret; +} + +/* adc mixer */ + +static int ailadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return _mixer_adc_linein_set(codec, SPRD_CODEC_AILADCL_SET, BIT(AIL_ADCL), + on , AIL_ADCL); +} + +static int ailadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return _mixer_adc_linein_set(codec, SPRD_CODEC_AILADCR_SET, BIT(AIL_ADCR), + on , AIL_ADCR); +} + +static int airadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return _mixer_adc_linein_set(codec, SPRD_CODEC_AIRADCL_SET, BIT(AIR_ADCL), + on , AIR_ADCL); +} + +static int airadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return _mixer_adc_linein_set(codec, SPRD_CODEC_AIRADCR_SET, BIT(AIR_ADCR), + on , AIR_ADCR); +} + +static int mainmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(MIC_ADCL), + on << MIC_ADCL); +} + +static int mainmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), BIT(MIC_ADCR), + on << MIC_ADCR); +} + +static int auxmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), + BIT(AUXMIC_ADCL), on << AUXMIC_ADCL); +} + +static int auxmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), + BIT(AUXMIC_ADCR), on << AUXMIC_ADCR); +} + +static int hpmicadcl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), + BIT(HEADMIC_ADCL), on << HEADMIC_ADCL); +} + +static int hpmicadcr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC5), + BIT(HEADMIC_ADCR), on << HEADMIC_ADCR); +} + +/* hp mixer */ + +static int daclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(DACL_P_HPL), on << DACL_P_HPL); +} + +static int daclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(DACL_N_HPR), on << DACL_N_HPR); +} + +static int dacrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(DACR_P_HPL), on << DACR_P_HPL); +} + +static int dacrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(DACR_P_HPR), on << DACR_P_HPR); +} + +static int adclhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(ADCL_P_HPL), on << ADCL_P_HPL); +} + +static int adclhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(ADCL_N_HPR), on << ADCL_N_HPR); +} + +static int adcrhpl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(ADCR_P_HPL), on << ADCR_P_HPL); +} + +static int adcrhpr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), + BIT(ADCR_P_HPR), on << ADCR_P_HPR); +} + +/* spkl mixer */ + +static int daclspkl_set(struct snd_soc_codec *codec, int on) +{ + int ret = 0; + int enable; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mutex_lock(&sprd_codec->sprd_dacspkl_mutex); + if(on){ + sprd_codec->sprd_dacspkl_set |= BIT(SPRD_CODEC_LEFT); + } else { + sprd_codec->sprd_dacspkl_set &= ~BIT(SPRD_CODEC_LEFT); + } + enable = sprd_codec->sprd_dacspkl_enable & BIT(SPRD_CODEC_LEFT); + sp_asoc_pr_dbg("%s %d,enalbe :%d\n", __func__, on,enable); + if(enable){ + ret = snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACL_AOL), + on << DACL_AOL); + } + mutex_unlock(&sprd_codec->sprd_dacspkl_mutex); +} + +static int dacrspkl_set(struct snd_soc_codec *codec, int on) +{ + int ret = 0; + int enable; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s %d\n", __func__, on); + mutex_lock(&sprd_codec->sprd_dacspkl_mutex); + if(on){ + sprd_codec->sprd_dacspkl_set |= BIT(SPRD_CODEC_RIGHT); + } else { + sprd_codec->sprd_dacspkl_set &= ~BIT(SPRD_CODEC_RIGHT); + } + enable = sprd_codec->sprd_dacspkl_enable & BIT(SPRD_CODEC_RIGHT); + sp_asoc_pr_dbg("%s %d,enalbe :%d\n", __func__, on,enable); + if(enable){ + ret = snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACR_AOL), + on << DACR_AOL); + } + mutex_unlock(&sprd_codec->sprd_dacspkl_mutex); + return ret; +} + +static int adclspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(ADCL_AOL), + on << ADCL_AOL); +} + +static int adcrspkl_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(ADCR_AOL), + on << ADCR_AOL); +} + +/* spkr mixer */ + +static int daclspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACL_AOR), + on << DACL_AOR); +} + +static int dacrspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACR_AOR), + on << DACR_AOR); +} + +static int adclspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(ADCL_AOR), + on << ADCL_AOR); +} + +static int adcrspkr_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(ADCR_AOR), + on << ADCR_AOR); +} + +/* ear mixer */ + +static int daclear_set(struct snd_soc_codec *codec, int on) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, on); + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC7), BIT(DACL_EAR), + on << DACL_EAR); +} + +static sprd_codec_mixer_set mixer_setting[SPRD_CODEC_MIXER_MAX] = { + /* adc mixer */ + ailadcl_set, ailadcr_set, + airadcl_set, airadcr_set, + mainmicadcl_set, mainmicadcr_set, + auxmicadcl_set, auxmicadcr_set, + hpmicadcl_set, hpmicadcr_set, + /* hp mixer */ + daclhpl_set, daclhpr_set, + dacrhpl_set, dacrhpr_set, + adclhpl_set, adclhpr_set, + adcrhpl_set, adcrhpr_set, + /* spk mixer */ + daclspkl_set, daclspkr_set, + dacrspkl_set, dacrspkr_set, + adclspkl_set, adclspkr_set, + adcrspkl_set, adcrspkr_set, + /* ear mixer */ + daclear_set, 0, +}; + +/* DO NOT USE THIS FUNCTION */ +static inline void __sprd_codec_pa_sw_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + mask = BIT(PA_SW_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU0), mask, val); +} + +static inline void __sprd_codec_speaker_pa_ibias_set(struct snd_soc_codec *codec, int c_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s %d\n", __func__, c_sel); + mask = PA_AB_I_MASK << PA_AB_I; + val = (c_sel << PA_AB_I) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU3), mask, val); +} + +static void sprd_inter_speaker_pa_pre(struct snd_soc_codec *codec) +{ + /* set class-AB Ibias to 7.4/4.7 from weifeng */ + __sprd_codec_speaker_pa_ibias_set(codec, 0); +} + +static inline void sprd_codec_pa_sw_set(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_set_fun(sprd_codec, fun); + __sprd_codec_pa_sw_en(codec, 1); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_pa_sw_clr(struct snd_soc_codec *codec, int fun) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("%s fun 0x%08x\n", __func__, fun); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + sprd_codec_clr_fun(sprd_codec, fun); + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + __sprd_codec_pa_sw_en(codec, 0); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +/* inter PA */ + +static inline void sprd_codec_pa_d_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_D_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC4), mask, val); +} + +static inline void sprd_codec_pa_demi_en(struct snd_soc_codec *codec, int on) +{ + int mask; + + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + if (on) + /*when emi is on, need to set this register from xun && junjie.chen*/ + snd_soc_update_bits(codec, SOC_REG(ANA_PMU6), 0xffff, 0x400); + else + snd_soc_update_bits(codec, SOC_REG(ANA_PMU6), 0xffff, 0); + + mask = BIT(PA_DEMI_EN); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC4), mask, on?mask:0); + + /*open ocp from xun && weifeng*/ + mask = BIT(DRV_OCP_AOL_PD) | BIT(DRV_OCP_AOR_PD); + snd_soc_update_bits(codec, SOC_REG(ANA_PMU6), mask, 0); +} + +static inline void sprd_codec_pa_ldo_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(PA_LDO_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU0), mask, val); + if (on) { + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + } +} + +static inline void sprd_codec_pa_ldo_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_LDO_V_MASK << PA_LDO_V; + val = (v_sel << PA_LDO_V) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU3), mask, val); +} + +static inline void sprd_codec_pa_ovp_v_sel(struct snd_soc_codec *codec, + int v_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, v_sel); + mask = PA_OVP_V_MASK << PA_OVP_V; + val = (v_sel << PA_OVP_V) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU5), mask, val); +} + +static inline void sprd_codec_pa_dtri_f_sel(struct snd_soc_codec *codec, + int f_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, f_sel); + mask = PA_DTRI_F_MASK << PA_DTRI_F; + val = (f_sel << PA_DTRI_F) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC4), mask, val); +} + +static inline void sprd_codec_pa_en(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + spin_lock(&sprd_codec->sprd_codec_pa_sw_lock); + if (on) { + mask = BIT(PA_EN); + val = mask; + } else { + if (!sprd_codec_test_fun(sprd_codec, SPRD_CODEC_PA_SW_FUN)) + mask = BIT(PA_EN) | BIT(PA_SW_EN) | BIT(PA_LDO_EN); + else + mask = BIT(PA_EN) | BIT(PA_LDO_EN); + val = 0; + } + snd_soc_update_bits(codec, SOC_REG(ANA_PMU0), mask, val); + spin_unlock(&sprd_codec->sprd_codec_pa_sw_lock); +} + +static inline void sprd_codec_ovp_ldo_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + mask = BIT(OVP_LDO_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU0), mask, val); +} + +static inline void sprd_codec_linein_mute_init(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec->sprd_linein_mute = 0; + sprd_codec->sprd_linein_set = 0; + mutex_init(&sprd_codec->sprd_linein_mute_mutex); +} + +static inline void sprd_codec_inter_pa_init(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec->inter_pa.setting.LDO_V_sel = 0x03; + sprd_codec->inter_pa.setting.DTRI_F_sel = 0x01; +} +static inline void sprd_codec_dacspkl_enable_init(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec->sprd_dacspkl_enable = BIT(SPRD_CODEC_RIGHT)|BIT(SPRD_CODEC_LEFT); + sprd_codec->sprd_dacspkl_set = 0; + mutex_init(&sprd_codec->sprd_dacspkl_mutex); +} + +static inline void sprd_codec_switch_init(struct sprd_codec_priv *sprd_codec) +{ + int i = 0; + for(i=0;i<SPRD_CODEC_SWITCH_MAX;i++){ + sprd_codec->switcher[i].set = NULL; + sprd_codec->switcher[i].on = 1; + } +} + +static void sprd_codec_ovp_irq_enable(struct snd_soc_codec *codec) +{ + int val_clr, val_en; + val_clr = BIT(OVP_IRQ) << AUD_A_INT_CLR; + val_en = BIT(OVP_IRQ) << AUD_A_INT_EN; + snd_soc_update_bits(codec, SOC_REG(DIG_CFG3), val_clr, val_clr); + snd_soc_update_bits(codec, SOC_REG(DIG_CFG4), val_en, val_en); +} + +static void sprd_codec_ovp_start(struct sprd_codec_priv *sprd_codec, int ms) +{ + schedule_delayed_work(&sprd_codec->ovp_delayed_work, + msecs_to_jiffies(ms)); +} + +static int sprd_ovp_irq(struct snd_soc_codec *codec, int is_ovp) +{ + int volt = sprd_get_vbat_voltage(); + sp_asoc_pr_dbg("OVP %d curr_volt %d\n", is_ovp, volt); + if (is_ovp) { + sprd_codec_pa_en(codec, 0); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC2), BIT(AOL_EN), 0); + } else { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC2), BIT(AOL_EN), + BIT(AOL_EN)); + sprd_codec_pa_en(codec, 1); + } + return 0; +} + +#define to_sprd_codec(x) container_of((x), struct sprd_codec_priv, ovp_delayed_work) +#define to_delay_worker(x) container_of((x), struct delayed_work, work) +static void sprd_codec_ovp_delay_worker(struct work_struct *work) +{ + struct sprd_codec_priv *sprd_codec = + to_sprd_codec(to_delay_worker(work)); + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!(snd_soc_read(codec, ANA_STS0) & BIT(OVP_FLAG))) { + sprd_ovp_irq(codec, 0); + } else { + sprd_ovp_irq(codec, 1); + sprd_codec_ovp_start(sprd_codec, 500); + sprd_codec_pa_ldo_v_sel(codec, PA_LDO_V_36); + } +} + +static inline int sprd_codec_pa_ldo_auto(struct snd_soc_codec *codec, int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt + (codec, sprd_codec_pa_ldo_v_sel, sprd_codec->s_vpa.v_map, + sprd_codec->s_vpa.v_map_size, init); +} + +static inline int sprd_codec_pa_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vpa, v_map, v_map_size); +} + +static int sprd_inter_speaker_pa(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_pa *p_setting = &sprd_codec->inter_pa.setting; + sp_asoc_pr_info("inter PA Switch %s\n", STR_ON_OFF(on)); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (on) { + sprd_inter_speaker_pa_pre(codec); + /* set ldo_v to 3.6v for ovp as weifeng's suggestion */ + sprd_codec_pa_ldo_v_sel(codec, PA_LDO_V_36); + sprd_codec_pa_ovp_v_sel(codec, PA_OVP_435); + sprd_codec_ovp_ldo_en(codec,1); + sprd_codec_ovp_irq_enable(codec); + sprd_codec_pa_d_en(codec, p_setting->is_classD_mode); + sprd_codec_pa_demi_en(codec, p_setting->is_DEMI_mode); + sprd_codec_pa_ldo_en(codec, p_setting->is_LDO_mode); + if (p_setting->is_LDO_mode) { + if (p_setting->is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(codec, 1); + } else { + sprd_codec_pa_ldo_v_sel(codec, + p_setting->LDO_V_sel); + } + } else { + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + } + sprd_codec_pa_dtri_f_sel(codec, p_setting->DTRI_F_sel); + /* delay 20ms as weifeng's suggestion to avoid pop noise from spk */ + sprd_codec_wait(20); + sprd_codec_pa_en(codec, 1); + sprd_codec->inter_pa.set = 1; + } else { + sprd_codec->inter_pa.set = 0; + sprd_codec_pa_en(codec, 0); + sprd_codec_wait(2); + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + sprd_codec_pa_ldo_en(codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + return 0; +} + +static int spk_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_speaker_pa(w->codec, on); +} + +#define SPRD_CODEC_HP_VER(bit) \ +do { \ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); \ + switch(sprd_codec->hp_ver) { \ + case SPRD_CODEC_HP_PA_VER_2: \ + mask = BIT(V2_##bit); \ + break; \ + case SPRD_CODEC_HP_PA_VER_1: \ + default: \ + mask = BIT(bit); \ + break; \ + } \ +} while(0) + +static inline void sprd_codec_hp_classg_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + //SPRD_CODEC_HP_VER(AUDIO_CLASSG_EN); + mask = BIT(CG_REF_EN); + val = on ? mask : 0; + /* FIXME: change by jian.chen */ + //snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_lpw(struct snd_soc_codec *codec, int lpw) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, lpw); + /* FIXME: change by jian.chen */ + /* + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + if (sci_get_ana_chip_ver() >= SC2711BA) { + mask = V2_AUDIO_CHP_LPW_MASK_BA << V2_AUDIO_CHP_LPW_BA; + val = (lpw << V2_AUDIO_CHP_LPW_BA) & mask; + } else { + mask = V2_AUDIO_CHP_LPW_MASK << V2_AUDIO_CHP_LPW; + val = (lpw << V2_AUDIO_CHP_LPW) & mask; + } + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + if (sci_get_ana_chip_ver() >= SC2713CA) { + mask = AUDIO_CHP_LPW_MASK_CA << AUDIO_CHP_LPW_CA; + val = (lpw << AUDIO_CHP_LPW_CA) & mask; + } else { + mask = AUDIO_CHP_LPW_MASK << AUDIO_CHP_LPW; + val = (lpw << AUDIO_CHP_LPW) & mask; + } + break; + } + */ + mask = CG_LPW_MASK << CG_LPW; + if (is_low_power_support()) + val = (0x2 << CG_LPW) & mask; + else + val = (lpw << CG_LPW) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_mode(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + if (sci_get_ana_chip_ver() >= SC2711BA) + mask = BIT(V2_AUDIO_CHP_MODE_BA ); + else + mask = BIT(V2_AUDIO_CHP_MODE); + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + if (sci_get_ana_chip_ver() >= SC2713CA) + mask = BIT(AUDIO_CHP_MODE_CA ); + else + mask = BIT(AUDIO_CHP_MODE ); + break; + } + */ + mask = BIT(CG_HP_MODE); + val = on? mask :0; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_osc(struct snd_soc_codec *codec, int osc) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, osc); + /* FIXME: change by jian.chen */ + /* + switch (sprd_codec->hp_ver) { + case SPRD_CODEC_HP_PA_VER_2: + mask = V2_AUDIO_CHP_OSC_MASK << V2_AUDIO_CHP_OSC; + val = (osc << V2_AUDIO_CHP_OSC) & mask; + break; + case SPRD_CODEC_HP_PA_VER_1: + default: + mask = AUDIO_CHP_OSC_MASK << AUDIO_CHP_OSC; + val = (osc << AUDIO_CHP_OSC) & mask; + break; + } + */ + mask = CG_CHP_OSC_MASK << CG_CHP_OSC; + val = (osc << CG_CHP_OSC) & mask; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CHP_EN); + */ + mask = BIT(CG_CHP_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_hpl_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CHP_HPL_EN); + */ + mask = BIT(CG_HPL_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_cg_ldo_en(struct snd_soc_codec *codec, int on) +{ + int ret = 0; + struct regulator **regu; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + if(!sprd_codec->cg_ldo) { + ret = sprd_codec_power_get(0, &sprd_codec->cg_ldo, "CGLDO"); + if (ret) { + pr_err("ERR:%s get CGLDO error %d\n", __func__, ret); + return; + } + } + regu = &sprd_codec->cg_ldo; + sprd_codec_regulator_set(regu, on); +} + +static inline void sprd_codec_hp_cg_dangl_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* set this value from xun.zhang */ + mask = 0xFFFF; + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DANGL), mask, val); +} + +static inline void sprd_codec_hp_cg_dangr_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* set this value from xun.zhang */ + mask = 0xFFFF; + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DANGR), mask, val); +} + +static inline void sprd_codec_hp_pa_hpr_en(struct snd_soc_codec *codec, int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CHP_HPR_EN); + */ + mask = BIT(CG_HPR_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_hp_pa_hpl_mute(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CHP_LMUTE); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); + */ +} + +static inline void sprd_codec_hp_pa_hpr_mute(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CHP_RMUTE); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(DCR8_DCR7), mask, val); + */ +} + +static inline void sprd_codec_hp_pa_cgcal_en(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + /* FIXME: change by jian.chen */ + /* + SPRD_CODEC_HP_VER(AUDIO_CGCAL_EN); + */ + mask = BIT(CG_HPCAL_EN); + val = on ? mask : 0; + if (is_low_power_support()) + if (val == 0) + val = mask; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), mask, val); +} + +static inline void sprd_codec_ldocg_fal_ild_en(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + + mask = BIT(LDOCG_EN_FAL_ILD); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), mask, val); +} + +static inline void sprd_codec_ldocg_iset_fal_current_sel(struct snd_soc_codec *codec, + int i_sel) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, i_sel); + + mask = BIT(LDOCG_ISET_FAL); + val = i_sel ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), mask, val); +} + +static inline void sprd_codec_ldocg_ldocg_ramp_en(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + + mask = BIT(LDOCG_RAMP_EN); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), mask, val); +} + +static inline void sprd_codec_head_l_int_pu_pd(struct snd_soc_codec *codec, + int on) +{ + int mask; + int val; + sp_asoc_pr_dbg("%s set %d\n", __func__, on); + + mask = BIT(AUD_HEAD_L_INT_PU_PD); + val = on ? mask : 0; + snd_soc_update_bits(codec, SOC_REG(ANA_HDT1), mask, val); +} + +static inline void sprd_codec_inter_hp_pa_init(struct sprd_codec_priv + *sprd_codec) +{ + sprd_codec->inter_hp_pa.setting.class_g_osc = 0x01; + sprd_codec->inter_hp_pa.setting.class_g_cgcal = 1; + sprd_codec->inter_hp_pa.setting.class_g_pa_en_delay_10ms = 1; + sprd_codec->inter_hp_pa.setting.class_g_hp_switch_delay_10ms = 3; + sprd_codec->inter_hp_pa.setting.class_g_hp_on_delay_20ms = 1; + + //sprd_codec->inter_hp_pa.setting.class_g_open_delay_10ms = 0; + //sprd_codec->inter_hp_pa.setting.class_g_vdd_delay_10ms = 0; + //sprd_codec->inter_hp_pa.setting.class_g_chp_delay_30ms = 0; + //sprd_codec->inter_hp_pa.setting.class_g_all_close_delay_100ms = 0; + /* set class-G default pga from weifeng */ + sprd_codec->pga[SPRD_CODEC_PGA_CG_HPL_1].pgaval = 0x10; + sprd_codec->pga[SPRD_CODEC_PGA_CG_HPR_1].pgaval = 0x10; + sprd_codec->pga[SPRD_CODEC_PGA_CG_HPL_2].pgaval = 0x7; + sprd_codec->pga[SPRD_CODEC_PGA_CG_HPR_2].pgaval = 0x7; +} + + +static void sprd_inter_headphone_pa_pre(struct sprd_codec_priv *sprd_codec,int on) +{ + struct snd_soc_codec *codec = sprd_codec->codec; + static struct regulator *regulator = 0; + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + + static struct regulator *regulator_reg= 0; + +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + static struct regulator *regulator_vb= 0; + int classg_vol = 2200000; +#endif + return ; + + if (on) { + if (!regulator) { + sp_asoc_pr_dbg("%s set vddclsg on\n", __func__); + /*1.CG_EN */ + //open clk + regulator_reg = regulator_get(0, "VREG"); + if (IS_ERR(regulator_reg)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_reg), "VREG"); + BUG_ON(1); + } + regulator_set_mode(regulator_reg, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_reg) < 0) { + } else { +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + //open vb + regulator_vb = regulator_get(0, "VB"); + if (IS_ERR(regulator_vb)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_vb), "VB"); + BUG_ON(1); + } + regulator_set_mode(regulator_vb, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_vb) < 0) { + } else { +#endif + sprd_codec_hp_classg_en(codec, 1); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + } +#endif + } + //close clk + regulator_set_mode(regulator_reg, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator_reg); + regulator_put(regulator_reg); + regulator_reg = 0; + + //sprd_codec_wait(p_setting->class_g_open_delay_10ms * 10); + /*2. LDO PD */ + regulator = regulator_get(0, CLASS_G_LDO_ID); + if (IS_ERR(regulator)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator), CLASS_G_LDO_ID); + BUG_ON(1); + } + regulator_set_mode(regulator, REGULATOR_MODE_STANDBY); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + if (sprd_codec->hp_ver == SPRD_CODEC_HP_PA_VER_1) + classg_vol = 1200000; + regulator_set_voltage(regulator, classg_vol, classg_vol); +#endif + if (regulator_enable(regulator) < 0) { + regulator_set_mode(regulator, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator); + regulator_put(regulator); + regulator = 0; + } + } + } else{ + if (regulator) { + sp_asoc_pr_dbg("%s set vddclsg off\n", __func__); + /*LDO PD*/ + regulator_set_mode(regulator, REGULATOR_MODE_NORMAL); + regulator_disable(regulator); + regulator_put(regulator); + regulator = 0; + /*CG_EN*/ + //open clk + regulator_reg = regulator_get(0, "VREG"); + if (IS_ERR(regulator_reg)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(regulator_reg), "VREG"); + BUG_ON(1); + } + regulator_set_mode(regulator_reg, REGULATOR_MODE_STANDBY); + if (regulator_enable(regulator_reg) < 0) { + } else { + sprd_codec_hp_classg_en(codec, 0); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + if(regulator_vb) { + //close vb + regulator_set_mode(regulator_vb, REGULATOR_MODE_NORMAL); + regulator_disable(regulator_vb); + regulator_put(regulator_vb); + regulator_vb = 0; + } +#endif + } + //close clk + regulator_set_mode(regulator_reg, + REGULATOR_MODE_NORMAL); + regulator_disable(regulator_reg); + regulator_put(regulator_reg); + regulator_reg = 0; + } + } +} +static int hp_notifier_handler (struct notifier_block *nb, + unsigned long action, void *data) +{ + struct sprd_codec_priv *sprd_codec = container_of (nb, struct sprd_codec_priv, nb); + sprd_inter_headphone_pa_pre (sprd_codec, !!action); + return 0; +} + +static int sprd_inter_headphone_pa(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + /*static struct regulator *regulator = 0;*/ + sp_asoc_pr_info("inter HP PA Switch %s\n", STR_ON_OFF(on)); + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + if (on) { + sprd_codec_hp_pa_lpw(codec, p_setting->class_g_low_power); + + sprd_codec_hp_pa_osc(codec, p_setting->class_g_osc); + /* CG_REF_EN */ + sprd_codec_hp_classg_en(codec, 1); + /* LDOCG_EN */ + sprd_codec_hp_cg_ldo_en(codec, 1); + /* CHP EN */ + sprd_codec_hp_pa_en(codec, 1); + + sprd_codec_ldocg_fal_ild_en(codec, 1); + sprd_codec_ldocg_iset_fal_current_sel(codec, 1); + sprd_codec_ldocg_ldocg_ramp_en(codec, 1); + + /* Head_L_INT pull up power down */ + sprd_codec_head_l_int_pu_pd(codec, 1); + /* DANGL/R EN */ + if (AUDIO_2723_VER_AA == sci_get_ana_chip_ver()) { + sprd_codec_hp_cg_dangl_en(codec, 1); + sprd_codec_hp_cg_dangr_en(codec, 1); + } + /* Set CG as weifeng's suggestion */ + sprd_codec_pga_cg_hpl_1_set(codec, 0x10); + sprd_codec_pga_cg_hpr_1_set(codec, 0x10); + sprd_codec_pga_cg_hpl_2_set(codec, 0x7); + sprd_codec_pga_cg_hpr_2_set(codec, 0x7); + + sprd_codec_wait(p_setting->class_g_hp_switch_delay_10ms * 10); + + } else { + /* CG_REF disable */ + sprd_codec_hp_classg_en(codec, 0); + + /* LDOCG disable */ + sprd_codec_hp_cg_ldo_en(codec, 0); + /* CHP disable */ + sprd_codec_hp_pa_en(codec, 0); + sprd_codec_ldocg_fal_ild_en(codec, 0); + sprd_codec_ldocg_iset_fal_current_sel(codec, 0); + sprd_codec_ldocg_ldocg_ramp_en(codec, 0); + + /* DANGL/R disable */ + if (AUDIO_2723_VER_AA == sci_get_ana_chip_ver()) { + sprd_codec_hp_cg_dangl_en(codec, 0); + sprd_codec_hp_cg_dangr_en(codec, 0); + } + } + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + return 0; +} + +static int sprd_inter_headphone_pa_post(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + if (on) { + /*open classG mute delay time */ + //sprd_codec_wait(p_setting->class_g_unmute_delay_100ms * 100); + /* L/R EN */ + sprd_codec_hp_pa_mode(codec, p_setting->class_g_mode); + sprd_codec_hp_pa_cgcal_en(codec, p_setting->class_g_cgcal); + + sprd_codec_wait(p_setting->class_g_pa_en_delay_10ms * 10); + sprd_codec_hp_pa_hpl_en(codec, 1); + sprd_codec_hp_pa_hpr_en(codec, 1); + sprd_codec_wait(p_setting->class_g_hp_on_delay_20ms * 20); + + /*unmute */ + sprd_codec_hp_pa_hpl_mute(codec, 0); + sprd_codec_hp_pa_hpr_mute(codec, 0); + + sprd_codec->inter_hp_pa.set = 1; + } else { + sprd_codec->inter_hp_pa.set = 0; + /*mute */ + sprd_codec_hp_pa_hpl_mute(codec, 1); + sprd_codec_hp_pa_hpr_mute(codec, 1); + sprd_codec_hp_pa_hpr_en(codec, 0); + sprd_codec_hp_pa_hpl_en(codec, 0); + sprd_codec_wait(10); + sprd_codec_hp_pa_cgcal_en(codec, 0); + sprd_codec_hp_pa_mode(codec, 0); + } + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_USE_INTER_HP_PA_V4 +static int hp_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_headphone_pa(w->codec, on); +} + +static int hp_pa_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + return sprd_inter_headphone_pa_post(w->codec, on); +} +#else +static int hp_pa_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} + +static int hp_pa_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return 0; +} +#endif + +static int sprd_codec_set_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + switch (rate) { + case 8000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_8000 << shift); + break; + case 11025: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_11025 << shift); + break; + case 16000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_16000 << shift); + break; + case 22050: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_22050 << shift); + break; + case 32000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_32000 << shift); + break; + case 44100: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_44100 << shift); + break; + case 48000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_48000 << shift); + break; + case 96000: + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), mask, + SPRD_CODEC_RATE_96000 << shift); + break; + default: + pr_err("ERR:SPRD-CODEC not support this rate %d\n", rate); + break; + } + sp_asoc_pr_dbg("Set Playback rate 0x%x\n", + snd_soc_read(codec, AUD_DAC_CTL)); + return 0; +} + +static int sprd_codec_set_ad_sample_rate(struct snd_soc_codec *codec, int rate, + int mask, int shift) +{ + int set; +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE + if (rate == 44100) + rate = CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE; +#endif + set = rate / 4000; + if (set > 13) { + pr_err("ERR:SPRD-CODEC not support this ad rate %d\n", rate); + } + snd_soc_update_bits(codec, SOC_REG(AUD_ADC_CTL), mask, set << shift); + return 0; +} + +static int sprd_codec_sample_rate_setting(struct sprd_codec_priv *sprd_codec) +{ + sp_asoc_pr_dbg("%s AD %d DA %d AD1 %d\n", __func__, + sprd_codec->ad_sample_val, sprd_codec->da_sample_val, + sprd_codec->ad1_sample_val); + if (sprd_codec->ad_sample_val) { + sprd_codec_set_ad_sample_rate(sprd_codec->codec, + sprd_codec->ad_sample_val, 0x0F, + 0); + } + if (sprd_codec->ad1_sample_val) { + sprd_codec_set_ad_sample_rate(sprd_codec->codec, + sprd_codec->ad1_sample_val, 0xF0, + 4); + } + if (sprd_codec->da_sample_val) { + sprd_codec_set_sample_rate(sprd_codec->codec, + sprd_codec->da_sample_val, 0x0F, 0); + } + return 0; +} + +static inline int sprd_codec_vcom_ldo_auto(struct snd_soc_codec *codec, + int init) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + return sprd_codec_auto_ldo_volt(codec, sprd_codec_vcm_v_sel, + sprd_codec->s_vcom.v_map, + sprd_codec->s_vcom.v_map_size, init); +} + +static inline int sprd_codec_vcom_ldo_cfg(struct sprd_codec_priv *sprd_codec, const struct sprd_codec_ldo_v_map + *v_map, const int v_map_size) +{ + return _sprd_codec_ldo_cfg_set(&sprd_codec->s_vcom, v_map, v_map_size); +} + +static DEFINE_MUTEX(ldo_mutex); +static int sprd_codec_ldo_on(struct sprd_codec_priv *sprd_codec) +{ + struct regulator **regu = &sprd_codec->head_mic; + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + atomic_inc(&sprd_codec->ldo_refcount); + if (atomic_read(&sprd_codec->ldo_refcount) == 1) { + sp_asoc_pr_dbg("LDO ON!\n"); + if (*regu) { + regulator_set_mode(*regu, REGULATOR_MODE_NORMAL); + } + arch_audio_codec_analog_enable(); + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 1); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_ldo_off(struct sprd_codec_priv *sprd_codec) +{ + struct regulator **regu = &sprd_codec->head_mic; + sp_asoc_pr_dbg("%s\n", __func__); + + mutex_lock(&ldo_mutex); + if (atomic_dec_and_test(&sprd_codec->ldo_refcount)) { + if (*regu && regulator_is_enabled(*regu)) { + regulator_set_mode(*regu, REGULATOR_MODE_STANDBY); + } + arch_audio_codec_analog_disable(); + sp_asoc_pr_dbg("LDO OFF!\n"); + } + mutex_unlock(&ldo_mutex); + + return 0; +} + +static int sprd_codec_analog_open(struct snd_soc_codec *codec) +{ + int ret = 0; + uint32_t val = 0; + uint32_t mask = 0; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + sprd_codec_sample_rate_setting(sprd_codec); + + if (is_low_power_support()) { + sp_asoc_pr_info("%s get here, true\n", __func__); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), (ADC_PGA_IBIAS_CTL_MASK << ADC_PGA_IBIAS_CTL_OFFSET), (ADC_PGA_IBIAS_CTL_05 << ADC_PGA_IBIAS_CTL_OFFSET)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), (DAC_FILTER_IBIS_CTL_MASK << DAC_FILTER_IBIS_CTL_OFFSET), (DAC_FILTER_IBIS_CTL_3_PER_7 << DAC_FILTER_IBIS_CTL_OFFSET)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), (RCV_IBIS_CTL_MASK << RCV_IBIS_CTL_OFFSET), (RCV_IBIS_CTL_065 << RCV_IBIS_CTL_OFFSET)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), (HP_CLASSAB_IBIS_CTL_MASK << HP_CLASSAB_IBIS_CTL_OFFSET), (HP_CLASSAB_IBIS_CTL_3_PER_7 << HP_CLASSAB_IBIS_CTL_OFFSET)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), BIT(AUDIO_POP_CHGR_PD), BIT(AUDIO_POP_CHGR_PD)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), BIT(AUDIO_POP_SOFTCHG_EN), BIT(AUDIO_POP_SOFTCHG_EN)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC3), BIT(CG_HPCAL_EN), BIT(CG_HPCAL_EN)); + /* B Setting */ + snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), (ADC_PGAL_BYP_SEL_MASK << ADCPGAL_BYP), (ADC_PGAL_BYP_SEL_PGAL1_2_ADCL << ADCPGAL_BYP)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), (ADC_PGAR_BYP_SEL_MASK << ADCPGAR_BYP), (ADC_PGAR_BYP_SEL_PGAR1_2_ADCR << ADCPGAR_BYP)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC10), (DACL_G_MASK << DACL_G), (DACL_G_MINUS_2_5DB << DACL_G)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC10), (DACR_G_MASK << DACR_G), (DACR_G_MINUS_2_5DB << DACR_G)); + } + /* SC7710/SC8830 ask from ASIC to set initial value */ + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), BIT(SEL_VCMI), + BIT(SEL_VCMI)); + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), BIT(VCMI_FAST_EN), + BIT(VCMI_FAST_EN)); + /*Bug 362021*/ + snd_soc_update_bits(codec,SOC_REG(ANA_CDC2),BIT(DACDC_RMV_EN),BIT(DACDC_RMV_EN)); + snd_soc_update_bits(codec, SOC_REG(ANA_PMU1), BIT(LDOVBO_FAST_EN), + BIT(LDOVBO_FAST_EN)); + snd_soc_write(codec, AUD_SDM_CTL0, 0x500); + return ret; +} + +static int sprd_codec_digital_open(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + /* FIXME : Some Clock SYNC bug will cause MUTE */ + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), BIT(DAC_MUTE_EN), 0); + + sprd_codec_sample_rate_setting(sprd_codec); + + /*Bug 362021*/ + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_SDM_L), 0xFFFF, 0X9999); + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_SDM_H), 0xFF, 0x1); + + return ret; +} + +static void sprd_codec_power_enable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + ret = sprd_codec_ldo_on(sprd_codec); + if (ret != 0) + pr_err("ERR:SPRD-CODEC open ldo error %d\n", ret); + + sprd_codec_analog_open(codec); +} + +static void sprd_codec_power_disable(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s\n", __func__); + + sprd_codec_ldo_off(sprd_codec); +} + +static void sprd_codec_adc_clock_input_en(struct snd_soc_codec *codec, int on) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_dbg("%s, on:%d \n", __func__, on); + + if (on) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC0), BIT(ADC_CLK_EN), BIT(ADC_CLK_EN)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC0), BIT(ADC_CLK_RST), BIT(ADC_CLK_RST)); + snd_soc_update_bits(codec, SOC_REG(ANA_CDC0), BIT(ADC_CLK_RST), 0); + } else { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC0), BIT(ADC_CLK_EN), 0); + } +} + + +static int digital_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: + arch_audio_codec_digital_reg_enable(); + arch_audio_codec_digital_enable(); + arch_audio_codec_digital_reset(); + sprd_codec_digital_open(w->codec); + break; + case SND_SOC_DAPM_POST_PMD: + /*maybe ADC module use it, so we cann't close it */ + /*arch_audio_codec_digital_disable(); */ + arch_audio_codec_digital_reg_disable(); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int analog_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + sprd_codec_power_enable(codec); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_power_disable(codec); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int audio_adc_clock_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + sprd_codec_adc_clock_input_en(codec, 1); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_adc_clock_input_en(codec, 0); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + + +static int chan_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int chan_id = FUN_REG(w->reg); + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + + sp_asoc_pr_info("%s %s\n", sprd_codec_chan_get_name(chan_id), + STR_ON_OFF(on)); + + return 0; +} + +static int dfm_out_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_info("DFM-OUT %s\n", STR_ON_OFF(on)); + sprd_codec->ad_sample_val = sprd_codec->da_sample_val; + sprd_codec_sample_rate_setting(sprd_codec); + + return 0; +} + +static int _mixer_set_mixer(struct snd_soc_codec *codec, int id, int lr, + int try_on, int need_set) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int reg = GET_MIXER_ID(ID_FUN(id, lr)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[reg]); + if (try_on) { + mixer->set = mixer_setting[reg]; + /* NOTES: reduce linein pop noise must open ADCL/R ->HP MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. */ + if (need_set) + return mixer->set(codec, mixer->on); + } else { + mixer_setting[reg] (codec, 0); + mixer->set = 0; + } + return 0; +} + +static inline int _mixer_setting(struct snd_soc_codec *codec, int start, + int end, int lr, int try_on, int need_set) +{ + int id; + for (id = start; id < end; id++) { + _mixer_set_mixer(codec, id, lr, try_on, need_set); + } + return 0; +} + +static inline int _mixer_setting_one(struct snd_soc_codec *codec, int id, + int try_on, int need_set) +{ + int lr = id & 0x1; + id = (id >>1) + SPRD_CODEC_MIXER_START; + return _mixer_setting(codec, id, id + 1, lr, try_on, need_set); +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static void sprd_codec_hp_pop_irq_enable(struct snd_soc_codec *codec) +{ + int val_clr, val_en; + val_clr = BIT(AUDIO_POP_IRQ) << AUD_A_INT_CLR; + val_en = BIT(AUDIO_POP_IRQ) << AUD_A_INT_EN; + snd_soc_update_bits(codec, SOC_REG(DIG_CFG3), val_clr, val_clr); + snd_soc_update_bits(codec, SOC_REG(DIG_CFG4), val_en, val_en); +} +#endif + +static irqreturn_t sprd_codec_ap_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = (snd_soc_read(codec, DIG_STS1) >> AUD_IRQ_MSK); + sp_asoc_pr_dbg("HP POP IRQ Mask = 0x%x\n", mask); + if (BIT(AUDIO_POP_IRQ) & mask) { + snd_soc_update_bits(codec, SOC_REG(DIG_CFG4), BIT(AUDIO_POP_IRQ) << AUD_A_INT_EN, 0); + complete(&sprd_codec->completion_hp_pop); + } + if (BIT(OVP_IRQ) & mask) { + snd_soc_update_bits(codec, SOC_REG(DIG_CFG4), BIT(OVP_IRQ) << AUD_A_INT_EN, 0); + sprd_codec_ovp_start(sprd_codec, 1); + sprd_codec_ovp_irq_enable(codec); + } + return IRQ_HANDLED; +} + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP +static inline int is_hp_pop_compelet(struct snd_soc_codec *codec) +{ + int val; + val = snd_soc_read(codec, ANA_STS0); + val = (val >> HP_POP_FLG) & HP_POP_FLG_MASK; + sp_asoc_pr_dbg("HP POP= 0x%x\n", val); + return HP_POP_FLG_NEAR_CMP == val; +} + +static inline int hp_pop_wait_for_compelet(struct snd_soc_codec *codec) +{ + int i; + int hp_pop_complete; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + hp_pop_complete = msecs_to_jiffies(SPRD_CODEC_HP_POP_TIMEOUT); + for (i = 0; i < 2; i++) { + sp_asoc_pr_dbg("HP POP %d IRQ Enable\n", i); + sprd_codec_hp_pop_irq_enable(codec); + init_completion(&sprd_codec->completion_hp_pop); + hp_pop_complete = + wait_for_completion_timeout(&sprd_codec->completion_hp_pop, + hp_pop_complete); + sp_asoc_pr_dbg("HP POP %d Completion %d\n", i, hp_pop_complete); + if (!hp_pop_complete) { + if (!is_hp_pop_compelet(codec)) { + pr_err("ERR:HP POP %d timeout not complete\n", + i); + } else { + pr_err("ERR:HP POP %d timeout but complete\n", + i); + } + } else { + /* 01 change to 10 maybe walk to 11 shortly, + so, check it double. */ + sprd_codec_wait(2); + if (is_hp_pop_compelet(codec)) { + return 0; + } + } + } + return 0; +} + +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int mask; + 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: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_STEP_2 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_UP << HP_POP_CTL); + sp_asoc_pr_dbg("U PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); + break; + case SND_SOC_DAPM_PRE_PMD: + mask = HP_POP_STEP_MASK << HP_POP_STEP; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_STEP_1 << HP_POP_STEP); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_DOWN << HP_POP_CTL); + sp_asoc_pr_dbg("D PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); + break; + case SND_SOC_DAPM_POST_PMU: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); + break; + case SND_SOC_DAPM_POST_PMD: + ret = hp_pop_wait_for_compelet(codec); + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#else +#ifndef CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME +#define CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME (0) +#endif +static int hp_pop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; + sp_asoc_pr_dbg("%s Event is %s wait %dms\n", __func__, + get_event_name(event), + CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_wait(CONFIG_SND_SOC_SPRD_HP_POP_DELAY_TIME); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} +#endif + +static int hp_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + int mask; +#endif + 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: +#if 1 /* enable the diff function in shark from weifeng.ni */ + snd_soc_update_bits(codec, SOC_REG(ANA_CDC2), BIT(DIFF_EN), + BIT(DIFF_EN)); + /* FIXME: change by jian.chen */ +/* + + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(HP_VCMI_EN), + BIT(HP_VCMI_EN)); +*/ +#endif + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_DIS << HP_POP_CTL); + sp_asoc_pr_dbg("DIS(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); +#endif + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, SOC_REG(ANA_CDC2), BIT(DIFF_EN), 0); + /* FIXME: change by jian.chen */ + /* + snd_soc_update_bits(codec, SOC_REG(DCR2_DCR1), BIT(HP_VCMI_EN), 0); + */ + +#ifndef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + mask = HP_POP_CTL_MASK << HP_POP_CTL; + snd_soc_update_bits(codec, SOC_REG(ANA_CDC16), mask, + HP_POP_CTL_HOLD << HP_POP_CTL); + sp_asoc_pr_dbg("HOLD(en) PNRCR1 = 0x%x\n", + snd_soc_read(codec, ANA_CDC16)); +#endif + goto _pre_pmd; + break; + case SND_SOC_DAPM_POST_PMD: + /* do nothing now */ + break; + default: + BUG(); + ret = -EINVAL; + } + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, ANA_CDC2) & BIT(HPL_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_DACL, + SPRD_CODEC_HP_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, ANA_CDC2) & BIT(HPR_EN), 1); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_LEFT, + snd_soc_read(codec, ANA_CDC2) & BIT(HPL_EN), 0); + + _mixer_setting(codec, SPRD_CODEC_HP_ADCL, + SPRD_CODEC_HP_MIXER_MAX, SPRD_CODEC_RIGHT, + snd_soc_read(codec, ANA_CDC2) & BIT(HPR_EN), 0); + +_pre_pmd: + + return ret; +} + +static int spk_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + if (snd_soc_read(codec, ANA_CDC2) & BIT(AOL_EN)) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + sprd_codec_pa_sw_set(codec, SPRD_CODEC_PA_SW_AOL); + break; + case SND_SOC_DAPM_PRE_PMD: + sprd_codec_pa_sw_clr(codec, SPRD_CODEC_PA_SW_AOL); + return 0; + default: + break; + } + } + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, ANA_CDC2) & BIT(AOL_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_DACL, + SPRD_CODEC_SPK_DAC_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, ANA_CDC2) & BIT(AOR_EN)), 1); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_LEFT, + (snd_soc_read(codec, ANA_CDC2) & BIT(AOL_EN)), 0); + + _mixer_setting(codec, SPRD_CODEC_SPK_ADCL, + SPRD_CODEC_SPK_MIXER_MAX, SPRD_CODEC_RIGHT, + (snd_soc_read(codec, ANA_CDC2) & BIT(AOR_EN)), 0); + + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK +static int ear_switch_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: + sprd_codec_pa_sw_set(w->codec, SPRD_CODEC_PA_SW_EAR); + break; + case SND_SOC_DAPM_POST_PMD: + sprd_codec_pa_sw_clr(w->codec, SPRD_CODEC_PA_SW_EAR); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} +#endif + +static int adcpgal_set(struct snd_soc_codec *codec, int on) +{ + if (is_low_power_support()) { + int mask = ADCPGAL_EN_MASK << ADCPGAL_EN; + int value = 0x2 << ADCPGAL_EN; + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), mask, + on ? value : 0); + } else { + int mask = ADCPGAL_EN_MASK << ADCPGAL_EN; + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), mask, + on ? mask : 0); + } +} + +static int adcpgar_set(struct snd_soc_codec *codec, int on) +{ + if (is_low_power_support()) { + int mask = ADCPGAR_EN_MASK << ADCPGAR_EN; + int value = 0x2 << ADCPGAR_EN; + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), mask, + on ? value : 0); + } else { + int mask = ADCPGAR_EN_MASK << ADCPGAR_EN; + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), mask, + on ? mask : 0); + } +} + +static int adc_switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int is_right = (w->shift == SPRD_CODEC_RIGHT); + int on = (! !SND_SOC_DAPM_EVENT_ON(event)); + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + if (is_right) { + adcpgar_set(codec, on); + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_RIGHT, on, 1); + } else { + adcpgal_set(codec, on); + _mixer_setting(codec, SPRD_CODEC_AIL, SPRD_CODEC_ADC_MIXER_MAX, + SPRD_CODEC_LEFT, on, 1); + } + + return 0; +} + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_PGA_ID(FUN_REG(w->reg)); + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + int ret = 0; + int min = sprd_codec_pga_cfg[id].min; + + sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__, + sprd_codec_pga_debug_str[id], pga->pgaval, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + pga->set = sprd_codec_pga_cfg[id].set; + ret = pga->set(codec, pga->pgaval); + break; + case SND_SOC_DAPM_PRE_PMD: + pga->set = 0; + if(min >= 0) + ret = sprd_codec_pga_cfg[id].set(codec, min); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} + +static int switch_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_SWITCH_ID(FUN_REG(w->reg)); + int ret = 0; + int min = 0; + struct sprd_codec_switch_op *switcher = &(sprd_codec->switcher[id]); + sp_asoc_pr_info("%s Switch %s(%s) Event is %s\n", __func__, + switch_name[id], switcher->on? "on":"off", + get_event_name(event)); + min = sprd_codec_switch_cfg[id].min; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switcher->set = sprd_codec_switch_cfg[id].set; + ret = switcher->set(codec, switcher->on); + break; + case SND_SOC_DAPM_PRE_PMD: + switcher->set = 0; + if(min >= 0) + ret = sprd_codec_switch_cfg[id].set(codec, min); + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} + +static int adcpgar_byp_set(struct snd_soc_codec *codec, int value) +{ + int mask = ADCPGAR_BYP_MASK << ADCPGAR_BYP; + return snd_soc_update_bits(codec, SOC_REG(ANA_CDC1), mask, + value << ADCPGAR_BYP); +} + +static int adcpgar_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + 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: + adcpgar_byp_set(codec, 2); + sprd_codec_wait(100); + if (is_low_power_support()) + adcpgar_byp_set(codec, 1); + else + adcpgar_byp_set(codec, 0); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + default: + BUG(); + ret = -EINVAL; + } + return ret; +} + +static int ana_loop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(w->reg)); + struct sprd_codec_mixer *mixer; + int ret = 0; + static int s_need_wait = 1; + + sp_asoc_pr_dbg("Entering %s event is %s\n", __func__, + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* NOTES: reduce linein pop noise must open ADCL/R ->HP/SPK MIXER + AFTER delay 250ms for both ADCL/ADCR switch complete. + */ + if (s_need_wait == 1) { + /* NOTES: reduce linein pop noise must delay 250ms + after linein mixer switch on. + actually this function perform after + adc_switch_event function for + both ADCL/ADCR switch complete. + */ + sprd_codec_wait(250); + s_need_wait++; + sp_asoc_pr_dbg("ADC Switch ON delay\n"); + } + mixer = &(sprd_codec->mixer[id]); + if (mixer->set) { + mixer->set(codec, mixer->on); + } + break; + case SND_SOC_DAPM_PRE_PMD: + case SND_SOC_DAPM_POST_PMD: + s_need_wait = 1; + mixer = &(sprd_codec->mixer[id]); + if (mixer->set) { + mixer->set(codec, mixer->on); + } + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int reg = FUN_REG(w->reg); + int ret = 0; + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + struct regulator **regu; + + sp_asoc_pr_dbg("%s %s Event is %s\n", __func__, + mic_bias_name[GET_MIC_BIAS_ID(reg)], + get_event_name(event)); + + switch (reg) { + case SPRD_CODEC_MIC_BIAS: + regu = &sprd_codec->main_mic; + break; + case SPRD_CODEC_AUXMIC_BIAS: + regu = &sprd_codec->aux_mic; + break; + case SPRD_CODEC_HEADMIC_BIAS: + regu = &sprd_codec->head_mic; + break; + default: + BUG(); + ret = -EINVAL; + } + + ret = sprd_codec_regulator_set(regu, on); + + return ret; +} + +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(w->reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + sp_asoc_pr_dbg("%s event is %s\n", sprd_codec_mixer_debug_str[id], + get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mixer->on = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + mixer->on = 0; + break; + default: + BUG(); + ret = -EINVAL; + } + if (ret >= 0) + _mixer_setting_one(codec, id, mixer->on, 1); + + return ret; +} + +static int mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(mc->reg)); + ucontrol->value.integer.value[0] = sprd_codec->mixer[id].on; + return 0; +} + +static int mixer_need_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, bool need_set) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(mc->reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + int ret = 0; + + if (mixer->on == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("Set %s Switch %s\n", sprd_codec_mixer_debug_str[id], + STR_ON_OFF(ucontrol->value.integer.value[0])); + + /*notice the sequence */ + snd_soc_dapm_put_volsw(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); */ + + if (mixer->set && need_set) + ret = mixer->set(codec, mixer->on); + + return ret; +} + +static int mixer_set_mem(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 0); +} + +static int mixer_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return mixer_need_set(kcontrol, ucontrol, 1); +} + +static int adie_loop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(w->reg)); + int ret = 0; + + sp_asoc_pr_info("%s, %s, Event is %s\n", + __func__, adc_loop_name[id], get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* enable loop clock */ + ret = arch_audio_codec_adie_loop_clk_en(1); + snd_soc_update_bits(codec, SOC_REG(DIG_CFG0), BIT(AUDIO_ADIE_LOOP_EN), 1 << AUDIO_ADIE_LOOP_EN); + break; + case SND_SOC_DAPM_POST_PMD: + /* disable loop clock */ + ret = arch_audio_codec_adie_loop_clk_en(0); + snd_soc_update_bits(codec, SOC_REG(DIG_CFG0), BIT(AUDIO_ADIE_LOOP_EN), 0 << AUDIO_ADIE_LOOP_EN); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + return ret; +} + +static int digital_loop_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(w->reg)); + int ret = 0; + + sp_asoc_pr_info("%s, %s, Event is %s\n", + __func__, adc_loop_name[id], get_event_name(event)); + + if (SPRD_CODEC_ADC_DAC_DIGITAL_LOOP == FUN_REG(w->reg)) + sprd_codec_set_ad_sample_rate(codec, 48000, 0x0F, 0); + else if (SPRD_CODEC_ADC1_DAC_DIGITAL_LOOP == FUN_REG(w->reg)) + sprd_codec_set_ad_sample_rate(codec, 48000, 0xF0, 4); + else { + sp_asoc_pr_info("%s, wrong id is %d\n", __func__, id); + return -EINVAL; + } + sprd_codec_set_sample_rate(codec, 48000, 0x0F, 0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, SOC_REG(AUD_LOOP_CTL), BIT(LOOP_ADC_PATH_SEL), + ((id-2) << LOOP_ADC_PATH_SEL)); + snd_soc_update_bits(codec, SOC_REG(AUD_LOOP_CTL), BIT(AUD_LOOP_TEST), 1 << AUD_LOOP_TEST); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, SOC_REG(AUD_LOOP_CTL), BIT(AUD_LOOP_TEST), 0 << AUD_LOOP_TEST); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + return ret; +} + +static int sprd_codec_adie_loop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(mc->reg)); + int ret = 0; + + ret = !!(ucontrol->value.integer.value[0]); + if (ret == sprd_codec->ad_da_loop_en[id]) { + return ret; + } + sp_asoc_pr_info("%s, %s, set %d \n", + __func__, adc_loop_name[id], (int)ucontrol->value.integer.value[0]); + /*notice the sequence */ + snd_soc_dapm_put_volsw(kcontrol, ucontrol); + sprd_codec->ad_da_loop_en[id] = ret; + return ret; +} + +static int sprd_codec_adie_loop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(mc->reg)); + + ucontrol->value.integer.value[0] = !!sprd_codec->ad_da_loop_en[id]; + return 0; +} + +static int sprd_codec_digital_loop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(mc->reg)); + int ret = 0; + + ret = !!(ucontrol->value.integer.value[0]); + if (ret == sprd_codec->ad_da_loop_en[id]) { + return ret; + } + sp_asoc_pr_info("%s, %s, set %d \n", + __func__, adc_loop_name[id], (int)ucontrol->value.integer.value[0]); + + /*notice the sequence */ + snd_soc_dapm_put_volsw(kcontrol, ucontrol); + sprd_codec->ad_da_loop_en[id] = ret; + return ret; +} + +static int sprd_codec_digital_loop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = wlist->widgets[0]->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_ADC_LOOP_ID(FUN_REG(mc->reg)); + + ucontrol->value.integer.value[0] = !!sprd_codec->ad_da_loop_en[id]; + return 0; +} +#define SPRD_CODEC_MIXER(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set) + +/*Just for LINE IN path, mixer_set not really set mixer (ADCL/R -> HP/SPK L/R) here but +setting in ana_loop_event, just remeber state here*/ +#define SPRD_CODEC_MIXER_NOSET(xname, xreg)\ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, mixer_get, mixer_set_mem) + +/* ADCL Mixer */ +static const struct snd_kcontrol_new adcl_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCL Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AIRADCL Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("MainMICADCL Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("AuxMICADCL Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("HPMICADCL Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_LEFT)), +}; + +/* ADCR Mixer */ +static const struct snd_kcontrol_new adcr_mixer_controls[] = { + SPRD_CODEC_MIXER("AILADCR Switch", + ID_FUN(SPRD_CODEC_AIL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AIRADCR Switch", + ID_FUN(SPRD_CODEC_AIR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("MainMICADCR Switch", + ID_FUN(SPRD_CODEC_MAIN_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("AuxMICADCR Switch", + ID_FUN(SPRD_CODEC_AUX_MIC, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("HPMICADCR Switch", + ID_FUN(SPRD_CODEC_HP_MIC, SPRD_CODEC_RIGHT)), +}; + +/* HPL Mixer */ +static const struct snd_kcontrol_new hpl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRHPL Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPL Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), +}; + +/* HPR Mixer */ +static const struct snd_kcontrol_new hpr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRHPR Switch", + ID_FUN(SPRD_CODEC_HP_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRHPR Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), +}; + +/* SPKL Mixer */ +static const struct snd_kcontrol_new spkl_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER("DACRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKL Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), +}; + +/* SPKR Mixer */ +static const struct snd_kcontrol_new spkr_mixer_controls[] = { + SPRD_CODEC_MIXER("DACLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER("DACRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_DACR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCLSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_MIXER_NOSET("ADCRSPKR Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), +}; + +static const struct snd_kcontrol_new sprd_codec_loop_controls[] = { + SOC_SINGLE_EXT("switch", FUN_REG(SPRD_CODEC_ADC_DAC_ADIE_LOOP), 0, 1, 0, + sprd_codec_adie_loop_get, sprd_codec_adie_loop_put), + SOC_SINGLE_EXT("switch", FUN_REG(SPRD_CODEC_ADC1_DAC_ADIE_LOOP), 0, 1, 0, + sprd_codec_adie_loop_get, sprd_codec_adie_loop_put), + SOC_SINGLE_EXT("switch", FUN_REG(SPRD_CODEC_ADC_DAC_DIGITAL_LOOP), 0, 1, 0, + sprd_codec_digital_loop_get, sprd_codec_digital_loop_put), + SOC_SINGLE_EXT("switch", FUN_REG(SPRD_CODEC_ADC1_DAC_DIGITAL_LOOP), 0, 1, 0, + sprd_codec_digital_loop_get, sprd_codec_digital_loop_put), +}; +/*ANA LOOP SWITCH*/ +#define SPRD_CODEC_LOOP_SWITCH(xname, xreg)\ + SND_SOC_DAPM_PGA_S(xname, SPRD_CODEC_ANA_MIXER_ORDER, FUN_REG(xreg), 0, 0, ana_loop_event,\ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#ifndef SND_SOC_DAPM_MICBIAS_E +#define SND_SOC_DAPM_MICBIAS_E(wname, wreg, wshift, winvert, wevent, wflags) \ +{ .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \ + .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags} +#endif + +static const struct snd_soc_dapm_widget sprd_codec_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("VBO", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VCOM_BUF", 0, 0), + SND_SOC_DAPM_SUPPLY_S("Digital Power", 1, SND_SOC_NOPM, 0, 0, + digital_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("Analog Power", 2, SND_SOC_NOPM, 0, 0, + analog_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("DA Clk", 3, SOC_REG(ANA_CDC0), DAC_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DRV Clk", 4, SOC_REG(ANA_CDC0), DRV_CLK_EN, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("AD IBUF", 3, SOC_REG(ANA_CDC1), ADC_IBUF_PD, + 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD Clk", 3, SND_SOC_NOPM, 0, 0, + audio_adc_clock_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("Digital DACL Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital DACR Switch", 4, SOC_REG(AUD_TOP_CTL), + DAC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital DACL Switch", 5, SOC_REG(DIG_CFG0), + AUDIFA_DACL_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital DACR Switch", 5, SOC_REG(DIG_CFG0), + AUDIFA_DACR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("DACL Switch", 6, FUN_REG(SPRD_CODEC_DACL), 0, 0, + switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("DACR Switch", 6, FUN_REG(SPRD_CODEC_DACR), 0, 0, + switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("DACL Mute", 6, FUN_REG(SPRD_CODEC_PGA_DACL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("DACR Mute", 6, FUN_REG(SPRD_CODEC_PGA_DACR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("DAC", "Playback-Vaudio", + FUN_REG(SPRD_CODEC_PLAYBACK), 0, + 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DFM-OUT", "DFM-Output", + SND_SOC_NOPM, 0, + 0, + dfm_out_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + SND_SOC_DAPM_PGA_S("HP POP", SPRD_CODEC_HP_POP_ORDER, SND_SOC_NOPM, 0, + 0, hp_pop_event, + SND_SOC_DAPM_POST_PMU), +#else + SND_SOC_DAPM_SUPPLY_S("HP POP", 5, SND_SOC_NOPM, 0, 0, hp_pop_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +#endif + SND_SOC_DAPM_PGA_S("HPL Switch", SPRD_CODEC_HP_SWITCH_ORDER, + SOC_REG(ANA_CDC2), HPL_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Switch", SPRD_CODEC_HP_SWITCH_ORDER, + SOC_REG(ANA_CDC2), HPR_EN, 0, + hp_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPL Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR Mute", 6, FUN_REG(SPRD_CODEC_PGA_HPR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER("HPL Mixer", SND_SOC_NOPM, 0, 0, + &hpl_mixer_controls[0], + ARRAY_SIZE(hpl_mixer_controls)), + SND_SOC_DAPM_MIXER("HPR Mixer", SND_SOC_NOPM, 0, 0, + &hpr_mixer_controls[0], + ARRAY_SIZE(hpr_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKL Mixer", SND_SOC_NOPM, 0, 0, + &spkl_mixer_controls[0], + ARRAY_SIZE(spkl_mixer_controls)), + SND_SOC_DAPM_MIXER("SPKR Mixer", SND_SOC_NOPM, 0, 0, + &spkr_mixer_controls[0], + ARRAY_SIZE(spkr_mixer_controls)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPL Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRHPR Loop Switch", + ID_FUN(SPRD_CODEC_HP_ADCR, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKL Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_LEFT)), + SPRD_CODEC_LOOP_SWITCH("ADCLSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCL, SPRD_CODEC_RIGHT)), + SPRD_CODEC_LOOP_SWITCH("ADCRSPKR Loop Switch", + ID_FUN(SPRD_CODEC_SPK_ADCR, SPRD_CODEC_RIGHT)), + SND_SOC_DAPM_PGA_S("SPKL Switch", 5, SOC_REG(ANA_CDC2), AOL_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Switch", 5, SOC_REG(ANA_CDC2), AOR_EN, 0, + spk_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("SPKL Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("SPKR Mute", 6, FUN_REG(SPRD_CODEC_PGA_SPKR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("EAR Mixer", 5, + FUN_REG(ID_FUN + (SPRD_CODEC_EAR_DACL, SPRD_CODEC_LEFT)), 0, + 0, mixer_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +#ifdef CONFIG_SND_SOC_SPRD_CODEC_EAR_WITH_IN_SPK + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(ANA_CDC2), EAR_EN, 0, + ear_switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +#else + SND_SOC_DAPM_PGA_S("EAR Switch", 6, SOC_REG(ANA_CDC2), EAR_EN, 0, NULL, + 0), +#endif + + SND_SOC_DAPM_PGA_S("EAR Mute", 7, FUN_REG(SPRD_CODEC_PGA_EAR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MIXER("ADCL Mixer", SND_SOC_NOPM, 0, 0, + &adcl_mixer_controls[0], + ARRAY_SIZE(adcl_mixer_controls)), + SND_SOC_DAPM_MIXER("ADCR Mixer", SND_SOC_NOPM, 0, 0, + &adcr_mixer_controls[0], + ARRAY_SIZE(adcr_mixer_controls)), + SND_SOC_DAPM_PGA_S("Digital ADCL Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADCR Switch", 3, SOC_REG(AUD_TOP_CTL), + ADC_EN_R, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital ADCL Switch", 2, SOC_REG(DIG_CFG0), + AUDIFA_ADCL_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADie Digital ADCR Switch", 2, SOC_REG(DIG_CFG0), + AUDIFA_ADCR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADCL Switch", 1, SOC_REG(ANA_CDC1), ADCL_PD, 1, + NULL, + 0), + SND_SOC_DAPM_PGA_S("ADCR Switch", 1, SOC_REG(ANA_CDC1), ADCR_PD, 1, + adcpgar_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("ADCL PGA", SND_SOC_NOPM, SPRD_CODEC_LEFT, 0, NULL, + 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("ADCR PGA", SND_SOC_NOPM, SPRD_CODEC_RIGHT, 0, NULL, + 0, + adc_switch_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADCL Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("ADCR Mute", 3, FUN_REG(SPRD_CODEC_PGA_ADCR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC", "Main-Capture-Vaudio", + FUN_REG(SPRD_CODEC_CAPTRUE), + 0, 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("MIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_MIC), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AUXMIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_AUXMIC), 0, + 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HEADMIC Boost", 3, FUN_REG(SPRD_CODEC_PGA_HEADMIC), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AIL Boost", 3, FUN_REG(SPRD_CODEC_PGA_AIL), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("AIR Boost", 3, FUN_REG(SPRD_CODEC_PGA_AIR), 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MICBIAS_E("Mic Bias", FUN_REG(SPRD_CODEC_MIC_BIAS), 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("AuxMic Bias", FUN_REG(SPRD_CODEC_AUXMIC_BIAS), + 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MICBIAS_E("HeadMic Bias", FUN_REG(SPRD_CODEC_HEADMIC_BIAS), + 0, 0, + mic_bias_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("Digital ADC1L Switch", 5, SOC_REG(AUD_TOP_CTL), + ADC1_EN_L, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADC1R Switch", 5, SOC_REG(AUD_TOP_CTL), + ADC1_EN_R, 0, NULL, 0), + SND_SOC_DAPM_ADC_E("ADC1", "Ext-Capture-Vaudio", + FUN_REG(SPRD_CODEC_CAPTRUE1), + 0, 0, + chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /*add DMIC */ + SND_SOC_DAPM_PGA_S("Digital ADC DMIC In", 4, SOC_REG(AUD_TOP_CTL), + ADC_DMIC_SEL, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Digital ADC1 DMIC In", 4, SOC_REG(AUD_TOP_CTL), + ADC1_DMIC1_SEL, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("DMIC Switch", 3, SOC_REG(AUD_DMIC_CTL), + ADC_DMIC_EN, 0, + NULL, 0), + SND_SOC_DAPM_PGA_S("DMIC1 Switch", 3, SOC_REG(AUD_DMIC_CTL), + ADC1_DMIC1_EN, 0, + NULL, 0), + + SND_SOC_DAPM_PGA_S("HP PA Switch", SPRD_CODEC_HP_PA_ORDER, SND_SOC_NOPM, + 0, 0, hp_pa_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("HP PA POST Switch", SPRD_CODEC_HP_PA_POST_ORDER, + SND_SOC_NOPM, 0, 0, hp_pa_post_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Spk PA Switch", SPRD_CODEC_PA_ORDER, SND_SOC_NOPM, + 0, 0, + spk_pa_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("HPL CGL Switch", SPRD_CODEC_HP_PA_POST_ORDER, FUN_REG(SPRD_CODEC_HPLCGL), + HPL_CGL, 0, switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR CGR Switch", SPRD_CODEC_HP_PA_POST_ORDER, FUN_REG(SPRD_CODEC_HPRCGR), + HPR_CGR, 0, switch_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPL CG Mute1", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_PGA_CG_HPL_1), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR CG Mute1", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_PGA_CG_HPR_1), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPL CG Mute2", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_PGA_CG_HPL_2), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("HPR CG Mute2", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_PGA_CG_HPR_2), + 0, 0, + pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SWITCH("ADC-DAC Adie Loop", FUN_REG(SPRD_CODEC_ADC_DAC_ADIE_LOOP), 0, 0, &sprd_codec_loop_controls[0]), + SND_SOC_DAPM_SWITCH("ADC1-DAC Adie Loop", FUN_REG(SPRD_CODEC_ADC1_DAC_ADIE_LOOP), 0, 0, &sprd_codec_loop_controls[1]), + SND_SOC_DAPM_SWITCH("ADC-DAC Digital Loop", FUN_REG(SPRD_CODEC_ADC_DAC_DIGITAL_LOOP), 0, 0, &sprd_codec_loop_controls[2]), + SND_SOC_DAPM_SWITCH("ADC1-DAC Digital Loop", FUN_REG(SPRD_CODEC_ADC1_DAC_DIGITAL_LOOP), 0, 0, &sprd_codec_loop_controls[3]), + + SND_SOC_DAPM_PGA_S("ADC-DAC Adie Loop post", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_ADC_DAC_ADIE_LOOP), + 0, 0, + adie_loop_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADC1-DAC Adie Loop post", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_ADC1_DAC_ADIE_LOOP), + 0, 0, + adie_loop_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADC-DAC Digital Loop post", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_ADC_DAC_DIGITAL_LOOP), + 0, 0, + digital_loop_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADC1-DAC Digital Loop post", SPRD_CODEC_CG_PGA_ORDER, FUN_REG(SPRD_CODEC_ADC1_DAC_DIGITAL_LOOP), + 0, 0, + digital_loop_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("DMIC"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_OUTPUT("HEAD_P_L"), + SND_SOC_DAPM_OUTPUT("HEAD_P_R"), + SND_SOC_DAPM_OUTPUT("AOL"), + SND_SOC_DAPM_OUTPUT("AOR"), + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_OUTPUT("HP PA"), + SND_SOC_DAPM_OUTPUT("Spk PA"), + SND_SOC_DAPM_OUTPUT("Spk2 PA"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("AUXMIC"), + SND_SOC_DAPM_INPUT("HPMIC"), + SND_SOC_DAPM_INPUT("AIL"), + SND_SOC_DAPM_INPUT("AIR"), +}; + +/* sprd_codec supported interconnection*/ +static const struct snd_soc_dapm_route sprd_codec_intercon[] = { + /* Power */ + {"Analog Power", NULL, "VCOM_BUF"}, + {"Analog Power", NULL, "VBO"}, + {"DA Clk", NULL, "Analog Power"}, + {"DA Clk", NULL, "Digital Power"}, + {"DAC", NULL, "DA Clk"}, + {"DFM-OUT", NULL, "DA Clk"}, + + {"AD IBUF", NULL, "Analog Power"}, + {"AD Clk", NULL, "Digital Power"}, + {"AD Clk", NULL, "AD IBUF"}, + {"ADC", NULL, "AD Clk"}, + + {"ADC1", NULL, "AD Clk"}, + + {"ADCL PGA", NULL, "AD IBUF"}, + {"ADCR PGA", NULL, "AD IBUF"}, + + {"HP POP", NULL, "DRV Clk"}, + {"HPL Switch", NULL, "DRV Clk"}, + {"HPR Switch", NULL, "DRV Clk"}, + {"SPKL Switch", NULL, "DRV Clk"}, + {"SPKR Switch", NULL, "DRV Clk"}, + {"EAR Switch", NULL, "DRV Clk"}, + + /* Playback */ + {"Digital DACL Switch", NULL, "DFM-OUT"}, + {"Digital DACR Switch", NULL, "DFM-OUT"}, + {"Digital DACL Switch", NULL, "DAC"}, + {"Digital DACR Switch", NULL, "DAC"}, + {"ADie Digital DACL Switch", NULL, "Digital DACL Switch"}, + {"ADie Digital DACR Switch", NULL, "Digital DACR Switch"}, + {"DACL Mute", NULL, "ADie Digital DACL Switch"}, + {"DACR Mute", NULL, "ADie Digital DACR Switch"}, + {"DACL Switch", NULL, "DACL Mute"}, + {"DACR Switch", NULL, "DACR Mute"}, + + /* Output */ + {"HPL Mixer", "DACLHPL Switch", "DACL Switch"}, + {"HPL Mixer", "DACRHPL Switch", "DACR Switch"}, + {"HPR Mixer", "DACLHPR Switch", "DACL Switch"}, + {"HPR Mixer", "DACRHPR Switch", "DACR Switch"}, + + {"ADCLHPL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLHPR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRHPL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRHPR Loop Switch", NULL, "ADCR PGA"}, + + {"HPL Mixer", "ADCLHPL Switch", "ADCLHPL Loop Switch"}, + {"HPL Mixer", "ADCRHPL Switch", "ADCRHPL Loop Switch"}, + {"HPR Mixer", "ADCLHPR Switch", "ADCLHPR Loop Switch"}, + {"HPR Mixer", "ADCRHPR Switch", "ADCRHPR Loop Switch"}, + +#ifdef CONFIG_SND_SOC_SPRD_CODEC_NO_HP_POP + {"HEAD_P_L", NULL, "HP POP"}, + {"HEAD_P_R", NULL, "HP POP"}, + {"HP POP", NULL, "HPL Switch"}, + {"HP POP", NULL, "HPR Switch"}, +#else + {"HPL Switch", NULL, "HP POP"}, + {"HPR Switch", NULL, "HP POP"}, +#endif + {"HPL Switch", NULL, "HPL Mixer"}, + {"HPR Switch", NULL, "HPR Mixer"}, + {"HPL Mute", NULL, "HPL Switch"}, + {"HPR Mute", NULL, "HPR Switch"}, + {"HEAD_P_L", NULL, "HPL Mute"}, + {"HEAD_P_R", NULL, "HPR Mute"}, + + {"HP PA", NULL, "HP PA POST Switch"}, + {"HP PA POST Switch", NULL, "HP PA Switch"}, + {"HPL CGL Switch", NULL, "HPL Mute"}, + {"HPR CGR Switch", NULL, "HPR Mute"}, + {"HPL CG Mute1", NULL, "HPL CGL Switch"}, + {"HPR CG Mute1", NULL, "HPR CGR Switch"}, + {"HPL CG Mute2", NULL, "HPL CG Mute1"}, + {"HPR CG Mute2", NULL, "HPR CG Mute1"}, + {"HP PA Switch", NULL, "HPL CG Mute2"}, + {"HP PA Switch", NULL, "HPR CG Mute2"}, + + {"SPKL Mixer", "DACLSPKL Switch", "DACL Switch"}, + {"SPKL Mixer", "DACRSPKL Switch", "DACR Switch"}, + {"SPKR Mixer", "DACLSPKR Switch", "DACL Switch"}, + {"SPKR Mixer", "DACRSPKR Switch", "DACR Switch"}, + + {"ADCLSPKL Loop Switch", NULL, "ADCL PGA"}, + {"ADCLSPKR Loop Switch", NULL, "ADCL PGA"}, + {"ADCRSPKL Loop Switch", NULL, "ADCR PGA"}, + {"ADCRSPKR Loop Switch", NULL, "ADCR PGA"}, + + {"SPKL Mixer", "ADCLSPKL Switch", "ADCLSPKL Loop Switch"}, + {"SPKL Mixer", "ADCRSPKL Switch", "ADCRSPKL Loop Switch"}, + {"SPKR Mixer", "ADCLSPKR Switch", "ADCLSPKR Loop Switch"}, + {"SPKR Mixer", "ADCRSPKR Switch", "ADCRSPKR Loop Switch"}, + + {"SPKL Switch", NULL, "SPKL Mixer"}, + {"SPKR Switch", NULL, "SPKR Mixer"}, + {"SPKL Mute", NULL, "SPKL Switch"}, + {"SPKR Mute", NULL, "SPKR Switch"}, + {"AOL", NULL, "SPKL Mute"}, + {"AOR", NULL, "SPKR Mute"}, + + {"Spk PA", NULL, "Spk PA Switch"}, + {"Spk PA Switch", NULL, "SPKL Mute"}, + + {"EAR Mixer", NULL, "DACL Switch"}, + {"EAR Switch", NULL, "EAR Mixer"}, + {"EAR Mute", NULL, "EAR Switch"}, + {"EAR", NULL, "EAR Mute"}, + + {"ADCL Mute", NULL, "ADCL Mixer"}, + {"ADCR Mute", NULL, "ADCR Mixer"}, + {"ADCL PGA", NULL, "ADCL Mute"}, + {"ADCR PGA", NULL, "ADCR Mute"}, + /* LineIN */ + {"ADCL Mixer", "AILADCL Switch", "AIL Boost"}, + {"ADCR Mixer", "AILADCR Switch", "AIL Boost"}, + {"ADCL Mixer", "AIRADCL Switch", "AIR Boost"}, + {"ADCR Mixer", "AIRADCR Switch", "AIR Boost"}, + {"AIL Boost", NULL, "AIL"}, + {"AIR Boost", NULL, "AIR"}, + /* Input */ + {"ADCL Mixer", "MainMICADCL Switch", "MIC Boost"}, + {"ADCR Mixer", "MainMICADCR Switch", "MIC Boost"}, + {"MIC Boost", NULL, "Mic Bias"}, + {"ADCL Mixer", "AuxMICADCL Switch", "AUXMIC Boost"}, + {"ADCR Mixer", "AuxMICADCR Switch", "AUXMIC Boost"}, + {"AUXMIC Boost", NULL, "AuxMic Bias"}, + {"ADCL Mixer", "HPMICADCL Switch", "HEADMIC Boost"}, + {"ADCR Mixer", "HPMICADCR Switch", "HEADMIC Boost"}, + {"HEADMIC Boost", NULL, "HeadMic Bias"}, + /* Captrue */ + {"ADCL Switch", NULL, "ADCL PGA"}, + {"ADCR Switch", NULL, "ADCR PGA"}, + {"ADie Digital ADCL Switch", NULL, "ADCL Switch"}, + {"ADie Digital ADCR Switch", NULL, "ADCR Switch"}, + {"Digital ADCL Switch", NULL, "ADie Digital ADCL Switch"}, + {"Digital ADCR Switch", NULL, "ADie Digital ADCR Switch"}, + {"ADC", NULL, "Digital ADCL Switch"}, + {"ADC", NULL, "Digital ADCR Switch"}, + + {"Digital ADC1L Switch", NULL, "ADie Digital ADCL Switch"}, + {"Digital ADC1R Switch", NULL, "ADie Digital ADCR Switch"}, + {"ADC1", NULL, "Digital ADC1L Switch"}, + {"ADC1", NULL, "Digital ADC1R Switch"}, + + {"Mic Bias", NULL, "MIC"}, + {"AuxMic Bias", NULL, "AUXMIC"}, + {"HeadMic Bias", NULL, "HPMIC"}, + + /* DMIC0 */ + {"DMIC Switch", NULL, "DMIC"}, + {"Digital ADC DMIC In", NULL, "DMIC Switch"}, + {"Digital ADCL Switch", NULL, "Digital ADC DMIC In"}, + {"Digital ADCR Switch", NULL, "Digital ADC DMIC In"}, + /* DMIC1 */ + {"DMIC1 Switch", NULL, "DMIC1"}, + {"Digital ADC1 DMIC In", NULL, "DMIC1 Switch"}, + {"Digital ADC1L Switch", NULL, "Digital ADC1 DMIC In"}, + {"Digital ADC1R Switch", NULL, "Digital ADC1 DMIC In"}, + + /* Bias independent */ + {"Mic Bias", NULL, "Analog Power"}, + {"AuxMic Bias", NULL, "Analog Power"}, + {"HeadMic Bias", NULL, "Analog Power"}, + {"ADC-DAC Adie Loop", "switch", "ADC"}, + {"ADC1-DAC Adie Loop", "switch", "ADC1"}, + {"ADC-DAC Adie Loop post", NULL, "ADC-DAC Adie Loop"}, + {"ADC1-DAC Adie Loop post", NULL, "ADC1-DAC Adie Loop"}, + {"DAC", NULL, "ADC-DAC Adie Loop post"}, + {"DAC", NULL, "ADC1-DAC Adie Loop post"}, + + {"ADC-DAC Digital Loop", "switch", "ADC"}, + {"ADC1-DAC Digital Loop", "switch", "ADC1"}, + {"ADC-DAC Digital Loop post", NULL, "ADC-DAC Digital Loop"}, + {"ADC1-DAC Digital Loop post", NULL, "ADC1-DAC Digital Loop"}, + {"DAC", NULL, "ADC-DAC Digital Loop post"}, + {"DAC", NULL, "ADC1-DAC Digital Loop post"}, +}; + +static int sprd_codec_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_PGA_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + int ret = 0; + + sp_asoc_pr_info("Set PGA[%s] to %ld\n", sprd_codec_pga_debug_str[id], + ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + pga->pgaval = val; + if (pga->set) { + ret = pga->set(codec, pga->pgaval); + } + return ret; +} + +static int sprd_codec_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_PGA_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int invert = mc->invert; + struct sprd_codec_pga_op *pga = &(sprd_codec->pga[id]); + + ucontrol->value.integer.value[0] = pga->pgaval; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} +static int sprd_codec_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_SWITCH_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + struct sprd_codec_switch_op *switcher = &(sprd_codec->switcher[id]); + int ret = 0; + + sp_asoc_pr_info("Switch [%s] %s\n", switch_name[id], + ucontrol->value.integer.value[0]?"on":"off"); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + switcher->on = val; + if (switcher->set) { + ret = switcher->set(codec, switcher->on); + } + return ret; +} + +static int sprd_codec_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = GET_SWITCH_ID(FUN_REG(mc->reg)); + int max = mc->max; + unsigned int invert = mc->invert; + struct sprd_codec_switch_op *switcher= &(sprd_codec->switcher[id]); + + ucontrol->value.integer.value[0] = switcher->on; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} +static int sprd_codec_inter_pa_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + + sp_asoc_pr_info("Config inter PA 0x%lx\n", + ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + + mutex_lock(&sprd_codec->inter_pa_mutex); + + sprd_codec->inter_pa.value = (u32) val; + + if (sprd_codec->inter_pa.set) { + mutex_unlock(&sprd_codec->inter_pa_mutex); + sprd_inter_speaker_pa(codec, 1); + } else { + mutex_unlock(&sprd_codec->inter_pa_mutex); + } + return ret; +} + +static int sprd_codec_inter_pa_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_pa_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_pa.value; + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_info_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int ret = 0; + + sp_asoc_pr_info("%s, %d\n", + __func__,(int)ucontrol->value.integer.value[0]); + + val = ucontrol->value.integer.value[0]; + + if (SPRD_CODEC_INFO != val) { + sp_asoc_pr_info("%s, wrong codec info (%d) \n", __func__, val); + } + return ret; +} + +static int sprd_codec_info_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 chip_id = 0; + int ver_id = 0; + + ucontrol->value.integer.value[0] = AUDIO_CODEC_2723; + + chip_id = sci_get_ana_chip_id() >> 16; + ver_id = sci_get_ana_chip_ver(); + + if (chip_id == 0x2723) + if (ver_id == AUDIO_2723_VER_E) + ucontrol->value.integer.value[0] = AUDIO_CODEC_2723E; + else if (ver_id == AUDIO_2723_VER_T) + ucontrol->value.integer.value[0] = AUDIO_CODEC_2723T; + + sp_asoc_pr_info("%s, codec info = %d\n", + __func__, ucontrol->value.integer.value[0]); + + return 0; +} + +static int sprd_codec_linein_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + int i = 0; + + sp_asoc_pr_info("%s, 0x%08x\n", + __func__,(int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->sprd_linein_mute_mutex); + sprd_codec->sprd_linein_mute = (u32) val; + if (sprd_codec->sprd_linein_set) { + _mixer_adc_linein_mute_nolock(codec, sprd_codec->sprd_linein_mute); + } + mutex_unlock(&sprd_codec->sprd_linein_mute_mutex); + return ret; +} + +static int sprd_codec_linein_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->sprd_linein_mute_mutex); + ucontrol->value.integer.value[0] = sprd_codec->sprd_linein_mute; + mutex_unlock(&sprd_codec->sprd_linein_mute_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + return 0; +} +static void _dacspkl_switch_nolock(struct snd_soc_codec *codec, int val) +{ + int dacrspkl = val & BIT(SPRD_CODEC_RIGHT); + int daclspkl = val & BIT(SPRD_CODEC_LEFT); + int r_on = dacrspkl ? 1:0; + int l_on = daclspkl ? 1:0; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + + sp_asoc_pr_info("%s,l:%d,r:%d\n",__func__,l_on,r_on); + if (sprd_codec->sprd_dacspkl_set & BIT(SPRD_CODEC_RIGHT)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACR_AOL), + r_on << DACR_AOL); + } + if (sprd_codec->sprd_dacspkl_set & BIT(SPRD_CODEC_LEFT)) { + snd_soc_update_bits(codec, SOC_REG(ANA_CDC6), BIT(DACL_AOL), + l_on << DACL_AOL); + } +} + +static int dacspkl_enable_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + unsigned int shift = mc ->shift; + + sp_asoc_pr_info("%s, %s(%s)\n", + __func__,shift?"dacrspkl":"daclspkl",(int)ucontrol->value.integer.value[0] ? "on":"off"); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->sprd_dacspkl_mutex); + if(val){ + sprd_codec->sprd_dacspkl_enable |= (1 << shift); + } else { + sprd_codec->sprd_dacspkl_enable &= ~(1 << shift); + } + sp_asoc_pr_info("%s,0x%x,0x%x\n",__func__,sprd_codec->sprd_dacspkl_enable,sprd_codec->sprd_dacspkl_set); + if (sprd_codec->sprd_dacspkl_set) { + _dacspkl_switch_nolock(codec, sprd_codec->sprd_dacspkl_enable); + } + mutex_unlock(&sprd_codec->sprd_dacspkl_mutex); + return 0; +} + +static int dacspkl_enable_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + int shift = mc->shift; + unsigned int invert = mc->invert; + int mask = 1 << shift; + + sp_asoc_pr_info("%s, 0x%08x,%s,shift:%d\n", + __func__,mask,mask&0x2 ? "dacrspkl":"daclspkl",shift); + mutex_lock(&sprd_codec->sprd_dacspkl_mutex); + ucontrol->value.integer.value[0] = (sprd_codec->sprd_dacspkl_enable & mask) >> shift; + mutex_unlock(&sprd_codec->sprd_dacspkl_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + return 0; +} + +static int sprd_codec_inter_hp_pa_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + struct sprd_codec_inter_hp_pa *p_setting = + &sprd_codec->inter_hp_pa.setting; + + sp_asoc_pr_info("Config inter HP PA 0x%08x\n", + (int)ucontrol->value.integer.value[0]); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = max - val; + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + sprd_codec->inter_hp_pa.value = (u32) val; + if (sprd_codec->inter_hp_pa.set) { + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + sprd_codec_hp_pa_lpw(codec, p_setting->class_g_low_power); + sprd_codec_hp_pa_mode(codec, p_setting->class_g_mode); + sprd_codec_hp_pa_osc(codec, p_setting->class_g_osc); + sprd_codec_hp_pa_cgcal_en(codec, p_setting->class_g_cgcal); + } else { + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + } + return ret; +} + +static int sprd_codec_inter_hp_pa_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int max = mc->max; + unsigned int invert = mc->invert; + + mutex_lock(&sprd_codec->inter_hp_pa_mutex); + ucontrol->value.integer.value[0] = sprd_codec->inter_hp_pa.value; + mutex_unlock(&sprd_codec->inter_hp_pa_mutex); + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static int sprd_codec_mic_bias_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret = 0; + int id = GET_MIC_BIAS_ID(reg); + + val = (ucontrol->value.integer.value[0] & mask); + + if (invert) + val = max - val; + if (sprd_codec->mic_bias[id] == val) { + return 0; + } + + sp_asoc_pr_info("%s Switch %s\n", mic_bias_name[id], STR_ON_OFF(val)); + + sprd_codec->mic_bias[id] = val; + if (val) { + ret = + snd_soc_dapm_force_enable_pin(&codec->card->dapm, + mic_bias_name[id]); + } else { + ret = + snd_soc_dapm_disable_pin(&codec->card->dapm, + mic_bias_name[id]); + } + + /* signal a DAPM event */ + snd_soc_dapm_sync(&codec->card->dapm); + + return ret; +} + +static int sprd_codec_mic_bias_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + unsigned int reg = FUN_REG(mc->reg); + int max = mc->max; + unsigned int invert = mc->invert; + int id = GET_MIC_BIAS_ID(reg); + + ucontrol->value.integer.value[0] = sprd_codec->mic_bias[id]; + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -600, 75, 0); +static const DECLARE_TLV_DB_SCALE(hp_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(ear_tlv, -3600, 300, 1); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -2400, 300, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -350, 50, 1); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(auxmic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(headmic_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(ailr_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_RANGE(clsg_1_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-3000, 300, 1), + 8, 46, TLV_DB_SCALE_ITEM(-850, 50, 0), +); + +static const struct soc_enum codec_info_enum = + SOC_ENUM_SINGLE_EXT(SP_AUDIO_CODEC_NUM, codec_hw_info); + +static const DECLARE_TLV_DB_SCALE(clsg_2_tlv, -84, 12, 0); + + +#define SPRD_CODEC_PGA(xname, xreg, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, FUN_REG(xreg), 0, 15, 0, \ + sprd_codec_vol_get, sprd_codec_vol_put, tlv_array) + +#define SPRD_CODEC_PGA_MAX(xname, xreg, max, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, FUN_REG(xreg), 0, max, 0, \ + sprd_codec_vol_get, sprd_codec_vol_put, tlv_array) + +#define SPRD_CODEC_MIC_BIAS(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, \ + sprd_codec_mic_bias_get, sprd_codec_mic_bias_put) + +#define SPRD_CODEC_SWITCH(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, \ + sprd_codec_switch_get, sprd_codec_switch_put) + +static const struct snd_kcontrol_new sprd_codec_snd_controls[] = { + SPRD_CODEC_PGA("SPKL Playback Volume", SPRD_CODEC_PGA_SPKL, spk_tlv), + SPRD_CODEC_PGA("SPKR Playback Volume", SPRD_CODEC_PGA_SPKR, spk_tlv), + SPRD_CODEC_PGA("HPL Playback Volume", SPRD_CODEC_PGA_HPL, hp_tlv), + SPRD_CODEC_PGA("HPR Playback Volume", SPRD_CODEC_PGA_HPR, hp_tlv), + SPRD_CODEC_PGA_MAX("HPL CG Playback Volume1", SPRD_CODEC_PGA_CG_HPL_1, 63, + clsg_1_tlv), + SPRD_CODEC_PGA_MAX("HPR CG Playback Volume1", SPRD_CODEC_PGA_CG_HPR_1, 63, + clsg_1_tlv), + SPRD_CODEC_PGA_MAX("HPL CG Playback Volume2", SPRD_CODEC_PGA_CG_HPL_2, 7, + clsg_2_tlv), + SPRD_CODEC_PGA_MAX("HPR CG Playback Volume2", SPRD_CODEC_PGA_CG_HPR_2, 7, + clsg_2_tlv), + SPRD_CODEC_PGA("EAR Playback Volume", SPRD_CODEC_PGA_EAR, ear_tlv), + + SPRD_CODEC_PGA_MAX("ADCL Capture Volume", SPRD_CODEC_PGA_ADCL, 63, + adc_tlv), + SPRD_CODEC_PGA_MAX("ADCR Capture Volume", SPRD_CODEC_PGA_ADCR, 63, + adc_tlv), + + SPRD_CODEC_PGA_MAX("DACL Playback Volume", SPRD_CODEC_PGA_DACL, 7, + dac_tlv), + SPRD_CODEC_PGA_MAX("DACR Playback Volume", SPRD_CODEC_PGA_DACR, 7, + dac_tlv), + SPRD_CODEC_PGA_MAX("MIC Boost", SPRD_CODEC_PGA_MIC, 3, mic_tlv), + SPRD_CODEC_PGA_MAX("AUXMIC Boost", SPRD_CODEC_PGA_AUXMIC, 3, + auxmic_tlv), + SPRD_CODEC_PGA_MAX("HEADMIC Boost", SPRD_CODEC_PGA_HEADMIC, 3, + headmic_tlv), + SPRD_CODEC_PGA_MAX("Linein Boost", SPRD_CODEC_PGA_AIL, 3, ailr_tlv), + + SOC_SINGLE_EXT("Inter PA Config", 0, 0, INT_MAX, 0, + sprd_codec_inter_pa_get, sprd_codec_inter_pa_put), + + SOC_SINGLE_EXT("Inter HP PA Config", 0, 0, INT_MAX, 0, + sprd_codec_inter_hp_pa_get, sprd_codec_inter_hp_pa_put), + + SPRD_CODEC_SWITCH("DACL Switch",SPRD_CODEC_DACL), + + SPRD_CODEC_SWITCH("DACR Switch",SPRD_CODEC_DACR), + + SPRD_CODEC_SWITCH("HPLCGL Switch",SPRD_CODEC_HPLCGL), + + SPRD_CODEC_SWITCH("HPRCGR Switch",SPRD_CODEC_HPRCGR), + + SPRD_CODEC_MIC_BIAS("MIC Bias Switch", SPRD_CODEC_MIC_BIAS), + + SPRD_CODEC_MIC_BIAS("AUXMIC Bias Switch", SPRD_CODEC_AUXMIC_BIAS), + + SPRD_CODEC_MIC_BIAS("HEADMIC Bias Switch", SPRD_CODEC_HEADMIC_BIAS), + + SOC_SINGLE_EXT("Linein Mute Switch", 0, 0, 1, 0, + sprd_codec_linein_mute_get, sprd_codec_linein_mute_put), + + SOC_SINGLE_EXT("DACLSPKL Enable", 0, SPRD_CODEC_LEFT , 1, 0, + dacspkl_enable_get, dacspkl_enable_put), + + SOC_SINGLE_EXT("DACRSPKL Enable", 0, SPRD_CODEC_RIGHT , 1, 0, + dacspkl_enable_get, dacspkl_enable_put), + + SOC_ENUM_EXT("Aud Codec Info", codec_info_enum, + sprd_codec_info_get, sprd_codec_info_put), +}; + +static unsigned int sprd_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + /* Because snd_soc_update_bits reg is 16 bits short type, + so muse do following convert + */ + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + return arch_audio_codec_read(reg); + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + return __raw_readl((void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base)); + } else if (IS_SPRD_CODEC_MIXER_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + return mixer->on; + } else if (IS_SPRD_CODEC_PGA_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_MIC_BIAS_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_SWITCH_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_ADC_LOOP_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_ADC_LOOP_ID(FUN_REG(reg)); + return sprd_codec->ad_da_loop_en[id]; + } else + sp_asoc_pr_dbg("read the register is not codec's reg = 0x%x\n", + reg); + return 0; +} + +static int sprd_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + int ret = 0; + if (IS_SPRD_CODEC_AP_RANG(reg | SPRD_CODEC_AP_BASE_HI)) { + reg |= SPRD_CODEC_AP_BASE_HI; + ret = arch_audio_codec_write(reg, val); + sp_asoc_pr_reg("A[0x%04x] W:[0x%08x] R:[0x%08x]\n", + (reg - CODEC_AP_BASE) & 0xFFFF, val, arch_audio_codec_read(reg)); + return ret; + } else if (IS_SPRD_CODEC_DP_RANG(reg | SPRD_CODEC_DP_BASE_HI)) { + reg |= SPRD_CODEC_DP_BASE_HI; + __raw_writel(val, (void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base)); + sp_asoc_pr_reg("D[0x%04x] W:[0x%08x] R:[0x%08x]\n", + (reg - CODEC_DP_BASE) & 0xFFFF, val, + __raw_readl((void __iomem *)(reg - CODEC_DP_BASE + sprd_codec_dp_base))); + return ret; + } else if (IS_SPRD_CODEC_MIXER_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_MIXER_ID(FUN_REG(reg)); + struct sprd_codec_mixer *mixer = &(sprd_codec->mixer[id]); + mixer->on = val ? 1 : 0; + } else if (IS_SPRD_CODEC_PGA_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_MIC_BIAS_RANG(FUN_REG(reg))) { + } else if (IS_SPRD_CODEC_SWITCH_RANG(FUN_REG(reg))) { + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + int id = GET_SWITCH_ID(FUN_REG(reg)); + struct sprd_codec_switch_op *switcher = &(sprd_codec->switcher[id]); + switcher->on = val ? 1 : 0; + } else + sp_asoc_pr_dbg("write the register is not codec's reg = 0x%x\n", + reg); + return ret; +} + +static int sprd_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int mask = 0x0F; + int shift = 0; + int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sprd_codec->da_sample_val = rate; + sp_asoc_pr_info("Playback rate is [%d]\n", rate); + sprd_codec_set_sample_rate(codec, rate, mask, shift); + } else { + sp_asoc_pr_info("Capture rate is [%d]\n", rate); + if (dai->id != SPRD_CODEC_IIS1_ID) { + sprd_codec->ad_sample_val = rate; + sprd_codec_set_ad_sample_rate(codec, rate, mask, shift); + } else { + sprd_codec->ad1_sample_val = rate; + sprd_codec_set_ad_sample_rate(codec, rate, + ADC1_SRC_N_MASK, + ADC1_SRC_N); + } + } + + return 0; +} + +static int sprd_codec_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +#if 0 +static void sprd_codec_dac_mute_irq_enable(struct snd_soc_codec *codec) +{ + int mask = BIT(DAC_MUTE_D); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_CLR), mask, mask); + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, mask); +} +#endif + +static irqreturn_t sprd_codec_dp_irq(int irq, void *dev_id) +{ + int mask; + struct sprd_codec_priv *sprd_codec = dev_id; + struct snd_soc_codec *codec = sprd_codec->codec; + mask = snd_soc_read(codec, AUD_AUD_STS0); + sp_asoc_pr_dbg("dac mute irq mask = 0x%x\n", mask); + if (BIT(DAC_MUTE_D_MASK) & mask) { + mask = BIT(DAC_MUTE_D); + complete(&sprd_codec->completion_dac_mute); + } + if (BIT(DAC_MUTE_U_MASK) & mask) { + mask = BIT(DAC_MUTE_U); + } + snd_soc_update_bits(codec, SOC_REG(AUD_INT_EN), mask, 0); + return IRQ_HANDLED; +} + +#if 0 +static int sprd_codec_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + if (dai->id == SPRD_CODEC_IIS1_ID) /*codec_dai_1 has no playback */ + return 0; + + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sp_asoc_pr_dbg("mute %i\n", mute); + + ret = + snd_soc_update_bits(codec, SOC_REG(AUD_DAC_CTL), + BIT(DAC_MUTE_START), + mute ? BIT(DAC_MUTE_START) : 0); +#if 0 + if (mute && ret) { + int dac_mute_complete; + struct sprd_codec_priv *sprd_codec = + snd_soc_codec_get_drvdata(codec); + sp_asoc_pr_dbg("Dac Mute IRQ enable\n"); + sprd_codec_dac_mute_irq_enable(codec); + init_completion(&sprd_codec->completion_dac_mute); + dac_mute_complete = + wait_for_completion_timeout + (&sprd_codec->completion_dac_mute, + msecs_to_jiffies(SPRD_CODEC_DAC_MUTE_TIMEOUT)); + sp_asoc_pr_dbg("Dac Mute Completion %d\n", + dac_mute_complete); + if (!dac_mute_complete) { + pr_err("Dac Mute Timeout\n"); + } + } +#endif + + } + + return ret; +} +#endif + +static struct snd_soc_dai_ops sprd_codec_dai_ops = { + .hw_params = sprd_codec_pcm_hw_params, + .hw_free = sprd_codec_pcm_hw_free, +}; + +#ifdef CONFIG_PM +static int sprd_codec_soc_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_soc_resume(struct snd_soc_codec *codec) +{ + return 0; +} +#else +#define sprd_codec_soc_suspend NULL +#define sprd_codec_soc_resume NULL +#endif + +/* + * proc interface + */ + +#ifdef CONFIG_PROC_FS +static void sprd_codec_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct sprd_codec_priv *sprd_codec = entry->private_data; + struct snd_soc_codec *codec = sprd_codec->codec; + int reg; + + snd_iprintf(buffer, "%s digital part\n", codec->name); + for (reg = SPRD_CODEC_DP_BASE; reg < SPRD_CODEC_DP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_DP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } + snd_iprintf(buffer, "%s analog part\n", codec->name); + for (reg = SPRD_CODEC_AP_BASE; reg < SPRD_CODEC_AP_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (unsigned int)(reg - SPRD_CODEC_AP_BASE) + , snd_soc_read(codec, reg + 0x00) + , snd_soc_read(codec, reg + 0x04) + , snd_soc_read(codec, reg + 0x08) + , snd_soc_read(codec, reg + 0x0C) + ); + } +} + +static void aud_glb_reg_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + #define N 30 + int i = 0, j = 0; + glb_reg_dump_t *aud_glb_reg = NULL; + glb_reg_dump_t *reg_p = NULL; + aud_glb_reg = devm_kzalloc(entry->card->dev, sizeof(glb_reg_dump_t) * N, GFP_KERNEL); + if (NULL == aud_glb_reg) { + sp_asoc_pr_info("%, error, not enough memory!!!\n", __func__); + return; + } + reg_p = aud_glb_reg; + AUDIO_GLB_REG_DUMP_LIST(reg_p); + + snd_iprintf(buffer, "audio global register dump\n"); + for (i = 0; i < N; i++) { + for (j = 0; j < aud_glb_reg[i].count; j++) + if (aud_glb_reg[i].func) { + if (j == 0) + snd_iprintf(buffer, "%30s: 0x%08x 0x%08x\n", + aud_glb_reg[i].reg_name, + aud_glb_reg[i].reg + sizeof(aud_glb_reg[i].reg) * j, + aud_glb_reg[i].func((void *)(aud_glb_reg[i].reg + sizeof(aud_glb_reg[i].reg) * j)) + ); + else + snd_iprintf(buffer, "%27s+%2d: 0x%08x 0x%08x\n", + aud_glb_reg[i].reg_name, + j, + aud_glb_reg[i].reg + sizeof(aud_glb_reg[i].reg) * j, + aud_glb_reg[i].func((void *)(aud_glb_reg[i].reg + sizeof(aud_glb_reg[i].reg) * j)) + ); + } + } + kfree(aud_glb_reg); +} + +static void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ + struct snd_info_entry *entry; + struct snd_soc_codec *codec = sprd_codec->codec; + + if (!snd_card_proc_new(codec->card->snd_card, "sprd-codec", &entry)) + snd_info_set_text_ops(entry, sprd_codec, sprd_codec_proc_read); + if (!snd_card_proc_new(codec->card->snd_card, "aud-glb", &entry)) + snd_info_set_text_ops(entry, sprd_codec, aud_glb_reg_read); +} +#else /* !CONFIG_PROC_FS */ +static inline void sprd_codec_proc_init(struct sprd_codec_priv *sprd_codec) +{ +} +#endif + +static void sprd_codec_power_changed(struct power_supply *psy) +{ + struct sprd_codec_priv *sprd_codec = 0; + sp_asoc_pr_dbg("%s %s\n", __func__, psy->name); +#if 1 + sprd_codec = container_of(psy, struct sprd_codec_priv, audio_ldo); + mutex_lock(&sprd_codec->inter_pa_mutex); + if (sprd_codec->inter_pa.set && sprd_codec->inter_pa.setting.is_LDO_mode + && sprd_codec->inter_pa.setting.is_auto_LDO_mode) { + sprd_codec_pa_ldo_auto(sprd_codec->codec, 0); + } + mutex_unlock(&sprd_codec->inter_pa_mutex); + if (atomic_read(&sprd_codec->ldo_refcount) >= 1) { + sprd_codec_vcom_ldo_auto(sprd_codec->codec, 0); + } +#endif +} + +static int sprd_codec_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + return -EINVAL; +} + +static int sprd_codec_audio_ldo(struct sprd_codec_priv *sprd_codec) +{ + int ret = 0; + struct snd_soc_codec *codec = sprd_codec->codec; + sprd_codec->audio_ldo.name = "audio-ldo"; + sprd_codec->audio_ldo.get_property = sprd_codec_power_get_property; + sprd_codec->audio_ldo.external_power_changed = sprd_codec_power_changed; +#ifdef CONFIG_POWER_SUPPLY + ret = power_supply_register(codec->dev, &sprd_codec->audio_ldo); + if (ret) { + pr_err("ERR:Register power supply error!\n"); + return -EFAULT; + } +#endif + return ret; +} + +#define SPRD_CODEC_PCM_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define SPRD_CODEC_PCM_AD_RATES \ + (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SRC_SUPPORT_RATE | \ + SNDRV_PCM_RATE_48000) + +/* PCM Playing and Recording default in full duplex mode */ +static struct snd_soc_dai_driver sprd_codec_dai[] = { + { + .name = "sprd-codec-i2s", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "sprd-codec-vaudio", + .playback = { + .stream_name = "Playback-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Main-Capture-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#endif + /*digital ad1 */ + { + .id = SPRD_CODEC_IIS1_ID, + .name = "codec-i2s-ext", + .capture = { + .stream_name = "Ext-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + /*digital ad1 */ + { + .id = SPRD_CODEC_IIS1_ID, + .name = "codec-vaudio-ext", + .capture = { + .stream_name = "Ext-Capture-Vaudio", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_AD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +#endif + { + .name = "sprd-codec-fm", + .playback = { + .stream_name = "DFM-Output", + .channels_min = 1, + .channels_max = 2, + .rates = SPRD_CODEC_PCM_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_codec_dai_ops, + }, +}; + +static int sprd_codec_soc_probe(struct snd_soc_codec *codec) +{ + struct sprd_codec_priv *sprd_codec = snd_soc_codec_get_drvdata(codec); + int ret = 0; +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 +#ifdef CONFIG_INPUT_HEADSET_SPRD_SC2723 + int hp_plug_state = get_hp_plug_state(); +#endif +#endif + + sp_asoc_pr_dbg("%s\n", __func__); + + codec->dapm.idle_bias_off = 1; + + sprd_codec->codec = codec; + + sprd_codec_proc_init(sprd_codec); + + sprd_codec_audio_ldo(sprd_codec); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + sprd_inter_headphone_pa_pre(sprd_codec, 1); +#else +#ifdef CONFIG_INPUT_HEADSET_SPRD_SC2723 + sprd_inter_headphone_pa_pre(sprd_codec, hp_plug_state); +#endif +#endif + return ret; +} + +/* power down chip */ +static int sprd_codec_soc_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static int sprd_codec_power_regulator_init(struct sprd_codec_priv *sprd_codec, + struct device *dev) +{ + sprd_codec_power_get(dev, &sprd_codec->main_mic, "MICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->aux_mic, "AUXMICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->head_mic, "HEADMICBIAS"); + sprd_codec_power_get(dev, &sprd_codec->cg_ldo, "CGLDO"); + + return 0; +} + +static void sprd_codec_power_regulator_exit(struct sprd_codec_priv *sprd_codec) +{ + sprd_codec_power_put(&sprd_codec->main_mic); + sprd_codec_power_put(&sprd_codec->aux_mic); + sprd_codec_power_put(&sprd_codec->head_mic); + sprd_codec_power_put(&sprd_codec->cg_ldo); +} + +static struct snd_soc_codec_driver soc_codec_dev_sprd_codec = { + .probe = sprd_codec_soc_probe, + .remove = sprd_codec_soc_remove, + .suspend = sprd_codec_soc_suspend, + .resume = sprd_codec_soc_resume, + .read = sprd_codec_read, + .write = sprd_codec_write, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = sprd_codec_intercon, + .num_dapm_routes = ARRAY_SIZE(sprd_codec_intercon), + .controls = sprd_codec_snd_controls, + .num_controls = ARRAY_SIZE(sprd_codec_snd_controls), +}; + +static int sprd_codec_probe(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec; + int ret; + struct device_node *node = pdev->dev.of_node; + u32 ana_chip_id; + + sp_asoc_pr_dbg("%s\n", __func__); + + arch_audio_codec_switch(AUDIO_TO_ARM_CTRL); + + sprd_codec = + devm_kzalloc(&pdev->dev, sizeof(struct sprd_codec_priv), + GFP_KERNEL); + if (sprd_codec == NULL) + return -ENOMEM; + + sprd_codec->da_sample_val = 44100; /*inital value for FM route */ + + if (node) { + u32 val; + u32 val_arr[2]; + if (!of_property_read_u32(node, "sprd,hp_pa_ver", &val)) { + if (val < SPRD_CODEC_HP_PA_VER_MAX) { + sprd_codec->hp_ver = val; + sp_asoc_pr_dbg("Set HP PA Ver is %d!\n", val); + } else { + pr_err + ("ERR:This driver just support less %d version!\n", + SPRD_CODEC_HP_PA_VER_MAX); + return -EINVAL; + } + } + if (!of_property_read_u32(node, "sprd,ap_irq", &val)) { + sprd_codec->ap_irq = val; + sp_asoc_pr_dbg("Set AP IRQ is %d!\n", val); + } else { + pr_err("ERR:Must give me the AP IRQ!\n"); + return -EINVAL; + } + if (!of_property_read_u32(node, "sprd,dp_irq", &val)) { + sprd_codec->dp_irq = val; + sp_asoc_pr_dbg("Set DP IRQ is %d!\n", val); + } else { + pr_err("ERR:Must give me the DP IRQ!\n"); + return -EINVAL; + } + if (!of_property_read_u32(node, "sprd,def_da_fs", &val)) { + sprd_codec->da_sample_val = val; + sp_asoc_pr_dbg("Change DA default fs to %d!\n", val); + } + if (!of_property_read_u32_array(node, "sprd,reg_dp", &val_arr[0], 2)) { + sprd_codec_dp_base = (unsigned long)ioremap_nocache(val_arr[0], val_arr[1]); + if(!sprd_codec_dp_base) { + pr_err("ERR: cannot create iomap address for codec DP!\n"); + return -EINVAL; + } + } else { + pr_err("ERR:Must give me the codec DP reg address!\n"); + return -EINVAL; + } + } else { + sprd_codec->ap_irq = CODEC_AP_IRQ; + sprd_codec->dp_irq = CODEC_DP_IRQ; + sprd_codec_dp_base = (unsigned long)ioremap_nocache(CODEC_DP_BASE_DEFAULT, CODEC_DP_SIZE_DEFAULT); + if(!sprd_codec_dp_base) { + pr_err("ERR: cannot create iomap address for codec DP with default address!\n"); + return -EINVAL; + } + } + ana_chip_id = sci_get_ana_chip_id() >> 16; + if (ana_chip_id == 0x2713) + sprd_codec->hp_ver = SPRD_CODEC_HP_PA_VER_1; + else if (ana_chip_id == 0x2711) + sprd_codec->hp_ver = SPRD_CODEC_HP_PA_VER_2; + else { + pr_info("ana_chip_id is 0x%x\n", (unsigned int)sci_get_ana_chip_id); + } + platform_set_drvdata(pdev, sprd_codec); + + ret = + request_irq(sprd_codec->ap_irq, sprd_codec_ap_irq, 0, + "sprd_codec_ap", sprd_codec); + if (ret) { + pr_err("ERR:Request irq ap failed!\n"); + goto err_irq; + } + + INIT_DELAYED_WORK(&sprd_codec->ovp_delayed_work, + sprd_codec_ovp_delay_worker); + + ret = + request_irq(sprd_codec->dp_irq, sprd_codec_dp_irq, 0, + "sprd_codec_dp", sprd_codec); + if (ret) { + pr_err("ERR:Request irq dp failed!\n"); + goto dp_err_irq; + } + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_sprd_codec, + sprd_codec_dai, + ARRAY_SIZE(sprd_codec_dai)); + if (ret != 0) { + pr_err("ERR:Failed to register CODEC: %d\n", ret); + goto reg_err_irq; + } + + sprd_codec_inter_pa_init(sprd_codec); + sprd_codec_inter_hp_pa_init(sprd_codec); + sprd_codec_linein_mute_init(sprd_codec); + sprd_codec_switch_init(sprd_codec); + sprd_codec_dacspkl_enable_init(sprd_codec); + sprd_codec_power_regulator_init(sprd_codec, &pdev->dev); + + mutex_init(&sprd_codec->inter_pa_mutex); + mutex_init(&sprd_codec->inter_hp_pa_mutex); + spin_lock_init(&sprd_codec->sprd_codec_fun_lock); + spin_lock_init(&sprd_codec->sprd_codec_pa_sw_lock); + + sprd_codec_vcom_ldo_cfg(sprd_codec, ldo_v_map, ARRAY_SIZE(ldo_v_map)); + sprd_codec_pa_ldo_cfg(sprd_codec, ldo_v_map, ARRAY_SIZE(ldo_v_map)); + + sprd_codec->nb.notifier_call = hp_notifier_handler; +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 +#ifdef CONFIG_INPUT_HEADSET_SPRD_SC2723 + hp_register_notifier(&sprd_codec->nb); +#endif +#endif + + return 0; +reg_err_irq: + free_irq(sprd_codec->dp_irq, sprd_codec); +dp_err_irq: + free_irq(sprd_codec->ap_irq, sprd_codec); +err_irq: + iounmap(sprd_codec_dp_base); + return -EINVAL; +} + +static int sprd_codec_remove(struct platform_device *pdev) +{ + struct sprd_codec_priv *sprd_codec = platform_get_drvdata(pdev); + sprd_codec_power_regulator_exit(sprd_codec); + free_irq(sprd_codec->ap_irq, sprd_codec); + free_irq(sprd_codec->dp_irq, sprd_codec); + iounmap(sprd_codec_dp_base); +#ifndef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 +#ifdef CONFIG_INPUT_HEADSET_SPRD_SC2723 + hp_unregister_notifier(&sprd_codec->nb); +#endif +#endif + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id codec_of_match[] = { + {.compatible = "sprd,sprd-codec",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, codec_of_match); +#endif + +static struct platform_driver sprd_codec_codec_driver = { + .driver = { + .name = "sprd-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(codec_of_match), + }, + .probe = sprd_codec_probe, + .remove = sprd_codec_remove, +}; + +static int __init sprd_codec_driver_init(void) +{ + return platform_driver_register(&sprd_codec_codec_driver); +} + +late_initcall(sprd_codec_driver_init); + +MODULE_DESCRIPTION("SPRD-CODEC ALSA SoC codec driver"); +MODULE_AUTHOR("Jian Chen <jian.chen@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:sprd-codec"); diff --git a/sound/soc/sprd/codec/sprd/v4/sprd-codec.h b/sound/soc/sprd/codec/sprd/v4/sprd-codec.h new file mode 100644 index 00000000..44a00052 --- /dev/null +++ b/sound/soc/sprd/codec/sprd/v4/sprd-codec.h @@ -0,0 +1,593 @@ +/* + * sound/soc/sprd/codec/sprd/v4/sprd-codec.h + * + * SPRD-CODEC -- SpreadTrum Tiger intergrated codec. + * + * 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 __SPRD_CODEC_H +#define __SPRD_CODEC_H + +#include <soc/sprd/hardware.h> +#include <soc/sprd/globalregs.h> +#include <soc/sprd/sprd-audio.h> +#include <soc/sprd/adi.h> +#include <asm/io.h> + + +/* unit: ms */ +#define SPRD_CODEC_LDO_WAIT_TIME (5) +#define SPRD_CODEC_LDO_VCM_TIME (2) +#define SPRD_CODEC_DAC_MUTE_TIMEOUT (600) + +#define SPRD_CODEC_HP_POP_TIMEOUT (1000) + +#define SPRD_CODEC_INFO (AUDIO_CODEC_2723) + +#define SPRD_CODEC_RATE_8000 (10) +#define SPRD_CODEC_RATE_9600 ( 9) +#define SPRD_CODEC_RATE_11025 ( 8) +#define SPRD_CODEC_RATE_12000 ( 7) +#define SPRD_CODEC_RATE_16000 ( 6) +#define SPRD_CODEC_RATE_22050 ( 5) +#define SPRD_CODEC_RATE_24000 ( 4) +#define SPRD_CODEC_RATE_32000 ( 3) +#define SPRD_CODEC_RATE_44100 ( 2) +#define SPRD_CODEC_RATE_48000 ( 1) +#define SPRD_CODEC_RATE_96000 ( 0) + +/* AUD_TOP_CTL */ +#define DAC_EN_L (0) +#define ADC_EN_L (1) +#define DAC_EN_R (2) +#define ADC_EN_R (3) +#define ADC_SINC_SEL (8) +#define ADC_SINC_SEL_MASK (0x3) +#define ADC_DMIC_SEL (9) +#define ADC1_EN_L (10) +#define ADC1_EN_R (11) +#define ADC1_SINC_SEL (14) +#define ADC1_SINC_SEL_MASK (0x3) +#define ADC1_DMIC1_SEL (15) + +/* AUD_DAC_CTL */ +#define DAC_MUTE_START (14) +#define DAC_MUTE_EN (15) + +/* AUD_ADC_CTL */ +#define ADC_SRC_N (0) +#define ADC_SRC_N_MASK (0x0f) +#define ADC1_SRC_N (4) +#define ADC1_SRC_N_MASK (0xf0) + +#define AUD_LOOP_TEST (0) +#define LOOP_ADC_PATH_SEL (9) +#define LOOP_PATH_SEL (1) +#define LOOP_PATH_SEL_MASK (0x3) +/* AUD_AUD_STS0 */ +#define DAC_MUTE_U_MASK (5) +#define DAC_MUTE_D_MASK (4) +#define DAC_MUTE_U_RAW (3) +#define DAC_MUTE_D_RAW (2) +#define DAC_MUTE_ST (0) +#define DAC_MUTE_ST_MASK (0x3) + +/* AUD_INT_CLR */ +/* AUD_INT_EN */ +#define DAC_MUTE_U (1) +#define DAC_MUTE_D (0) + +/*AUD_DMIC_CTL*/ +#define ADC_DMIC_CLK_MODE (0) +#define ADC_DMIC_CLK_MODE_MASK (0x3) +#define ADC_DMIC_LR_SEL (2) +#define ADC1_DMIC_CLK_MODE (3) +#define ADC1_DMIC_CLK_MODE_MASK (0x3) +#define ADC1_DMIC_LR_SEL (5) +#define ADC_DMIC_EN (6) +#define ADC1_DMIC1_EN (7) + +/*DAC_SDM_DC_L*/ +#define DAC_SDM_DC_L (0) +#define DAC_SDM_DC_L_MASK (0xffff) +/*DAC_SDM_DC_H*/ +#define DAC_SDM_DC_R (0) +#define DAC_SDM_DC_R_MASK (0xff) + + +#define LDO_V_28 (0) +#define LDO_V_29 (1) +#define LDO_V_30 (2) +#define LDO_V_31 (3) +#define LDO_V_32 (4) +#define LDO_V_33 (5) +#define LDO_V_34 (6) +#define LDO_V_36 (7) + + +#define MIC_LDO_V_21 (0) +#define MIC_LDO_V_19 (1) +#define MIC_LDO_V_23 (2) +#define MIC_LDO_V_25 (3) + +/* ANA_PMU0 */ +#define PA_SW_EN (15) +#define PA_LDO_EN (14) +#define PA_EN (13) +#define OVP_LDO_EN (11) +#define LDOCG_EN (10) +#define VB_EN (9) +#define VBO_EN (8) +#define HEADMICBIAS_EN (7) +#define HEADMIC_SLEEP_EN (6) + +/* ANA_PMU1 */ +#define BG_EN (15) +#define BG_IBIAS_EN (14) +#define VCM_EN (13) +#define VCM_BUF_EN (12) +#define SEL_VCMI (11) +#define VCMI_FAST_EN (10) +#define MICBIAS_EN (9) +#define AUXMICBIAS_EN (8) +#define LDOCG_EN_FAL_ILD (3) +#define LDOCG_ISET_FAL (2) +#define LDOCG_RAMP_EN (1) +#define LDOVBO_FAST_EN (0) + +/* ANA_PMU2 */ +#define VCM_V (13) +#define VCM_V_MASK (0x7) +#define MICBIAS_V (11) +#define MICBIAS_V_MASK (0x3) +#define HEADMICBIAS_V (9) +#define HEADMICBIAS_V_MASK (0x3) + +/* ANA_PMU3 */ +#define BG_I (14) +#define BG_I_MASK (0x3) +#define PA_AB_I (11) +#define PA_AB_I_MASK (0x7) +#define PA_LDO_V (8) +#define PA_LDO_V_MASK (0x7) +#define PA_LDO_V_28 (0) +#define PA_LDO_V_29 (1) +#define PA_LDO_V_30 (2) +#define PA_LDO_V_31 (3) +#define PA_LDO_V_32 (4) +#define PA_LDO_V_33 (5) +#define PA_LDO_V_34 (6) +#define PA_LDO_V_36 (7) +#define CG_LDO_V (0) +#define CG_LDO_V_MASK (0xff) + +/* ANA_PMU4 */ +#define VB_CAL (8) +#define VB_CAL_MASK (0x1F) +#define VCM_CAL (5) +#define VCM_CAL_MASK (0x7) +#define LDO_PA_CAL (0) +#define LDO_PA_CAL_MASK (0x1F) + +/* ANA_PMU5 */ +#define PA_OVP_V (13) +#define PA_OVP_V_MASK (0x7) +#define PA_OVP_542 (0) +#define PA_OVP_527 (1) +#define PA_OVP_512 (2) +#define PA_OVP_496 (3) +#define PA_OVP_481 (4) +#define PA_OVP_465 (5) +#define PA_OVP_450 (6) +#define PA_OVP_435 (7) + +/* ANA_PMU6 */ +#define DRV_OCP_AOR_PD (15) +#define DRV_OCP_AOL_PD (14) +#define DRV_OCP_EAR_PD (13) +#define DRV_OCP_HP_PD (12) + +/* ANA_CDC0 */ +#define ADC_CLK_EN (15) +#define ADC_CLK_RST (14) +#define ADC_CLK_F (12) +#define ADC_CLK_F_MASK (0x3) +#define DAC_CLK_EN (11) +#define DAC_CLK_F (9) +#define DAC_CLK_F_MASK (0x3) +#define DRV_CLK_EN (8) + +/* ANA_CDC1 */ +#define ADCPGAL_BYP (14) +#define ADCPGAL_BYP_MASK (0x3) +#define ADCPGAL_EN (12) +#define ADCPGAL_EN_MASK (0x3) +#define ADCPGAR_BYP (10) +#define ADCPGAR_BYP_MASK (0x3) +#define ADCPGAR_EN (8) +#define ADCPGAR_EN_MASK (0x3) +#define ADC_IBUF_PD (7) +#define ADC_VREF1P5 (6) +#define ADCL_PD (5) +#define ADCL_RST (4) +#define ADCR_PD (3) +#define ADCR_RST (2) +#define FM_REC_EN (1) + +#define ADC_PGAL_BYP_SEL_MASK (0x3) +#define ADC_PGAL_BYP_SEL_NORMAL_INPUT (0x0) +#define ADC_PGAL_BYP_SEL_PGAL1_2_ADCL (0x1) +#define ADC_PGAL_BYP_SEL_HEADMIC_2_ADCL (0x2) +#define ADC_PGAL_BYP_SEL_ALL_DISCONNECT (0x3) + +#define ADC_PGAR_BYP_SEL_MASK (0x3) +#define ADC_PGAR_BYP_SEL_NORMAL_INPUT (0x0) +#define ADC_PGAR_BYP_SEL_PGAR1_2_ADCR (0x1) +#define ADC_PGAR_BYP_SEL_HEADMIC_2_ADCR (0x2) +#define ADC_PGAR_BYP_SEL_ALL_DISCONNECT (0x3) + +/* ANA_CDC2 */ +#define DACL_EN (15) +#define DACR_EN (14) +#define DACBUF_I_S (13) +#define DACDC_RMV_EN (12) +#define DACDC_RMV_S (10) +#define DACDC_RMV_S_MASK (0x3) +#define DRV_SOFT_EN (9) +#define HPL_EN (8) +#define HPR_EN (7) +#define DIFF_EN (6) +#define EAR_EN (5) +#define AOL_EN (4) +#define AOR_EN (3) + +/* ANA_CDC3 */ +#define CG_REF_EN (15) +#define CG_CHP_EN (14) +#define CG_HPL_EN (13) +#define CG_HPR_EN (12) +#define CG_HP_DIFF_EN (11) +#define CG_CHP_CLK_S (10) +#define CG_CHP_OSC (7) +#define CG_CHP_OSC_MASK (0x7) +#define CG_LPW (5) +#define CG_LPW_MASK (0x3) +#define CG_CHP_MODE (4) +#define CG_HP_MODE (3) +#define CG_HPCAL_EN (2) +#define CG_HPCALSTP_EN (1) + +/* ANA_CDC4 */ +#define PA_D_EN (15) +#define PA_DTRI_F (13) +#define PA_DTRI_F_MASK (0x03) +#define PA_DEMI_EN (12) +#define PA_SWOCP_PD (11) +#define PA_LDOOCP_PD (10) +#define PA_SS_EN (9) +#define PA_SS_RST (8) +#define PA_SS_F (6) +#define PA_SS_F_MASK (0x3) +#define PA_SS_32K_EN (5) +#define PA_SS_T (2) +#define PA_SS_T_MASK (0x7) +#define DRV_STOP_EN (1) + +/* ANA_CDC5 */ +#define MIC_ADCR (15) +#define AUXMIC_ADCR (14) +#define HEADMIC_ADCR (13) +#define AIL_ADCR (12) +#define AIR_ADCR (11) +#define MIC_ADCL (10) +#define AUXMIC_ADCL (9) +#define HEADMIC_ADCL (8) +#define AIL_ADCL (7) +#define AIR_ADCL (6) + +/* ANA_CDC6 */ +#define ADCL_AOL (15) +#define ADCR_AOL (14) +#define DACL_AOL (13) +#define DACR_AOL (12) +#define ADCL_AOR (11) +#define ADCR_AOR (10) +#define DACL_AOR (9) +#define DACR_AOR (8) + +/* ANA_CDC7 */ +#define ADCL_EAR (15) +#define ADCR_EAR (14) +#define DACL_EAR (13) +#define DACR_EAR (12) +#define HPL_CGL (11) +#define HPR_CGR (10) +#define ADCL_P_HPL (9) +#define ADCR_P_HPL (8) +#define DACL_P_HPL (7) +#define DACR_P_HPL (6) +#define ADCL_N_HPR (5) +#define ADCR_P_HPR (4) +#define DACL_N_HPR (3) +#define DACR_P_HPR (2) + +/* ANA_CDC8 */ +#define ADC_MIC_PGA (14) +#define ADC_MIC_PGA_MASK (0x3) +#define ADC_AUXMIC_PGA (12) +#define ADC_AUXMIC_PGA_MASK (0x3) +#define ADC_HEADMIC_PGA (10) +#define ADC_HEADMIC_PGA_MASK (0x3) +#define ADC_AILR_PGA (8) +#define ADC_AILR_PGA_MASK (0x3) + +/* ANA_CDC9 */ +#define ADC_PGA_L (4) +#define ADC_PGA_L_MASK (0x3F) +#define ADC_PGA_R (10) +#define ADC_PGA_R_MASK (0x3F) + + +/* ANA_CDC10 */ +#define DACL_G (13) +#define DACL_G_MASK (0x7) +#define DACR_G (10) +#define DACR_G_MASK (0x7) + +#define DACL_G_MUTE (0x0) +#define DACL_G_MINUS_3DB (0x1) +#define DACL_G_MINUS_2_5DB (0x2) +#define DACL_G_MINUS_2DB (0x3) +#define DACL_G_MINUS_1_5DB (0x4) +#define DACL_G_MINUS_1DB (0x5) +#define DACL_G_MINUS_0_5DB (0x6) +#define DACL_G_MINUS_0DB (0x7) + +#define DACR_G_MUTE (0x0) +#define DACR_G_MINUS_3DB (0x1) +#define DACR_G_MINUS_2_5DB (0x2) +#define DACR_G_MINUS_2DB (0x3) +#define DACR_G_MINUS_1_5DB (0x4) +#define DACR_G_MINUS_1DB (0x5) +#define DACR_G_MINUS_0_5DB (0x6) +#define DACR_G_MINUS_0DB (0x7) + + + +/* ANA_CDC11 */ +#define AOL_G (12) +#define AOL_G_MASK (0xF) +#define AOR_G (8) +#define AOR_G_MASK (0xF) + +/* ANA_CDC12 */ +#define EAR_G (12) +#define EAR_G_MASK (0xF) +#define HPL_G (8) +#define HPL_G_MASK (0xF) +#define HPR_G (4) +#define HPR_G_MASK (0xF) + +/* ANA_CDC13 */ +#define CG_HPL_G_2 (7) +#define CG_HPL_G_2_MASK (0x7) +#define CG_HPL_G_1 (10) +#define CG_HPL_G_1_MASK (0x3F) + +/* ANA_CDC14 */ +#define CG_HPR_G_2 (7) +#define CG_HPR_G_2_MASK (0x7) +#define CG_HPR_G_1 (10) +#define CG_HPR_G_1_MASK (0x3F) + + +/* ANA_CDC15 */ +#define AUD_NG_EN (15) +#define AUD_NG_DA_EN (14) +#define AUD_NG_PA_EN (13) +#define AUD_NG_PA_EN (13) +#define AUD_NG_CG_LPW_EN (12) +#define AUD_CG_DRE_EN (11) +#define AUD_CG_DRE_HYS_EN (10) +#define AUD_CG_POP_EN (9) +#define AUD_CG_POP_HYS_EN (8) + +/* ANA_CDC16 */ +#define HP_POP_CTL (6) +#define HP_POP_CTL_MASK (0x3) +#define HP_POP_CTL_DIS (0) +#define HP_POP_CTL_UP (1) +#define HP_POP_CTL_DOWN (2) +#define HP_POP_CTL_HOLD (3) + +#define HP_POP_STEP (3) +#define HP_POP_STEP_MASK (0x7) +#define HP_POP_STEP_012 (0) +#define HP_POP_STEP_025 (1) +#define HP_POP_STEP_05 (2) +#define HP_POP_STEP_1 (3) +#define HP_POP_STEP_2 (4) +#define HP_POP_STEP_4 (5) +#define HP_POP_STEP_8 (6) +#define HP_POP_STEP_16 (7) +#define ADC_PGA_IBIAS_CTL_OFFSET (8) +#define ADC_PGA_IBIAS_CTL_MASK (0x3) +#define ADC_PGA_IBIAS_CTL_10 (0x0) +#define ADC_PGA_IBIAS_CTL_08 (0x1) +#define ADC_PGA_IBIAS_CTL_05 (0x2) +#define ADC_PGA_IBIAS_CTL_04 (0x3) + +#define DAC_FILTER_IBIS_CTL_OFFSET (10) +#define DAC_FILTER_IBIS_CTL_MASK (0x3) +#define DAC_FILTER_IBIS_CTL_10 (0x0) +#define DAC_FILTER_IBIS_CTL_075 (0x1) +#define DAC_FILTER_IBIS_CTL_05 (0x2) +#define DAC_FILTER_IBIS_CTL_3_PER_7 (0x3) + +#define RCV_IBIS_CTL_OFFSET (12) +#define RCV_IBIS_CTL_MASK (0x3) +#define RCV_IBIS_CTL_095 (0x0) +#define RCV_IBIS_CTL_075 (0x1) +#define RCV_IBIS_CTL_065 (0x2) +#define RCV_IBIS_CTL_05 (0x3) + +#define HP_CLASSAB_IBIS_CTL_OFFSET (14) +#define HP_CLASSAB_IBIS_CTL_MASK (0x3) +#define HP_CLASSAB_IBIS_CTL_09 (0x0) +#define HP_CLASSAB_IBIS_CTL_06 (0x1) +#define HP_CLASSAB_IBIS_CTL_05 (0x2) +#define HP_CLASSAB_IBIS_CTL_3_PER_7 (0x3) + +#define AUDIO_POP_CHGR_PD (2) +#define AUDIO_POP_SOFTCHG_EN (1) + + +/* ANA_CDC17 */ + +/* ANA_CDC18 */ +#define CG_HPL_DCVI (8) +#define CG_HPL_DCVI_MASK (0xFF) +#define CG_HPR_DCVI (0) +#define CG_HPR_DCVI_MASK (0xFF) + +/* ANA_HDT0 */ +#define AUD_V2ADC_EN (15) +#define AUD_V2ADC_SEL (11) +#define AUD_V2ADC_SEL_MASK (0xF) +#define HIB_SBUT (1) +#define HIB_SBUT_MASK (0xF) + +/* ANA_HDT1 */ +#define AUD_GND_DET_PU_PD (15) +#define AUD_HEAD_L_INT_PU_PD (14) +#define AUD_HEAD_APP_G (9) +#define AUD_HEAD_APP_G_MASK (3) +#define AUD_HEAD_APP_P (7) +#define AUD_HEAD_APP_P_MASK (3) + +/* ANA_HDT2 */ + +/* ANA_STS0 */ +#define HP_POP_FLG (7)////////////CHECK +#define HP_POP_FLG_MASK (0x3) +#define HP_POP_FLG_NEAR_CMP (3) +#define OVP_FLAG (6) + +/* ANA_STS1 */ + +/* ANA_STS2 */ + +/* DANGL */ +#define AUDIO_DANGL (15) +/* DANGR */ +#define AUDIO_DANGR (15) +/* DRE */ + +/* DIG_CFG0 */ +#define AUDIFA_DACL_EN (9) +#define AUDIFA_ADCL_EN (10) +#define AUDIFA_DACR_EN (11) +#define AUDIFA_ADCR_EN (12) +#define AUDIO_ADIE_LOOP_EN (14) + +/* DIG_CFG1 */ + +/* DIG_CFG2 */ + +/* DIG_CFG3 */ +#define AUD_A_INT_CLR (3) +#define AUD_A_INT_CLR_MASK (0xFF) +/* DIG_CFG4 */ +#define AUD_A_INT_EN (8) +#define AUD_A_INT_EN_MASK (0xFF) +/* DIG_STS0 */ +#define AUDIO_POP_IRQ (7) +#define OVP_IRQ (6) +#define OTP_IRQ (5) +#define PA_OCP_IRQ (4) +#define LOR_OCP_IRQ (3) +#define LOL_OCP_IRQ (2) +#define EAR_OCP_IRQ (1) +#define HP_OCP_IRQ (0) + +/* DIG_STS1 */ +#define AUD_IRQ_MSK (8) +#define AUD_IRQ_MSK_MASK (0xFF) + + +#define SPRD_CODEC_DP_BASE (CODEC_DP_BASE) +#define AUD_TOP_CTL (SPRD_CODEC_DP_BASE + 0x0000) +#define AUD_AUD_CTR (SPRD_CODEC_DP_BASE + 0x0004) +#define AUD_I2S_CTL (SPRD_CODEC_DP_BASE + 0x0008) +#define AUD_DAC_CTL (SPRD_CODEC_DP_BASE + 0x000C) +#define AUD_SDM_CTL0 (SPRD_CODEC_DP_BASE + 0x0010) +#define AUD_SDM_CTL1 (SPRD_CODEC_DP_BASE + 0x0014) +#define AUD_ADC_CTL (SPRD_CODEC_DP_BASE + 0x0018) +#define AUD_LOOP_CTL (SPRD_CODEC_DP_BASE + 0x001C) +#define AUD_AUD_STS0 (SPRD_CODEC_DP_BASE + 0x0020) +#define AUD_INT_CLR (SPRD_CODEC_DP_BASE + 0x0024) +#define AUD_INT_EN (SPRD_CODEC_DP_BASE + 0x0028) +#define AUDIF_FIFO_CTL (SPRD_CODEC_DP_BASE + 0x002C) +#define AUD_DMIC_CTL (SPRD_CODEC_DP_BASE + 0x0030) +#define AUD_ADC1_I2S_CTL (SPRD_CODEC_DP_BASE + 0x0034) +#define AUD_DAC_SDM_L (SPRD_CODEC_DP_BASE + 0x0038) +#define AUD_DAC_SDM_H (SPRD_CODEC_DP_BASE + 0x003C) +#define SPRD_CODEC_DP_END (SPRD_CODEC_DP_BASE + 0x0040) +#define IS_SPRD_CODEC_DP_RANG(reg) (((reg) >= SPRD_CODEC_DP_BASE) && ((reg) < SPRD_CODEC_DP_END)) +#define SPRD_CODEC_AP_BASE (CODEC_AP_BASE) +#define ANA_PMU0 (SPRD_CODEC_AP_BASE + 0x0000) +#define ANA_PMU1 (SPRD_CODEC_AP_BASE + 0x0004) +#define ANA_PMU2 (SPRD_CODEC_AP_BASE + 0x0008) +#define ANA_PMU3 (SPRD_CODEC_AP_BASE + 0x000C) +#define ANA_PMU4 (SPRD_CODEC_AP_BASE + 0x0010) +#define ANA_PMU5 (SPRD_CODEC_AP_BASE + 0x0014) +#define ANA_PMU6 (SPRD_CODEC_AP_BASE + 0x0018) +#define ANA_CDC0 (SPRD_CODEC_AP_BASE + 0x001C) +#define ANA_CDC1 (SPRD_CODEC_AP_BASE + 0x0020) +#define ANA_CDC2 (SPRD_CODEC_AP_BASE + 0x0024) +#define ANA_CDC3 (SPRD_CODEC_AP_BASE + 0x0028) +#define ANA_CDC4 (SPRD_CODEC_AP_BASE + 0x002C) +#define ANA_CDC5 (SPRD_CODEC_AP_BASE + 0x0030) +#define ANA_CDC6 (SPRD_CODEC_AP_BASE + 0x0034) +#define ANA_CDC7 (SPRD_CODEC_AP_BASE + 0x0038) +#define ANA_CDC8 (SPRD_CODEC_AP_BASE + 0x003C) +#define ANA_CDC9 (SPRD_CODEC_AP_BASE + 0x0040) +#define ANA_CDC10 (SPRD_CODEC_AP_BASE + 0x0044) +#define ANA_CDC11 (SPRD_CODEC_AP_BASE + 0x0048) +#define ANA_CDC12 (SPRD_CODEC_AP_BASE + 0x004C) +#define ANA_CDC13 (SPRD_CODEC_AP_BASE + 0x0050) +#define ANA_CDC14 (SPRD_CODEC_AP_BASE + 0x0054) +#define ANA_CDC15 (SPRD_CODEC_AP_BASE + 0x0058) +#define ANA_CDC16 (SPRD_CODEC_AP_BASE + 0x005C) +#define ANA_CDC17 (SPRD_CODEC_AP_BASE + 0x0060) +#define ANA_CDC18 (SPRD_CODEC_AP_BASE + 0x0064) +#define ANA_HDT0 (SPRD_CODEC_AP_BASE + 0x0068) +#define ANA_HDT1 (SPRD_CODEC_AP_BASE + 0x006C) +#define ANA_HDT2 (SPRD_CODEC_AP_BASE + 0x0070) +#define ANA_STS0 (SPRD_CODEC_AP_BASE + 0x0074) +#define ANA_STS1 (SPRD_CODEC_AP_BASE + 0x0078) +#define ANA_STS2 (SPRD_CODEC_AP_BASE + 0x007C) +#define DANGL (SPRD_CODEC_AP_BASE + 0x0080) +#define DANGR (SPRD_CODEC_AP_BASE + 0x0084) +#define DRE (SPRD_CODEC_AP_BASE + 0x0088) +#define DIG_CFG0 (SPRD_CODEC_AP_BASE + 0x008C) +#define DIG_CFG1 (SPRD_CODEC_AP_BASE + 0x0090) +#define DIG_CFG2 (SPRD_CODEC_AP_BASE + 0x0094) +#define DIG_CFG3 (SPRD_CODEC_AP_BASE + 0x0098) +#define DIG_CFG4 (SPRD_CODEC_AP_BASE + 0x009C) +#define DIG_STS0 (SPRD_CODEC_AP_BASE + 0x00A0) +#define DIG_STS1 (SPRD_CODEC_AP_BASE + 0x00A4) +#define SPRD_CODEC_AP_END (SPRD_CODEC_AP_BASE + 0x00A8) +#define IS_SPRD_CODEC_AP_RANG(reg) (((reg) >= SPRD_CODEC_AP_BASE) && ((reg) < SPRD_CODEC_AP_END)) + +#define SPRD_CODEC_IIS1_ID 111 +#endif /* __SPRD_CODEC_H */ diff --git a/sound/soc/sprd/dai/Kconfig b/sound/soc/sprd/dai/Kconfig new file mode 100644 index 00000000..eb68a154 --- /dev/null +++ b/sound/soc/sprd/dai/Kconfig @@ -0,0 +1,35 @@ +menu "SPRD AUDIO DMA" +config SND_SOC_SPRD_AUDIO_DMA + tristate + +config SND_SOC_SPRD_AUDIO_DMA_ENGINE + bool "Use DMA driver based on dmaengine" + depends on DMA_SPRD + default y if DMA_SPRD + help + Say Y if you want to use dma driver based on linux dmaengine. + +config SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + bool "Use IRAM when sound playback" + help + Say Y if you want use IRAM replacment RAM when playback. + sometimes, use IRAM maybe reduce power consumption. + but, it will cause system more busy, becuase the buffer smaller. + +config SND_SOC_SPRD_IRAM_BACKUP + bool "Backup IRAM when sound playback" + depends on SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + help + Say Y if you want backup IRAM when playback. sometimes, + IRAM maybe shared with other modules, + then you need to open this. + +config SND_SOC_SPRD_AUDIO_USE_AON_DMA + bool "Use AON DMA when sound playback" + depends on SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM && DMA_SPRD && SND_SOC_SPRD_AUDIO_DMA_ENGINE + help + Say Y if you want to use AON DMA when playback, sometimes, + AON DMA maybe reduce power consumption. But AON DMA can only + access IRAM, so we must depend on IRAM switch. + +endmenu diff --git a/sound/soc/sprd/dai/Makefile b/sound/soc/sprd/dai/Makefile new file mode 100644 index 00000000..e3daa77f --- /dev/null +++ b/sound/soc/sprd/dai/Makefile @@ -0,0 +1,11 @@ +# SPRD DMA Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai/vbc/ -I$(SPRD_SOUND_TREE)/dai/vaudio/ + +ifneq ($(CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE),) + snd-soc-sprd-audio-dma-objs := sprd-dmaengine-pcm.o +else + snd-soc-sprd-audio-dma-objs := sprd-pcm.o +endif + +obj-$(CONFIG_SND_SOC_SPRD_AUDIO_DMA) += snd-soc-sprd-audio-dma.o diff --git a/sound/soc/sprd/dai/i2s/Kconfig b/sound/soc/sprd/dai/i2s/Kconfig new file mode 100644 index 00000000..76da44c9 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_SPRD_I2S + tristate #"I2S" diff --git a/sound/soc/sprd/dai/i2s/Makefile b/sound/soc/sprd/dai/i2s/Makefile new file mode 100644 index 00000000..c6d03d85 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/Makefile @@ -0,0 +1,6 @@ +# SPRD I2S Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai + +snd-soc-sprd-i2s-objs := i2s.o +obj-$(CONFIG_SND_SOC_SPRD_I2S) += snd-soc-sprd-i2s.o diff --git a/sound/soc/sprd/dai/i2s/i2s.c b/sound/soc/sprd/dai/i2s/i2s.c new file mode 100644 index 00000000..a29819a9 --- /dev/null +++ b/sound/soc/sprd/dai/i2s/i2s.c @@ -0,0 +1,1441 @@ +/* + * sound/soc/sprd/dai/i2s/i2s.c + * + * SPRD SoC CPU-DAI -- SpreadTrum SOC DAI i2s. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" I2S ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include <soc/sprd/hardware.h> + +#include "sprd-asoc-common.h" +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE +#include "sprd-pcm.h" +#else +#include "sprd-dmaengine-pcm.h" +#endif +#include <soc/sprd/i2s.h> + +/* register offset */ +#define IIS_TXD (0x0000) +#define IIS_CLKD (0x0004) +#define IIS_CTRL0 (0x0008) +#define IIS_CTRL1 (0x000C) +#define IIS_CTRL2 (0x0010) +#define IIS_CTRL3 (0x0014) +#define IIS_INT_IEN (0x0018) +#define IIS_INT_CLR (0x001C) +#define IIS_INT_RAW (0x0020) +#define IIS_INT_STS (0x0024) +#define IIS_STS1 (0x0028) +#define IIS_STS2 (0x002C) +#define IIS_STS3 (0x0030) +#define IIS_DSPWAIT (0x0034) +#define IIS_CTRL4 (0x0038) +#define IIS_STS4 (0x003C) + +#define I2S_REG(i2s, offset) ((unsigned long)((i2s)->membase + (offset))) +#define I2S_PHY_REG(i2s, offset) (((unsigned int)(i2s)->memphys + (offset))) + +static unsigned int membase = 0; +static struct i2s_config *dup_config = NULL; + +#define CMD_BUFFER_LENGTH 64 + + +struct i2s_rtx { + int dma_no; +}; +#define DAI_NAME_SIZE 20 +struct i2s_priv { + int id; + int hw_port; + struct device *dev; + struct list_head list; + struct i2s_rtx rx; + struct i2s_rtx tx; + atomic_t open_cnt; + char dai_name[DAI_NAME_SIZE]; + int irq_no; + void __iomem *membase; + unsigned int *memphys; + unsigned int reg_size; + struct i2s_config config; + struct clk *i2s_clk; + struct snd_soc_dai_driver i2s_dai_driver[2]; + int i2s_type; +}; + +static struct sprd_pcm_dma_params i2s_pcm_stereo_out = { + .name = "I2S PCM Stereo out", + .irq_type = BLK_DONE, + .desc = { + .datawidth = WORD_WIDTH, + .src_step = 4, + .des_step = 0, + }, +}; + +static struct sprd_pcm_dma_params i2s_pcm_stereo_in = { + .name = "I2S PCM Stereo in", + .irq_type = BLK_DONE, + .desc = { + .datawidth = WORD_WIDTH, + .src_step = 0, + .des_step = 4, + }, +}; + +/* default pcm config */ +const static struct i2s_config def_pcm_config = { + .hw_port = 0, + .fs = 8000, + .slave_timeout = 0xF11, + .bus_type = PCM_BUS, + .byte_per_chan = I2S_BPCH_16, + .mode = I2S_MASTER, + .lsb = I2S_MSB, + .rtx_mode = I2S_RTX_MODE, + .sync_mode = I2S_SYNC,// I2S_SYNC better! + .lrck_inv = I2S_L_LEFT, + .clk_inv = I2S_CLK_N, + .pcm_bus_mode = I2S_SHORT_FRAME, + .pcm_slot = 0x1, + .pcm_cycle = 1, + .tx_watermark = 12, + .rx_watermark = 20, +}; + +/* default i2s config */ +const static struct i2s_config def_i2s_config = { + .hw_port = 0, + .fs = 32000, + .slave_timeout = 0xF11, + .bus_type = I2S_BUS, + .byte_per_chan = I2S_BPCH_16, + .mode = I2S_SLAVE, + .lsb = I2S_MSB, + .rtx_mode = I2S_RX_MODE, + .sync_mode = I2S_LRCK, + .lrck_inv = I2S_L_LEFT, + .clk_inv = I2S_CLK_N, + .i2s_bus_mode = I2S_MSBJUSTFIED, + .tx_watermark = 12, + .rx_watermark = 20, +}; + +static DEFINE_SPINLOCK(i2s_lock); + +static inline int i2s_reg_read(unsigned long reg) +{ + return __raw_readl((void *__iomem)reg); +} + +static inline void i2s_reg_raw_write(unsigned long reg, int val) +{ + __raw_writel(val, (void *__iomem)reg); +} + +#if 0 +static int i2s_reg_write(unsigned int reg, int val) +{ + spin_lock(&i2s_lock); + i2s_reg_raw_write(reg, val); + spin_unlock(&i2s_lock); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + i2s_reg_read(reg)); + return 0; +} +#endif + + +static inline struct i2s_dai *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +/* + * Returns 1 for change, 0 for no change, or negative error code. + */ +static int i2s_reg_update(unsigned long reg, int val, int mask) +{ + int new, old; + spin_lock(&i2s_lock); + old = i2s_reg_read(reg); + new = (old & ~mask) | (val & mask); + i2s_reg_raw_write(reg, new); + spin_unlock(&i2s_lock); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new, + i2s_reg_read(reg)); + return old != new; +} + +static int i2s_global_disable(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + return arch_audio_i2s_disable(i2s->hw_port); +} + +static int i2s_global_enable(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + arch_audio_i2s_enable(i2s->hw_port); + return arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL); +} + +static int i2s_soft_reset(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s\n", __func__); + return arch_audio_i2s_reset(i2s->hw_port); +} + +static int i2s_calc_clk(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int bit_clk; + int cycle; + int source_clk; + int bit; + int val; + struct clk *clk_parent; + switch (config->fs) { + case 8000: + case 16000: + case 32000: + clk_parent = clk_get(NULL, "clk_128m"); + break; + case 9600: + case 12000: + case 24000: + case 48000: + clk_parent = clk_get(NULL, "clk_76m8"); + break; + default: + pr_err("ERR:I2S Can't Support %d Clock\n", config->fs); + return -ENOTSUPP; + } + + if (IS_ERR(clk_parent)) { + int ret = PTR_ERR(clk_parent); + pr_err("ERR:I2S Get Clock Source Error %d!\n", ret); + return ret; + } + clk_set_parent(i2s->i2s_clk, clk_parent); + source_clk = clk_get_rate(clk_parent); + clk_set_rate(i2s->i2s_clk, source_clk); + clk_put(clk_parent); + + sp_asoc_pr_dbg("I2S Source Clock is %d HZ\n", source_clk); + cycle = (PCM_BUS == config->bus_type) ? (config->pcm_cycle + 1) : 2; + bit = 8 << config->byte_per_chan; + bit_clk = config->fs * cycle * bit; + sp_asoc_pr_dbg("I2S BIT Clock is %d HZ\n", bit_clk); + if (source_clk % (bit_clk << 1)) + return -ENOTSUPP; + val = (source_clk / bit_clk) >> 1; + --val; + return val; +} + +static int i2s_set_clkd(struct i2s_priv *i2s) +{ + int shift = 0; + int mask = 0xFFFF << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CLKD); + sp_asoc_pr_dbg("%s\n", __func__); + val = i2s_calc_clk(i2s); + if (val < 0) + return val; + i2s_reg_update(reg, val, mask); + return 0; +} + +static int i2s_set_clk_m_n(struct i2s_priv *i2s) +{ + /* This will support futher */ + return -ENOTSUPP; +} + +static void i2s_set_bus_type(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(15); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (PCM_BUS == config->bus_type) ? mask : 0, mask); +} + +static void i2s_set_byte_per_channal(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 4; + int mask = 0x3 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->byte_per_chan; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(3); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s mode %d\n", __func__, config->mode); + i2s_reg_update(reg, (I2S_SLAVE == config->mode) ? mask : 0, mask); +} + +static void i2s_set_lsb(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(2); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_LSB == config->lsb) ? mask : 0, mask); +} + +static void i2s_set_rtx_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 6; + int mask = 0x3 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->rtx_mode; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_sync_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(9); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_SYNC == config->sync_mode) ? mask : 0, mask); +} + +static void i2s_set_lrck_invert(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(10); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_L_RIGTH == config->lrck_inv) ? mask : 0, mask); +} + +static void i2s_set_clk_invert(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(11); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_CLK_R == config->clk_inv) ? mask : 0, mask); +} + +static void i2s_set_i2s_bus_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(8); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, (I2S_COMPATIBLE == config->i2s_bus_mode) ? mask : 0, + mask); +} + +static void i2s_set_pcm_bus_mode(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int mask = BIT(8); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s\n", __func__); + i2s_reg_update(reg, + (I2S_SHORT_FRAME == config->pcm_bus_mode) ? mask : 0, + mask); +} + +static void i2s_set_pcm_slot(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x7 << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL2); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->pcm_slot; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_pcm_cycle(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 3; + int mask = 0x7F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL2); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->pcm_cycle; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_rx_watermark(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x1F1F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL3); + sp_asoc_pr_dbg("%s\n", __func__); + /* full watermark */ + val = config->rx_watermark; + /* empty watermark */ + val |= (I2S_FIFO_DEPTH - config->rx_watermark) << 8; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_tx_watermark(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0x1F1F << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL4); + sp_asoc_pr_dbg("%s\n", __func__); + /* empty watermark */ + val = config->tx_watermark << 8; + /* full watermark */ + val |= I2S_FIFO_DEPTH - config->tx_watermark; + i2s_reg_update(reg, val << shift, mask); +} + +static void i2s_set_slave_timeout(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + int shift = 0; + int mask = 0xFFF << shift; + int val = 0; + unsigned long reg = I2S_REG(i2s, IIS_CTRL1); + sp_asoc_pr_dbg("%s\n", __func__); + val = config->slave_timeout; + i2s_reg_update(reg, val << shift, mask); +} + +static int i2s_get_dma_data_width(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + return SHORT_WIDTH; + } + } + return WORD_WIDTH; +} + +static int i2s_get_dma_step(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + return 2; + } + } + return 4; +} + +static int i2s_get_data_position(struct i2s_priv *i2s) +{ + struct i2s_config *config = &i2s->config; + if (PCM_BUS == config->bus_type) { + if (config->byte_per_chan == I2S_BPCH_16) { + if (I2S_SLAVE == config->mode) { + return 2; + } + } + } + return 0; +} + +static int i2s_config_rate(struct i2s_priv *i2s) +{ + int ret = 0; + struct i2s_config *config = &i2s->config; + if (I2S_MASTER == config->mode) { + ret = i2s_set_clkd(i2s); + } + if (ret) { + ret = i2s_set_clk_m_n(i2s); + } + return ret; +} + +static int i2s_config_apply(struct i2s_priv *i2s) +{ + int ret = 0; + struct i2s_config *config = &i2s->config; + sp_asoc_pr_dbg("%s\n", __func__); + + if (I2S_BPCH_8 == config->byte_per_chan) { + pr_err("ERR:The I2S can't Support 8byte Mode in this code\n"); + ret = -ENOTSUPP; + } + + i2s_set_bus_type(i2s); + i2s_set_mode(i2s); + i2s_set_byte_per_channal(i2s); + i2s_set_rtx_mode(i2s); + i2s_set_sync_mode(i2s); + i2s_set_lsb(i2s); + i2s_set_lrck_invert(i2s); + i2s_set_clk_invert(i2s); + i2s_set_rx_watermark(i2s); + i2s_set_tx_watermark(i2s); + if (I2S_SLAVE == config->mode) { + i2s_set_slave_timeout(i2s); + } + if (I2S_BUS == config->bus_type) { + i2s_set_i2s_bus_mode(i2s); + } + if (PCM_BUS == config->bus_type) { + i2s_set_pcm_bus_mode(i2s); + i2s_set_pcm_slot(i2s); + i2s_set_pcm_cycle(i2s); + } + + ret = i2s_config_rate(i2s); + + return ret; +} + +static void i2s_dma_ctrl(struct i2s_priv *i2s, int enable) +{ + int mask = BIT(14); + unsigned long reg = I2S_REG(i2s, IIS_CTRL0); + sp_asoc_pr_dbg("%s Enable = %d\n", __func__, enable); + if (!enable) { + if (atomic_read(&i2s->open_cnt) <= 0) { + i2s_reg_update(reg, 0, mask); + } + } else { + i2s_reg_update(reg, mask, mask); + } +} + +static int i2s_close(struct i2s_priv *i2s) +{ + sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt)); + if (atomic_dec_and_test(&i2s->open_cnt)) { + i2s_soft_reset(i2s); + i2s_global_disable(i2s); + if (!IS_ERR(i2s->i2s_clk)) { + clk_disable_unprepare(i2s->i2s_clk); + clk_put(i2s->i2s_clk); + } + } + return 0; +} + +static int i2s_open(struct i2s_priv *i2s) +{ + int ret = 0; + + sp_asoc_pr_dbg("%s %d\n", __func__, atomic_read(&i2s->open_cnt)); + + atomic_inc(&i2s->open_cnt); + if (atomic_read(&i2s->open_cnt) == 1) { + i2s->i2s_clk = + clk_get(NULL, arch_audio_i2s_clk_name(i2s->hw_port)); + if (IS_ERR(i2s->i2s_clk)) { + ret = PTR_ERR(i2s->i2s_clk); + pr_err("ERR:I2S Get clk Error %d!\n", ret); + return ret; + } + i2s_global_enable(i2s); + i2s_soft_reset(i2s); + i2s_dma_ctrl(i2s, 0); + ret = i2s_config_apply(i2s); + clk_prepare_enable(i2s->i2s_clk); + } + + return ret; +} + +static int i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct i2s_priv *i2s = to_info(dai); + printk("i2s->id %d \n",i2s->id); + dai->ac97_pdata = &i2s->config; + ret = i2s_open(i2s); + if (ret < 0) { + pr_err("ERR:I2S Open Error!\n"); + return ret; + } + + return 0; +} + +static void i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + + sp_asoc_pr_dbg("%s\n", __func__); + + i2s_close(i2s); +} + +static int i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct sprd_pcm_dma_params *dma_data; + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + + sp_asoc_pr_dbg("%s Port %d\n", __func__, i2s->hw_port); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data = &i2s_pcm_stereo_out; + dma_data->channels[0] = i2s->tx.dma_no; + } else { + dma_data = &i2s_pcm_stereo_in; + dma_data->channels[0] = i2s->rx.dma_no; + } + dma_data->desc.datawidth = i2s_get_dma_data_width(i2s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->desc.src_step = i2s_get_dma_step(i2s); + else + dma_data->desc.des_step = i2s_get_dma_step(i2s); + + dma_data->dev_paddr[0] = + I2S_PHY_REG(i2s, IIS_TXD) + i2s_get_data_position(i2s); + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + i2s->config.fs = params_rate(params); + ret = i2s_config_rate(i2s); + if (ret) { + return ret; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + ret = -ENOTSUPP; + pr_err("ERR:I2S Only Supports format S16_LE now!\n"); + break; + } + + if (params_channels(params) > 2) { + ret = -ENOTSUPP; + pr_err("ERR:I2S Can not Supports Grate 2 Channels\n"); + } + + return ret; +} + +static int i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct i2s_config *config = dai->ac97_pdata; + struct i2s_priv *i2s = container_of(config, struct i2s_priv, config); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + i2s_dma_ctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + i2s_dma_ctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct snd_soc_dai_ops sprd_i2s_dai_ops = { + .startup = i2s_startup, + .shutdown = i2s_shutdown, + .hw_params = i2s_hw_params, + .trigger = i2s_trigger, +}; +static void i2s_config_setting(int index,int value,struct i2s_config *config) +{ + pr_debug("i2s_config_setting i2s index %d,value %d \n",index,value); + if (config == NULL) { + pr_err("i2s_config_setting config is NULL,error! \n"); + return; + } + switch (index) { + case FS: + if (value >= SAMPLATE_MIN && value <= SAMPLATE_MAX) + config->fs = value; + break; + case HW_PROT: + if (value >= 0 && value <= 3) + config->hw_port = value; + break; + case SLAVE_TIMEOUT: + config->slave_timeout = value; + break; + case BUS_TYPE: + if (value == I2S_BUS || value == PCM_BUS) + config->bus_type = value; + break; + case BYTE_PER_CHAN: + if (value == I2S_BPCH_8 || value == I2S_BPCH_16 || value == I2S_BPCH_32) + config->byte_per_chan = value; + break; + case MODE: + if (value == I2S_MASTER || value == I2S_SLAVE) + config->mode = value; + break; + case LSB: + if (value == I2S_MSB || value == I2S_LSB) + config->lsb = value; + break; + case TRX_MODE: + if (value >= I2S_RTX_DIS && value <= I2S_RTX_MODE) + config->rtx_mode = value; + break; + case LRCK_INV: + if (value == I2S_L_LEFT || value == I2S_L_RIGTH) + config->lrck_inv = value; + break; + case SYNC_MODE: + if (value == I2S_SYNC || value == I2S_LRCK) + config->sync_mode = value; + break; + case CLK_INV: + if (value == I2S_CLK_N || value == I2S_CLK_R) + config->clk_inv = value; + break; + case I2S_BUS_MODE: + if (value == I2S_MSBJUSTFIED || value == I2S_COMPATIBLE) + config->i2s_bus_mode = value; + break; + case PCM_BUS_MODE: + if (value == I2S_SHORT_FRAME || value == I2S_LONG_FRAME) + config->pcm_bus_mode = value; + break; + case PCM_SLOT: + if (value >= 1 && value <= 4) + config->pcm_slot = value; + break; + case PCM_CYCLE: + if (value >= 0 && value <= 127) + config->pcm_cycle = value; + break; + case TX_WATERMARK: + if (value >= 0 && value <= I2S_FIFO_DEPTH) + config->tx_watermark = value; + break; + case RX_WATERMARK: + if (value >= 0 && value <= I2S_FIFO_DEPTH) + config->rx_watermark = value; + break; + default: + pr_err("i2s echo cmd is invalide \n"); + break; + } +} + +static int i2s_config_getting(int index,struct i2s_config *config) +{ + pr_debug("i2s_config_getting i2s index %d\n",index); + if (config == NULL) { + pr_err("i2s_config_getting config is NULL,error\n"); + return -1; + } + switch (index) { + case FS: + return config->fs; + case HW_PROT: + return config->hw_port; + case SLAVE_TIMEOUT: + return config->slave_timeout; + case BUS_TYPE: + return config->bus_type; + case BYTE_PER_CHAN: + return config->byte_per_chan; + case MODE: + return config->mode; + case LSB: + return config->lsb; + case TRX_MODE: + return config->rtx_mode; + case LRCK_INV: + return config->lrck_inv; + case SYNC_MODE: + return config->sync_mode; + case CLK_INV: + return config->clk_inv; + case I2S_BUS_MODE: + return config->i2s_bus_mode; + case PCM_BUS_MODE: + return config->pcm_bus_mode; + case PCM_SLOT: + return config->pcm_slot; + case PCM_CYCLE: + return config->pcm_cycle; + case TX_WATERMARK: + return config->tx_watermark; + case RX_WATERMARK: + return config->rx_watermark; + default: + pr_err("i2s config index is invalide \n"); + return -1; + } +} + +static int get_index(char *line_first,char *line_end) +{ + char *line_index = line_first; + char *line_first_dummy = line_first; + if (line_first == NULL || line_end == NULL) + return -1; + for (line_first;line_first < line_end;line_first++) { + if (*line_first >= '0' && *line_first <= '9') { + *line_index = *line_first; + line_index++; + } + } + *line_index = '\0'; + return simple_strtoul(line_first_dummy, NULL, 10); +} + +static int get_index_value(char *line_first) +{ + char *line_index_value = line_first; + char *line_first_dummy = line_first; + if (line_first == NULL) + return -1; + pr_debug("i2s get_index_value %s\n",line_first); + for (line_first;;line_first++) { + if (*line_first == '\0') + break; + if (*line_first >= '0' && *line_first <= '9') { + *line_index_value = *line_first; + line_index_value ++; + } + } + *line_index_value = '\0'; + return simple_strtoul(line_first_dummy, NULL, 10); +} + +void i2s_debug_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int index; + int index_value; + char *line = NULL; + char *sym_eq = NULL; + if (dup_config == NULL) { + pr_err("i2s_debug_write failed! \n"); + return; + } + line = kzalloc(CMD_BUFFER_LENGTH,GFP_KERNEL); + if (!line) { + pr_err("malloc line buffer failed! \n"); + return; + } + if (!snd_info_get_line(buffer, line, CMD_BUFFER_LENGTH)) { + pr_debug("i2s: input line %s\n",line); + sym_eq = strchr(line,'='); + if (sym_eq != NULL) { + index = get_index(line,sym_eq); + pr_debug("i2s: index %d\n",index); + index_value = get_index_value(++sym_eq); + pr_debug("i2s: index_value %d \n",index_value); + if (index >= 0 && index_value >= 0) + i2s_config_setting(index,index_value,dup_config); + } else if (strstr(line,"pcm")) { + memcpy(dup_config,&def_pcm_config,sizeof(def_pcm_config)); + } else if (strstr(line,"i2s")) { + memcpy(dup_config,&def_i2s_config,sizeof(def_i2s_config)); + } else { + pr_err("i2s:echo str fmt not right \n"); + } + } else { + pr_err("i2s echo error \n"); + } + kfree(line); + return; +} + +void i2s_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + if (dup_config == NULL) { + snd_iprintf(buffer, "\n\n dup_config is NUll,error!\n"); + return; + } + snd_iprintf(buffer, "\n\n using examples :\n"); + snd_iprintf(buffer, " 1)echo 0=33 > i2s-debug \n"); + snd_iprintf(buffer, " 2)echo i2s > i2s-debug \n"); + snd_iprintf(buffer, " 3)echo pcm >i2s-debug \n\n"); + snd_iprintf(buffer, " 0 fs 0d:%d\n 1 hw_port 0d:%d\n 2 slave_timeout 0x%x\n", + dup_config->fs,dup_config->hw_port,dup_config->slave_timeout); + snd_iprintf(buffer, " 3 bus_type(****tip:bus_type 0 is iis,1 is pcm****) 0x%x\n 4 byte_per_chan 0x%x\n", + dup_config->bus_type,dup_config->byte_per_chan); + snd_iprintf(buffer, " 5 mode(****tip:mode 0 is master,1 is slave****) 0x%x\n 6 lsb 0x%x\n", + dup_config->mode,dup_config->lsb); + snd_iprintf(buffer, " 7 rtx_mode 0x%x\n 8 lrck_inv 0x%x\n", + dup_config->rtx_mode,dup_config->lrck_inv); + snd_iprintf(buffer, " 9 sync_mode 0x%x\n 10 clk_inv 0x%x\n", + dup_config->sync_mode,dup_config->clk_inv); + snd_iprintf(buffer, " 11 i2s_bus_mode 0x%x\n 12 pcm_bus_mode 0x%x\n", + dup_config->i2s_bus_mode,dup_config->pcm_bus_mode); + snd_iprintf(buffer, " 13 pcm_slot 0x%x\n 14 pcm_cycle 0x%x\n", + dup_config->pcm_slot,dup_config->pcm_cycle); + snd_iprintf(buffer, " 15 tx_watermark 0d:%d\n 16 rx_watermark 0d:%d\n\n", + dup_config->tx_watermark,dup_config->rx_watermark); +} + +static inline int iis_reg_read(unsigned int reg) +{ + return __raw_readl((void *__iomem)reg); +} + +void i2s_register_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int reg; + if (membase == 0) { + snd_iprintf(buffer,"\n\n i2s membase is NULL \n"); + return; + } + pr_debug("i2s membase 0x%x \n",membase); + snd_iprintf(buffer, "i2s register dump\n"); + for (reg = IIS_TXD + membase; reg <= IIS_STS4 + membase; reg += 0x10) { + snd_iprintf(buffer, "0x%08x | 0x%08x 0x%08x 0x%08x 0x%08x\n", + (reg - IIS_TXD -membase) + , iis_reg_read(reg + 0x00) + , iis_reg_read(reg + 0x04) + , iis_reg_read(reg + 0x08) + , iis_reg_read(reg + 0x0C) + ); + } +} + +int i2s_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + if (dup_config == NULL) { + pr_err("i2s_config_get return \n"); + return 0; + } + ucontrol->value.integer.value[0] = i2s_config_getting(id,dup_config); + pr_debug("i2s_config_get return value %d,id %d\n",ucontrol->value.integer.value[0],id); + return 0; +} + +int i2s_config_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + int id = FUN_REG(mc->reg); + if (dup_config == NULL) { + pr_err("i2s_config_set return \n"); + return 0; + } + i2s_config_setting(id, ucontrol->value.integer.value[0],dup_config); + return 0; +} + +static const struct snd_soc_component_driver sprd_i2s_component = { + .name = "i2s", +}; + +static int i2s_config_from_node(struct device_node *node, struct i2s_priv *i2s) +{ + if (!of_find_property(node, "sprd,config", NULL)) { + i2s->config = def_i2s_config; + sp_asoc_pr_dbg("Use I2S default config!\n"); + } else { + u32 val; + struct device_node *config_node; + config_node = of_parse_phandle(node, "sprd,config", 0); + if (of_find_property(config_node, "sprd,def_pcm_config", NULL)) { + i2s->config = def_pcm_config; + sp_asoc_pr_dbg("Use PCM default config!\n"); + } else { + i2s->config = def_i2s_config; + sp_asoc_pr_dbg("Use I2S default config!\n"); + } + + if (!of_property_read_u32 + (config_node, "sprd,_hw_port", &val)) { + i2s->config.hw_port = val; + sp_asoc_pr_dbg("Change _hw_port to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,fs", &val)) { + i2s->config.fs = val; + sp_asoc_pr_dbg("Change fs to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,bus_type", &val)) { + if (val){ + i2s->config.bus_type = PCM_BUS; + } else { + i2s->config.bus_type = I2S_BUS; + } + sp_asoc_pr_dbg("Change bus_type to %d!\n", val); + } + + if (!of_property_read_u32 + (config_node, "sprd,rtx_mode", &val)) { + i2s->config.rtx_mode = val; + sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val); + } + + if (!of_property_read_u32 + (config_node, "sprd,slave_timeout", &val)) { + i2s->config.slave_timeout = val; + sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,byte_per_chan", &val)) { + i2s->config.byte_per_chan = val; + sp_asoc_pr_dbg("Change byte per channal to %d!\n", val); + } + if (!of_property_read_u32(config_node, "sprd,slave_mode", &val)) { + if (val) { + i2s->config.mode = I2S_SLAVE; + } else { + i2s->config.mode = I2S_MASTER; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Slave" : "Master"); + } + if (!of_property_read_u32(config_node, "sprd,lsb", &val)) { + if (val) { + i2s->config.lsb = I2S_LSB; + } else { + i2s->config.lsb = I2S_MSB; + } + sp_asoc_pr_dbg("Change to %s!\n", val ? "LSB" : "MSB"); + } + if (!of_property_read_u32(config_node, "sprd,lrck", &val)) { + if (val) { + i2s->config.sync_mode = I2S_LRCK; + } else { + i2s->config.sync_mode = I2S_SYNC; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "LRCK" : "SYNC"); + } + if (!of_property_read_u32 + (config_node, "sprd,low_for_left", &val)) { + if (val) { + i2s->config.lrck_inv = I2S_L_LEFT; + } else { + i2s->config.lrck_inv = I2S_L_RIGTH; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Low for Left" : "Low for Right"); + } + if (!of_property_read_u32(config_node, "sprd,clk_inv", &val)) { + if (val) { + i2s->config.clk_inv = I2S_CLK_R; + } else { + i2s->config.clk_inv = I2S_CLK_N; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "CLK INV" : "CLK Normal"); + } + if (!of_property_read_u32 + (config_node, "sprd,i2s_compatible", &val)) { + if (val) { + i2s->config.i2s_bus_mode = I2S_COMPATIBLE; + } else { + i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Compatible" : "MSBJustfied"); + } + if (!of_property_read_u32 + (config_node, "sprd,pcm_short_frame", &val)) { + if (val) { + i2s->config.pcm_bus_mode = I2S_SHORT_FRAME; + } else { + i2s->config.pcm_bus_mode = I2S_LONG_FRAME; + } + sp_asoc_pr_dbg("Change to %s!\n", + val ? "Short Frame" : "Long Frame"); + } + if (!of_property_read_u32(config_node, "sprd,pcm_slot", &val)) { + i2s->config.pcm_slot = val; + sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val); + } + if (!of_property_read_u32(config_node, "sprd,pcm_cycle", &val)) { + i2s->config.pcm_cycle = val; + sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,tx_watermark", &val)) { + i2s->config.tx_watermark = val; + sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val); + } + if (!of_property_read_u32 + (config_node, "sprd,rx_watermark", &val)) { + i2s->config.rx_watermark = val; + sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val); + } + of_node_put(config_node); + } + return 0; +} + +static int i2s_drv_probe(struct platform_device *pdev) +{ + int ret; + struct i2s_priv *i2s; + struct resource *res; + struct device_node *node = pdev->dev.of_node; + + sp_asoc_pr_dbg("%s\n", __func__); + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_priv), GFP_KERNEL); + if (!i2s) { + pr_err("ERR:No memery!\n"); + return -ENOMEM; + } + i2s->dev = &pdev->dev; + if (node) { + u32 val[2]; + if (of_property_read_u32(node, "sprd,id", &val[0])) { + pr_err("ERR:Must give me the id!\n"); + goto out; + } + i2s->id = val[0]; + if (of_property_read_u32(node, "sprd,hw_port", &val[0])) { + pr_err("ERR:Must give me the hw_port!\n"); + goto out; + } + i2s->hw_port = val[0]; + if (of_property_read_u32_array(node, "sprd,base", &val[0], 2)) { + pr_err("ERR:Must give me the base address!\n"); + goto out; + } + i2s->memphys = (unsigned int *)val[0]; + i2s->reg_size= (unsigned int)val[1]; + i2s->membase = (void __iomem *)ioremap_nocache(i2s->memphys, i2s->reg_size); + if (!i2s->membase) { + pr_err("ERR:i2s reg address ioremap_nocache error !\n"); + return -EINVAL; + } + if (of_property_read_u32(node, "sprd,dma_rx_no", &val[0])) { + pr_err("ERR:Must give me the dma rx number!\n"); + goto out; + } + i2s->rx.dma_no = val[0]; + if (of_property_read_u32(node, "sprd,dma_tx_no", &val[0])) { + pr_err("ERR:Must give me the dma tx number!\n"); + goto out; + } + i2s->tx.dma_no = val[0]; + + const char *dai_name = NULL; + if (of_property_read_string(node, "sprd,dai_name", &dai_name)) { + pr_err("ERR:Must give me the dai_name!\n"); + //goto out; + } + if (dai_name != NULL) + strcpy(i2s->dai_name,dai_name); + printk("dt version i2s->dai_name %s \n",i2s->dai_name); + ret = i2s_config_from_node(node, i2s); + + const char * config_type = NULL; + if (of_property_read_string(node, "sprd,config_type", &config_type)) { + i2s->config = def_pcm_config; + i2s->i2s_type = 0; + printk("warning:Must give me the config_type! default is pcm\n"); + } + printk("i2s config_type %s \n",config_type); + + if (config_type != NULL){ + if (strcmp(config_type,"i2s") == 0){ + i2s->config = def_i2s_config; + i2s->i2s_type = 1; + } else if (strcmp(config_type,"pcm") == 0) { + i2s->config = def_pcm_config; + i2s->i2s_type = 0; + } else { + pr_err("ERR:config_type must be pcm or i2s !\n"); + } + } + + if (!of_property_read_u32 + (node, "sprd,_hw_port", &val[0])) { + i2s->config.hw_port = val[0]; + sp_asoc_pr_dbg("Change _hw_port to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,fs", &val[0])) { + i2s->config.fs = val[0]; + sp_asoc_pr_dbg("Change fs to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,bus_type", &val[0])) { + if (val[0]){ + i2s->config.bus_type = PCM_BUS; + } else { + i2s->config.bus_type = I2S_BUS; + } + sp_asoc_pr_dbg("Change bus_type to %d!\n", val[0]); + } + + if (!of_property_read_u32 + (node, "sprd,rtx_mode", &val[0])) { + i2s->config.rtx_mode = val[0]; + sp_asoc_pr_dbg("Change rtx_mode to %d!\n", val[0]); + } + + if (!of_property_read_u32 + (node, "sprd,slave_timeout", &val[0])) { + i2s->config.slave_timeout = val[0]; + sp_asoc_pr_dbg("Change slave_timeout to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,byte_per_chan", &val[0])) { + i2s->config.byte_per_chan = val[0]; + sp_asoc_pr_dbg("Change byte per channal to %d!\n", val[0]); + } + if (!of_property_read_u32(node, "sprd,slave_mode", &val[0])) { + if (val[0]) { + i2s->config.mode = I2S_SLAVE; + } else { + i2s->config.mode = I2S_MASTER; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Slave" : "Master"); + } + if (!of_property_read_u32(node, "sprd,lsb", &val[0])) { + if (val[0]) { + i2s->config.lsb = I2S_LSB; + } else { + i2s->config.lsb = I2S_MSB; + } + sp_asoc_pr_dbg("Change to %s!\n", val[0] ? "LSB" : "MSB"); + } + if (!of_property_read_u32(node, "sprd,lrck", &val[0])) { + if (val[0]) { + i2s->config.sync_mode = I2S_LRCK; + } else { + i2s->config.sync_mode = I2S_SYNC; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "LRCK" : "SYNC"); + } + if (!of_property_read_u32 + (node, "sprd,low_for_left", &val[0])) { + if (val[0]) { + i2s->config.lrck_inv = I2S_L_LEFT; + } else { + i2s->config.lrck_inv = I2S_L_RIGTH; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Low for Left" : "Low for Right"); + } + if (!of_property_read_u32(node, "sprd,clk_inv", &val[0])) { + if (val[0]) { + i2s->config.clk_inv = I2S_CLK_R; + } else { + i2s->config.clk_inv = I2S_CLK_N; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "CLK INV" : "CLK Normal"); + } + if (i2s->i2s_type){ + if (!of_property_read_u32 + (node, "sprd,i2s_compatible", &val[0])) { + if (val[0]) { + i2s->config.i2s_bus_mode = I2S_COMPATIBLE;//I2S_SHORT_FRAME + } else { + i2s->config.i2s_bus_mode = I2S_MSBJUSTFIED;//I2S_LONG_FRAME + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Compatible" : "MSBJustfied"); + } + } + if (!of_property_read_u32 + (node, "sprd,pcm_short_frame", &val[0])) { + if (val[0]) { + i2s->config.pcm_bus_mode = I2S_SHORT_FRAME; + } else { + i2s->config.pcm_bus_mode = I2S_LONG_FRAME; + } + sp_asoc_pr_dbg("Change to %s!\n", + val[0] ? "Short Frame" : "Long Frame"); + } + if (!i2s->i2s_type) { + if (!of_property_read_u32(node, "sprd,pcm_slot", &val[0])) { + i2s->config.pcm_slot = val[0]; + sp_asoc_pr_dbg("Change PCM Slot to 0x%x!\n", val[0]); + } + if (!of_property_read_u32(node, "sprd,pcm_cycle", &val[0])) { + i2s->config.pcm_cycle = val[0]; + sp_asoc_pr_dbg("Change PCM Cycle to %d!\n", val[0]); + } + } + if (!of_property_read_u32 + (node, "sprd,tx_watermark", &val[0])) { + i2s->config.tx_watermark = val[0]; + sp_asoc_pr_dbg("Change TX Watermark to %d!\n", val[0]); + } + if (!of_property_read_u32 + (node, "sprd,rx_watermark", &val[0])) { + i2s->config.rx_watermark = val[0]; + sp_asoc_pr_dbg("Change RX Watermark to %d!\n", val[0]); + } + } else { + i2s->config = + *((struct i2s_config *)(dev_get_platdata(&pdev->dev))); + i2s->id = pdev->id; + snprintf(i2s->dai_name,DAI_NAME_SIZE,"%s%d","i2s_bt_sco",i2s->id); + printk("not bt version i2s->dai_name %s \n",i2s->dai_name); + i2s->hw_port = i2s->config.hw_port; + sp_asoc_pr_dbg("i2s.%d(%d) default fs is (%d)\n", i2s->id, + i2s->hw_port, i2s->config.fs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->membase = (void __iomem *)res->start; + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + i2s->memphys = (unsigned int *)res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + i2s->tx.dma_no = res->start; + i2s->rx.dma_no = res->end; + } + + i2s->i2s_dai_driver[0].id = I2S_MAGIC_ID; + i2s->i2s_dai_driver[0].name = i2s->dai_name; + printk("i2s->i2s_dai_driver[0].name %s \n",i2s->i2s_dai_driver[0].name); + i2s->i2s_dai_driver[0].playback.channels_min = 1; + i2s->i2s_dai_driver[0].playback.channels_max = 2; + i2s->i2s_dai_driver[0].playback.rates = SNDRV_PCM_RATE_CONTINUOUS; + i2s->i2s_dai_driver[0].playback.rate_max = 96000; + i2s->i2s_dai_driver[0].playback.formats = SNDRV_PCM_FMTBIT_S16_LE; + + i2s->i2s_dai_driver[0].capture.channels_min = 1; + i2s->i2s_dai_driver[0].capture.channels_max = 2; + i2s->i2s_dai_driver[0].capture.rates = SNDRV_PCM_RATE_CONTINUOUS; + i2s->i2s_dai_driver[0].capture.rate_max = 96000; + i2s->i2s_dai_driver[0].capture.formats = SNDRV_PCM_FMTBIT_S16_LE; + i2s->i2s_dai_driver[0].ops = &sprd_i2s_dai_ops; + + i2s->i2s_dai_driver[1].id = I2S_MAGIC_ID + 1; + i2s->i2s_dai_driver[1].name = "i2s_bt_sco_dummy"; + printk("i2s->i2s_dai_driver[1].name %s \n",i2s->i2s_dai_driver[1].name); + + arch_audio_i2s_switch(i2s->hw_port, AUDIO_TO_ARM_CTRL); + sp_asoc_pr_dbg("membase = 0x%x memphys = 0x%x\n", (int)i2s->membase, + (int)i2s->memphys); + sp_asoc_pr_dbg("DMA Number tx = %d rx = %d\n", i2s->tx.dma_no, + i2s->rx.dma_no); + + platform_set_drvdata(pdev, i2s); + + atomic_set(&i2s->open_cnt, 0); + + ret = + snd_soc_register_component(&pdev->dev, &sprd_i2s_component, + i2s->i2s_dai_driver, ARRAY_SIZE(i2s->i2s_dai_driver)); + sp_asoc_pr_dbg("return %i\n", ret); + + if (strcmp(i2s->dai_name,"i2s_bt_sco0") == 0) { + dup_config = &(i2s->config); + membase = i2s->membase; + } + return ret; +out: + devm_kfree(&pdev->dev,i2s); + return -EINVAL; +} + +static int i2s_drv_remove(struct platform_device *pdev) +{ + struct i2s_priv *i2s; + i2s = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id i2s_of_match[] = { + {.compatible = "sprd,i2s",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, i2s_of_match); +#endif + +static struct platform_driver i2s_driver = { + .driver = { + .name = "i2s", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i2s_of_match), + }, + + .probe = i2s_drv_probe, + .remove = i2s_drv_remove, +}; + +module_platform_driver(i2s_driver); + +MODULE_DESCRIPTION("SPRD ASoC I2S CUP-DAI driver"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cpu-dai:i2s"); diff --git a/sound/soc/sprd/dai/sprd-dmaengine-pcm.c b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c new file mode 100644 index 00000000..548601f8 --- /dev/null +++ b/sound/soc/sprd/dai/sprd-dmaengine-pcm.c @@ -0,0 +1,1069 @@ +/* + * sound/soc/sprd/dai/sprd-dmaengine-pcm.c + * + * SpreadTrum DMA for the pcm stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" PCM ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include <soc/sprd/sprd-audio.h> +#include "sprd-asoc-common.h" +#include "sprd-dmaengine-pcm.h" +#include "vaudio.h" +#include <soc/sprd/i2s.h> +#include "dfm.h" + +#ifndef DMA_LINKLIST_CFG_NODE_SIZE +#define DMA_LINKLIST_CFG_NODE_SIZE (sizeof(struct sprd_dma_cfg)) +#endif + +struct sprd_dma_callback_data { + struct snd_pcm_substream *substream; + struct dma_chan *dma_chn; +}; + +struct sprd_runtime_data { + int dma_addr_offset; + struct sprd_pcm_dma_params *params; + struct dma_chan *dma_chn[2]; + struct sprd_dma_cfg *dma_cfg_array; + struct sprd_dma_callback_data *dma_pdata; + dma_addr_t dma_cfg_phy[2]; + void *dma_cfg_virt[2]; + struct dma_async_tx_descriptor *dma_tx_des[2]; + dma_cookie_t cookie[2]; + int int_pos_update[2]; + int burst_len; + int hw_chan; + int dma_pos_pre[2]; + int interleaved; + int cb_called; + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + int buffer_in_iram; +#endif +#ifdef CONFIG_SND_VERBOSE_PROCFS + struct snd_info_entry *proc_info_entry; +#endif +}; + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM +#define SPRD_AUDIO_DMA_NODE_SIZE (1024) +#endif + +static const struct snd_pcm_hardware sprd_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + /* 16bits, stereo-2-channels */ + .period_bytes_min = VBC_FIFO_FRAME_NUM * 4, + /* non limit */ + .period_bytes_max = VBC_FIFO_FRAME_NUM * 4 * 100, + .periods_min = 1, + /* non limit */ + .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE, + .buffer_bytes_max = VBC_BUFFER_BYTES_MAX, +}; + +static const struct snd_pcm_hardware sprd_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + /* 16bits, stereo-2-channels */ + .period_bytes_min = 8 * 2, + /* non limit */ + .period_bytes_max = 32 * 2 * 100, + .periods_min = 1, + /* non limit */ + .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE, + .buffer_bytes_max = I2S_BUFFER_BYTES_MAX, +}; + +atomic_t lightsleep_refcnt; + +int sprd_lightsleep_disable(const char *id, int disalbe) + __attribute__ ((weak, alias("__sprd_lightsleep_disable"))); + +static int __sprd_lightsleep_disable(const char *id, int disable) +{ + sp_asoc_pr_dbg("NO lightsleep control function %d\n", disable); + return 0; +} + +static inline int sprd_is_vaudio(struct snd_soc_dai *cpu_dai) +{ + return ((cpu_dai->driver->id == VAUDIO_MAGIC_ID) + || (cpu_dai->driver->id == VAUDIO_MAGIC_ID + 1)); +} + +static inline int sprd_is_i2s(struct snd_soc_dai *cpu_dai) +{ + return (cpu_dai->driver->id == I2S_MAGIC_ID); +} + +static inline int sprd_is_dfm(struct snd_soc_dai *cpu_dai) +{ + return (cpu_dai->driver->id == DFM_MAGIC_ID); +} + +static inline const char *sprd_dai_pcm_name(struct snd_soc_dai *cpu_dai) +{ + if (sprd_is_i2s(cpu_dai)) { + return "I2S"; + } else if (sprd_is_vaudio(cpu_dai)) { + return "VAUDIO"; + } else if (sprd_is_dfm(cpu_dai)) { + return "DFM"; + } + return "VBC"; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP +static char *s_mem_for_iram = 0; +#endif +static char *s_iram_remap_base = 0; + +static int sprd_buffer_iram_backup(void) +{ +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + void __iomem *iram_start; + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram); +#endif + if (!s_iram_remap_base) { + s_iram_remap_base = + ioremap_nocache(SPRD_IRAM_ALL_PHYS, SPRD_IRAM_ALL_SIZE); + } +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + if (!s_mem_for_iram) { + s_mem_for_iram = kzalloc(SPRD_IRAM_ALL_SIZE, GFP_KERNEL); + } else { + sp_asoc_pr_dbg("IRAM is Backup, Be Careful use IRAM!\n"); + return 0; + } + if (!s_mem_for_iram) { + pr_err("ERR:IRAM Backup Error!\n"); + return -ENOMEM; + } + iram_start = (void __iomem *)(s_iram_remap_base); + memcpy_fromio(s_mem_for_iram, iram_start, SPRD_IRAM_ALL_SIZE); +#endif + return 0; +} + +static int sprd_buffer_iram_restore(void) +{ +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + void __iomem *iram_start; + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram); + if (!s_mem_for_iram) { + pr_err("ERR:IRAM not Backup\n"); + return 0; + } + iram_start = (void __iomem *)(s_iram_remap_base); + memcpy_toio(iram_start, s_mem_for_iram, SPRD_IRAM_ALL_SIZE); + kfree(s_mem_for_iram); + s_mem_for_iram = 0; +#endif + return 0; +} +#endif + +static inline int sprd_pcm_is_interleaved(struct snd_pcm_runtime *runtime) +{ + return (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); +} + +#define PCM_DIR_NAME(stream) (stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Captrue") + +static int sprd_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + struct sprd_runtime_data *rtd; + struct i2s_config *config; + int burst_len; + int hw_chan; + int ret; + + sp_asoc_pr_info("%s Open %s\n", sprd_dai_pcm_name(srtd->cpu_dai), + PCM_DIR_NAME(substream->stream)); + + if (sprd_is_i2s(srtd->cpu_dai)) { + snd_soc_set_runtime_hwparams(substream, &sprd_i2s_pcm_hardware); + config = srtd->cpu_dai->ac97_pdata; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + burst_len = I2S_FIFO_DEPTH - config->tx_watermark; + } else { + burst_len = config->rx_watermark; + } + burst_len <<= config->byte_per_chan; + hw_chan = 1; + } else { + snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware); + burst_len = (VBC_FIFO_FRAME_NUM * 4); + hw_chan = 2; + } + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + burst_len); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + burst_len); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + ret = -ENOMEM; + rtd = kzalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + goto out; + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + runtime->private_data = rtd; + ret = 0; + goto out; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (sprd_is_i2s(srtd->cpu_dai) + || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + && 0 == sprd_buffer_iram_backup())) { +#endif + rtd->dma_cfg_virt[0] = + (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + &rtd->dma_cfg_phy[0], GFP_KERNEL); + if (!rtd->dma_cfg_virt[0]) + goto err2; + rtd->dma_cfg_virt[1] = + (void *)dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + &rtd->dma_cfg_phy[1], GFP_KERNEL); + if (!rtd->dma_cfg_virt[1]) + goto err3; + if (atomic_inc_return(&lightsleep_refcnt) == 1) + sprd_lightsleep_disable("audio", 1); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } else { + runtime->hw.periods_max = + SPRD_AUDIO_DMA_NODE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE; + runtime->hw.buffer_bytes_max = + SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE); + rtd->buffer_in_iram = 1; + rtd->dma_cfg_virt[0] = s_iram_remap_base + runtime->hw.buffer_bytes_max; + rtd->dma_cfg_phy[0] = SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max; + rtd->dma_cfg_virt[1] = ((unsigned long)(rtd->dma_cfg_virt[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg))) + 15) & (~0xf); + rtd->dma_cfg_phy[1] = ((unsigned int)(rtd->dma_cfg_phy[0] + runtime->hw.periods_max*(sizeof(struct sprd_dma_cfg)))+ 15) & (~0xf); + } +#endif + rtd->dma_cfg_array = + (struct sprd_dma_cfg *)kzalloc(hw_chan * runtime->hw.periods_max * sizeof(struct sprd_dma_cfg),GFP_KERNEL); + if (!rtd->dma_cfg_array) + goto err4; + + rtd->dma_pdata = + (struct sprd_dma_callback_data *)kzalloc(hw_chan * sizeof(struct sprd_dma_callback_data),GFP_KERNEL); + if (!rtd->dma_pdata) + goto err5; + + rtd->dma_chn[0] = rtd->dma_chn[1] = NULL; + rtd->dma_tx_des[0] = rtd->dma_tx_des[1] = NULL; + rtd->cookie[0] = rtd->cookie[1] = 0; + + rtd->burst_len = burst_len; + rtd->hw_chan = hw_chan; + + runtime->private_data = rtd; + ret = 0; + goto out; + +err5: + pr_err("ERR:dma_pdata alloc failed!\n"); + kfree(rtd->dma_cfg_array); +err4: + pr_err("ERR:dma_cfg_array alloc failed!\n"); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (!rtd->buffer_in_iram) +#endif + dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]); +err3: + pr_err("ERR:dma_cfg_virt[1] alloc failed!\n"); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (!rtd->buffer_in_iram) +#endif + dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]); +err2: + pr_err("ERR:dma_cfg_virt[0] alloc failed!\n"); + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (rtd->buffer_in_iram) + sprd_buffer_iram_restore(); +#endif + kfree(rtd); + if (!atomic_dec_return(&lightsleep_refcnt)) + sprd_lightsleep_disable("audio", 0); +out: + return ret; +} + +static int sprd_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + + sp_asoc_pr_info("%s Close %s\n", sprd_dai_pcm_name(srtd->cpu_dai), + PCM_DIR_NAME(substream->stream)); + + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + kfree(rtd); + return 0; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (rtd->buffer_in_iram) + sprd_buffer_iram_restore(); + else { +#endif + if (!atomic_dec_return(&lightsleep_refcnt)) + sprd_lightsleep_disable("audio", 0); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } +#endif + kfree(rtd->dma_pdata); + kfree(rtd->dma_cfg_array); + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (!rtd->buffer_in_iram) { +#endif + dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + rtd->dma_cfg_virt[1], rtd->dma_cfg_phy[1]); + dma_free_coherent(substream->pcm->card->dev, runtime->hw.periods_max * sizeof(struct sprd_dma_cfg), + rtd->dma_cfg_virt[0], rtd->dma_cfg_phy[0]); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } +#endif + kfree(rtd); + + return 0; +} + +static void sprd_pcm_dma_buf_done(void *data) +{ + struct sprd_dma_callback_data *dma_cb_data = (struct sprd_dma_callback_data*)data; + struct snd_pcm_runtime *runtime = dma_cb_data->substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + int i = 0; + + if (!rtd->cb_called) { + rtd->cb_called = 1; + sp_asoc_pr_info("DMA Callback CALL\n"); + } + + if (rtd->hw_chan == 1) + goto irq_fast; + + for (i = 0; i < 2; i++) { + if (dma_cb_data->dma_chn == rtd->dma_chn[i]) { + rtd->int_pos_update[i] = 1; + + if (rtd->dma_chn[1 - i]) { + if (rtd->int_pos_update[1 - i]) + goto irq_ready; + } else { + goto irq_ready; + } + } + } + goto irq_ret; +irq_ready: + rtd->int_pos_update[0] = 0; + rtd->int_pos_update[1] = 0; +irq_fast: + snd_pcm_period_elapsed(dma_cb_data->substream); +irq_ret: + return; +} + +/* + * proc interface + */ + +//#ifdef CONFIG_SND_VERBOSE_PROCFS +#if 0 +static void sprd_pcm_proc_dump_reg(int id, struct snd_info_buffer *buffer) +{ + u32 reg, base_reg; + u32 offset = sci_dma_dump_reg(id, &base_reg); + for (reg = base_reg; reg < base_reg + offset; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%08x 0x%08x 0x%08x 0x%08x\n", + (unsigned int)(reg - base_reg), + __raw_readl((void __iomem *)(reg + 0x00)), + __raw_readl((void __iomem *)(reg + 0x04)), + __raw_readl((void __iomem *)(reg + 0x08)), + __raw_readl((void __iomem *)(reg + 0x0C)) + ); + } +} + +static void sprd_pcm_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct sprd_runtime_data *rtd = substream->runtime->private_data; + int i; + + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->uid_cid_map[i] >= 0) { + snd_iprintf(buffer, "Channel%d Config\n", + rtd->uid_cid_map[i]); + sprd_pcm_proc_dump_reg(rtd->uid_cid_map[i], buffer); + } + } +} + +static void sprd_pcm_proc_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + struct snd_pcm_str *pstr = substream->pstr; + struct snd_pcm *pcm = pstr->pcm; + struct sprd_runtime_data *rtd = substream->runtime->private_data; + + if ((entry = + snd_info_create_card_entry(pcm->card, "DMA", + pstr->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, sprd_pcm_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + rtd->proc_info_entry = entry; +} + +static void sprd_pcm_proc_done(struct snd_pcm_substream *substream) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + snd_info_free_entry(rtd->proc_info_entry); + rtd->proc_info_entry = NULL; +} +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline void sprd_pcm_proc_init(struct snd_pcm_substream *substream) +{ +} + +static void sprd_pcm_proc_done(struct snd_pcm_substream *substream) +{ +} +#endif + +static int sprd_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + struct sprd_pcm_dma_params *dma_data; + struct dma_chan *dma_chn_request; + struct sprd_dma_cfg *dma_config_ptr[2]; + struct sprd_dma_callback_data *dma_pdata_ptr[2]; + dma_cap_mask_t mask; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + dma_addr_t dma_buff_phys[2]; + struct i2s_config *config = NULL; + enum dma_filter_param chn_type; + int ret = 0; + int i, j = 0; + int used_chan_count; + + dma_data = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream); + if (!dma_data) + goto no_dma; + + used_chan_count = params_channels(params); + sp_asoc_pr_info("chan=%d totsize=%d period=%d\n", used_chan_count, + totsize, period); + if (sprd_is_i2s(srtd->cpu_dai)) { + config = srtd->cpu_dai->ac97_pdata; + used_chan_count = rtd->hw_chan; + } else { + rtd->interleaved = (used_chan_count == 2) + && sprd_pcm_is_interleaved(runtime); + if (rtd->interleaved) { + sp_asoc_pr_dbg("Interleaved Access\n"); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data->desc.src_step = 4; + } else { + dma_data->desc.des_step = 4; + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data->desc.src_step = 2; + } else { + dma_data->desc.des_step = 2; + } + } + } + + /* this may get called several times by oss emulation + * with different params */ + if (rtd->params == NULL) { + rtd->params = dma_data; + for (i = 0; i < used_chan_count; i++) { + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY|DMA_SG,mask); + /* request dma channel */ +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_USE_AON_DMA + chn_type = AP_FULL_DMA; +#else + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK + && (!sprd_is_i2s(srtd->cpu_dai))) { + chn_type = AON_FULL_DMA; + } else { + chn_type = AP_FULL_DMA; + } +#endif + dma_chn_request = dma_request_channel(mask, sprd_dma_filter_fn, (void *)&chn_type); + if (!dma_chn_request) { + pr_err("ERR:PCM Request DMA Error %d\n", + dma_data->channels[i]); + for (i--; i >= 0; i--) { + rtd->dma_chn[i] = NULL; + rtd->dma_tx_des[i] = NULL; + rtd->cookie[i] = 0; + rtd->params = NULL; + } + goto hw_param_err; + } + rtd->dma_chn[i] = dma_chn_request; + sp_asoc_pr_dbg("Chan%d DMA ID=%d, chn_type:%d \n", + rtd->dma_chn[i]->chan_id, + rtd->params->channels[i], chn_type); + } + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totsize; + + rtd->dma_addr_offset = (totsize / used_chan_count); + if (sprd_pcm_is_interleaved(runtime)) + rtd->dma_addr_offset = 2; + + for (i = 0; i < used_chan_count; i++) { + dma_config_ptr[i] = rtd->dma_cfg_array + i * (runtime->hw.periods_max); + memset(dma_config_ptr[i],0,sizeof(struct sprd_dma_cfg) * runtime->hw.periods_max); + dma_pdata_ptr[i] = rtd->dma_pdata + i; + memset(dma_pdata_ptr[i],0,sizeof(struct sprd_dma_callback_data)); + dma_pdata_ptr[i]->dma_chn = rtd->dma_chn[i]; + dma_pdata_ptr[i]->substream = substream; + dma_buff_phys[i] = runtime->dma_addr + i * rtd->dma_addr_offset; + } + + do { + for (i = 0; i < used_chan_count; i++) { + (dma_config_ptr[i]+j)->datawidth = 1; + if (sprd_is_i2s(srtd->cpu_dai)) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + (dma_config_ptr[i]+j)->fragmens_len = + (I2S_FIFO_DEPTH - + config->tx_watermark) * + (dma_config_ptr[i]+j)->datawidth; + } else { + (dma_config_ptr[i]+j)->fragmens_len = + config->rx_watermark * + (dma_config_ptr[i]+j)->datawidth; + } + } else { + (dma_config_ptr[i]+j)->fragmens_len = dma_data->desc.fragmens_len; + } + (dma_config_ptr[i]+j)->block_len = period / used_chan_count; + (dma_config_ptr[i]+j)->transcation_len = 0; + (dma_config_ptr[i]+j)->req_mode = FRAG_REQ_MODE; + (dma_config_ptr[i]+j)->irq_mode= BLK_DONE; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step; + (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step; + (dma_config_ptr[i]+j)->src_addr = dma_buff_phys[i]; + (dma_config_ptr[i]+j)->des_addr = dma_data->dev_paddr[i]; + } else { + (dma_config_ptr[i]+j)->src_step = dma_data->desc.src_step; + (dma_config_ptr[i]+j)->des_step = dma_data->desc.des_step; + (dma_config_ptr[i]+j)->src_addr = dma_data->dev_paddr[i]; + (dma_config_ptr[i]+j)->des_addr = dma_buff_phys[i]; + } + dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len; + if (rtd->interleaved) + dma_buff_phys[i] += (dma_config_ptr[i]+j)->block_len; + + (dma_config_ptr[i]+j)->link_cfg_v = (unsigned long)(rtd->dma_cfg_virt[i]); + (dma_config_ptr[i]+j)->link_cfg_p = (unsigned long)(rtd->dma_cfg_phy[i]); + (dma_config_ptr[i]+j)->dev_id = dma_data->channels[i]; + } + + if (period > totsize) + period = totsize; + j++; + } while (totsize -= period); + for (i = 0; i < used_chan_count; i++) { + (dma_config_ptr[i]+j-1)->is_end = 2; + } + + sp_asoc_pr_dbg("Node Size:%d\n", j); + + /* config dma channel */ + ret = dmaengine_device_control(rtd->dma_chn[0], + DMA_SLAVE_CONFIG, + (unsigned long)(dma_config_ptr[0])); + if(ret < 0){ + sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[0]->chan_id); + goto hw_param_err; + } + /* get dma desc from dma config */ + rtd->dma_tx_des[0] = + rtd->dma_chn[0]->device->device_prep_dma_memcpy(rtd->dma_chn[0], 0, 0, 0, + DMA_CFG_FLAG|DMA_HARDWARE_FLAG); + if(!rtd->dma_tx_des[0]) { + sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[0]->chan_id); + goto hw_param_err; + } + + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n", + __func__,rtd->dma_chn[0]->chan_id); + rtd->dma_tx_des[0]->callback = sprd_pcm_dma_buf_done; + rtd->dma_tx_des[0]->callback_param = (void *)(dma_pdata_ptr[0]); + } + + if (used_chan_count > 1) { + /* config dma channel */ + ret = dmaengine_device_control(rtd->dma_chn[1], + DMA_SLAVE_CONFIG, + (unsigned long)(dma_config_ptr[1])); + if(ret < 0){ + sp_asoc_pr_dbg("%s, DMA chan ID %d config is failed!\n", __func__, rtd->dma_chn[1]->chan_id); + goto hw_param_err; + } + /* get dma desc from dma config */ + rtd->dma_tx_des[1] = + rtd->dma_chn[1]->device->device_prep_dma_memcpy(rtd->dma_chn[1], 0, 0, 0, + DMA_CFG_FLAG|DMA_HARDWARE_FLAG); + if(!rtd->dma_tx_des[1]) { + sp_asoc_pr_dbg("%s, DMA chan ID %d memcpy is failed!\n", __func__, rtd->dma_chn[1]->chan_id); + goto hw_param_err; + } + + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("%s, Register Callback func for DMA chan ID %d\n", + __func__,rtd->dma_chn[1]->chan_id); + rtd->dma_tx_des[1]->callback = sprd_pcm_dma_buf_done; + rtd->dma_tx_des[1]->callback_param = (void *)(dma_pdata_ptr[1]); + } + + } + sprd_pcm_proc_init(substream); + + goto ok_go_out; + +no_dma: + sp_asoc_pr_dbg("no dma\n"); + rtd->params = NULL; + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = totsize; + return ret; +hw_param_err: + sp_asoc_pr_dbg("hw_param_err\n"); +ok_go_out: + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static int sprd_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + struct sprd_pcm_dma_params *dma = rtd->params; + int i; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (dma) { + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->dma_chn[i]) { + dma_release_channel(rtd->dma_chn[i]); + rtd->dma_chn[i] = NULL; + rtd->dma_tx_des[i] = NULL; + rtd->cookie[i] = 0; + } + } + rtd->params = NULL; + } + + sprd_pcm_proc_done(substream); + + return 0; +} + +static int sprd_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + struct sprd_pcm_dma_params *dma = rtd->params; + int ret = 0; + int i; + if (!dma) { + sp_asoc_pr_dbg("no trigger"); + return 0; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->dma_tx_des[i]) { + rtd->cookie[i] = dmaengine_submit(rtd->dma_tx_des[i]); + } + } + dma_issue_pending_all(); + sp_asoc_pr_info("S\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->dma_chn[i]) { + dmaengine_pause(rtd->dma_chn[i]); + } + } + rtd->cb_called = 0; + sp_asoc_pr_info("E\n"); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + snd_pcm_uframes_t x; + int now_pointer; + int bytes_of_pointer = 0; + int shift = 1; + int sel_max = 0; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + sp_asoc_pr_dbg("no pointer"); + return 0; + } + + if (rtd->interleaved) + shift = 0; + if (rtd->dma_chn[0]) { + now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[0], + rtd->cookie[0], substream) - + runtime->dma_addr; + bytes_of_pointer = now_pointer; + } + if (rtd->dma_chn[1]) { + now_pointer = sprd_pcm_dma_get_addr(rtd->dma_chn[1], + rtd->cookie[1], substream) - + runtime->dma_addr - rtd->dma_addr_offset; + if (!bytes_of_pointer) { + bytes_of_pointer = now_pointer; + } else { + sel_max = (bytes_of_pointer < rtd->dma_pos_pre[0]); + sel_max ^= (now_pointer < rtd->dma_pos_pre[1]); + rtd->dma_pos_pre[0] = bytes_of_pointer; + rtd->dma_pos_pre[1] = now_pointer; + if (sel_max) { + bytes_of_pointer = + max(bytes_of_pointer, now_pointer) << shift; + } else { + bytes_of_pointer = + min(bytes_of_pointer, now_pointer) << shift; + } + } + } + x = bytes_to_frames(runtime, bytes_of_pointer); + + if (x == runtime->buffer_size) + x = 0; + +#if 0 + sp_asoc_pr_dbg("p=%d f=%d\n", bytes_of_pointer, x); +#endif + return x; +} + +static int sprd_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + sp_asoc_pr_dbg("no mmap"); + return 0; + } +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +#else + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + return remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +#endif +} + +static struct snd_pcm_ops sprd_pcm_ops = { + .open = sprd_pcm_open, + .close = sprd_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sprd_pcm_hw_params, + .hw_free = sprd_pcm_hw_free, + .prepare = sprd_pcm_prepare, + .trigger = sprd_pcm_trigger, + .pointer = sprd_pcm_pointer, + .mmap = sprd_pcm_mmap, +}; + +static int sprd_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = AUDIO_BUFFER_BYTES_MAX; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + if (sprd_is_i2s(rtd->cpu_dai)) { + size = I2S_BUFFER_BYTES_MAX; + } else { + size = VBC_BUFFER_BYTES_MAX; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (sprd_is_i2s(rtd->cpu_dai) + || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + && 0 == sprd_buffer_iram_backup())) { +#endif + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } else { + buf->private_data = buf; + buf->area = (void *)(s_iram_remap_base); + buf->addr = SPRD_IRAM_ALL_PHYS; + size = SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE); + } +#endif + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static u64 sprd_pcm_dmamask = DMA_BIT_MASK(32); +static struct snd_dma_buffer *save_p_buf = 0; +#define VBC_CHAN (2) +static struct snd_dma_buffer *save_c_buf[VBC_CHAN] = { 0 }; + +static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_substream *substream; + int ret = 0; + + sp_asoc_pr_dbg("%s %s\n", __func__, sprd_dai_pcm_name(cpu_dai)); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &sprd_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + if (sprd_is_i2s(cpu_dai) || !save_p_buf) { + ret = sprd_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + if (!sprd_is_i2s(cpu_dai)) { + save_p_buf = buf; + } + sp_asoc_pr_dbg("Playback alloc memery\n"); + } else { + memcpy(buf, save_p_buf, sizeof(*buf)); + sp_asoc_pr_dbg("Playback share memery\n"); + } + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + int id = cpu_dai->driver->id; + struct snd_dma_buffer *buf = &substream->dma_buffer; + if (sprd_is_vaudio(cpu_dai)) { + id -= VAUDIO_MAGIC_ID; + } + if (sprd_is_i2s(cpu_dai) || !save_c_buf[id]) { + ret = sprd_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + if (!sprd_is_i2s(cpu_dai)) { + save_c_buf[id] = buf; + } + sp_asoc_pr_dbg("Capture alloc memery %d\n", id); + } else { + memcpy(buf, save_c_buf[id], sizeof(*buf)); + sp_asoc_pr_dbg("Capture share memery %d\n", id); + } + } +out: + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static void sprd_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + int i; + + sp_asoc_pr_dbg("%s\n", __func__); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (buf->private_data) + sprd_buffer_iram_restore(); + else +#endif + dma_alloc_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + if (buf == save_p_buf) { + save_p_buf = 0; + } + for (i = 0; i < VBC_CHAN; i++) { + if (buf == save_c_buf[i]) { + save_c_buf[i] = 0; + } + } + } +} + +static struct snd_soc_platform_driver sprd_soc_platform = { + .ops = &sprd_pcm_ops, + .pcm_new = sprd_pcm_new, + .pcm_free = sprd_pcm_free_dma_buffers, +}; + +static int sprd_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &sprd_soc_platform); +} + +static int sprd_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sprd_pcm_of_match[] = { + {.compatible = "sprd,sprd-pcm",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, sprd_pcm_of_match); +#endif + +static struct platform_driver sprd_pcm_driver = { + .driver = { + .name = "sprd-pcm-audio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sprd_pcm_of_match), + }, + + .probe = sprd_soc_platform_probe, + .remove = sprd_soc_platform_remove, +}; + +module_platform_driver(sprd_pcm_driver); + +MODULE_DESCRIPTION("SPRD ASoC PCM DMA"); +MODULE_AUTHOR("Jian chen <jian.chen@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sprd-audio"); diff --git a/sound/soc/sprd/dai/sprd-dmaengine-pcm.h b/sound/soc/sprd/dai/sprd-dmaengine-pcm.h new file mode 100644 index 00000000..09bf8c3a --- /dev/null +++ b/sound/soc/sprd/dai/sprd-dmaengine-pcm.h @@ -0,0 +1,56 @@ +/* + * sound/soc/sprd/dai/sprd-dmaengine-pcm.h + * + * SpreadTrum DMA for the pcm stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __SPRD_DMA_ENGINE_PCM_H +#define __SPRD_DMA_ENGINE_PCM_H + +//#include <mach/dma.h> +//#include <sprd_dma.h> +#include <../../../drivers/dma/sprd_dma.h> +#include <linux/dmaengine.h> + + +#define VBC_PCM_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define VBC_FIFO_FRAME_NUM (160) +#define VBC_BUFFER_BYTES_MAX (128 * 1024) + +#define I2S_BUFFER_BYTES_MAX (64 * 1024) + +#define AUDIO_BUFFER_BYTES_MAX (VBC_BUFFER_BYTES_MAX + I2S_BUFFER_BYTES_MAX) + +struct sprd_pcm_dma_params { + char *name; /* stream identifier */ + int channels[2]; /* channel id */ + int irq_type; /* dma interrupt type */ + struct sprd_dma_cfg desc; /* dma description struct */ + u32 dev_paddr[2]; /* device physical address for DMA */ +}; + +static inline u32 sprd_pcm_dma_get_addr(struct dma_chan *dma_chn, + dma_cookie_t cookie, struct snd_pcm_substream *substream) +{ + struct dma_tx_state dma_state; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_state.residue = SPRD_SRC_ADDR; + } else { + dma_state.residue = SPRD_DST_ADDR; + } + dmaengine_tx_status(dma_chn, cookie, &dma_state); + //printk(KERN_ERR "%s, residue:0x%x \n",__func__,dma_state.residue); + return dma_state.residue; +} + +#endif /* __SPRD_DMA_ENGINE_PCM_H */ diff --git a/sound/soc/sprd/dai/sprd-pcm.c b/sound/soc/sprd/dai/sprd-pcm.c new file mode 100644 index 00000000..517b588c --- /dev/null +++ b/sound/soc/sprd/dai/sprd-pcm.c @@ -0,0 +1,1025 @@ +/* + * sound/soc/sprd/dai/sprd-pcm.c + * + * SpreadTrum DMA for the pcm stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" PCM ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include <soc/sprd/dma.h> +#include <soc/sprd/dma_reg.h> +#include <soc/sprd/sprd-audio.h> + +#include "sprd-asoc-common.h" +#include "sprd-pcm.h" +#include "vaudio.h" +#include <soc/sprd/i2s.h> +#include "dfm.h" + +#ifndef DMA_LINKLIST_CFG_NODE_SIZE +#define DMA_LINKLIST_CFG_NODE_SIZE (sizeof(sprd_dma_desc)) +#endif + +struct sprd_runtime_data { + int dma_addr_offset; + struct sprd_pcm_dma_params *params; + int uid_cid_map[2]; + int int_pos_update[2]; + sprd_dma_desc *dma_cfg_array; + dma_addr_t *dma_desc_array_orig; + dma_addr_t dma_desc_array_phys_orig; + dma_addr_t *dma_desc_array; + dma_addr_t dma_desc_array_phys; + int burst_len; + int hw_chan; + int dma_pos_pre[2]; + int interleaved; + int irq_called; +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + int buffer_in_iram; +#endif +#ifdef CONFIG_SND_VERBOSE_PROCFS + struct snd_info_entry *proc_info_entry; +#endif +}; + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM +#define SPRD_AUDIO_DMA_NODE_SIZE (1024) +#endif + +static const struct snd_pcm_hardware sprd_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + /* 16bits, stereo-2-channels */ + .period_bytes_min = VBC_FIFO_FRAME_NUM * 4, + /* non limit */ + .period_bytes_max = VBC_FIFO_FRAME_NUM * 4 * 100, + .periods_min = 1, + /* non limit */ + .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE, + .buffer_bytes_max = VBC_BUFFER_BYTES_MAX, +}; + +static const struct snd_pcm_hardware sprd_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + /* 16bits, stereo-2-channels */ + .period_bytes_min = 8 * 2, + /* non limit */ + .period_bytes_max = 32 * 2 * 100, + .periods_min = 1, + /* non limit */ + .periods_max = PAGE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE, + .buffer_bytes_max = I2S_BUFFER_BYTES_MAX, +}; + +atomic_t lightsleep_refcnt; + +int sprd_lightsleep_disable(const char *id, int disalbe) + __attribute__ ((weak, alias("__sprd_lightsleep_disable"))); + +static int __sprd_lightsleep_disable(const char *id, int disable) +{ + sp_asoc_pr_dbg("NO lightsleep control function %d\n", disable); + return 0; +} + +static inline int sprd_is_vaudio(struct snd_soc_dai *cpu_dai) +{ + return ((cpu_dai->driver->id == VAUDIO_MAGIC_ID) + || (cpu_dai->driver->id == VAUDIO_MAGIC_ID + 1)); +} + +static inline int sprd_is_i2s(struct snd_soc_dai *cpu_dai) +{ + return (cpu_dai->driver->id == I2S_MAGIC_ID); +} + +static inline int sprd_is_dfm(struct snd_soc_dai *cpu_dai) +{ + return (cpu_dai->driver->id == DFM_MAGIC_ID); +} + +static inline const char *sprd_dai_pcm_name(struct snd_soc_dai *cpu_dai) +{ + if (sprd_is_i2s(cpu_dai)) { + return "I2S"; + } else if (sprd_is_vaudio(cpu_dai)) { + return "VAUDIO"; + } else if (sprd_is_dfm(cpu_dai)) { + return "DFM"; + } + return "VBC"; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP +static char *s_mem_for_iram = 0; +#endif +static char *s_iram_remap_base = 0; + +static int sprd_buffer_iram_backup(void) +{ +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + void __iomem *iram_start; + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram); +#endif + if (!s_iram_remap_base) { + s_iram_remap_base = + ioremap_nocache(SPRD_IRAM_ALL_PHYS, SPRD_IRAM_ALL_SIZE); + } +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + if (!s_mem_for_iram) { + s_mem_for_iram = kzalloc(SPRD_IRAM_ALL_SIZE, GFP_KERNEL); + } else { + sp_asoc_pr_dbg("IRAM is Backup, Be Careful use IRAM!\n"); + return 0; + } + if (!s_mem_for_iram) { + pr_err("ERR:IRAM Backup Error!\n"); + return -ENOMEM; + } + iram_start = (void __iomem *)(s_iram_remap_base); + memcpy_fromio(s_mem_for_iram, iram_start, SPRD_IRAM_ALL_SIZE); +#endif + return 0; +} + +static int sprd_buffer_iram_restore(void) +{ +#ifdef CONFIG_SND_SOC_SPRD_IRAM_BACKUP + void __iomem *iram_start; + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)s_mem_for_iram); + if (!s_mem_for_iram) { + pr_err("ERR:IRAM not Backup\n"); + return 0; + } + iram_start = (void __iomem *)(s_iram_remap_base); + memcpy_toio(iram_start, s_mem_for_iram, SPRD_IRAM_ALL_SIZE); + kfree(s_mem_for_iram); + s_mem_for_iram = 0; +#endif + return 0; +} +#endif + +static inline int sprd_pcm_is_interleaved(struct snd_pcm_runtime *runtime) +{ + return (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); +} + +#define PCM_DIR_NAME(stream) (stream == SNDRV_PCM_STREAM_PLAYBACK ? "Playback" : "Captrue") + +static int sprd_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + struct sprd_runtime_data *rtd; + struct i2s_config *config; + int burst_len; + int hw_chan; + int ret; + + sp_asoc_pr_info("%s Open %s\n", sprd_dai_pcm_name(srtd->cpu_dai), + PCM_DIR_NAME(substream->stream)); + + if (sprd_is_i2s(srtd->cpu_dai)) { + snd_soc_set_runtime_hwparams(substream, &sprd_i2s_pcm_hardware); + config = srtd->cpu_dai->ac97_pdata; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + burst_len = I2S_FIFO_DEPTH - config->tx_watermark; + } else { + burst_len = config->rx_watermark; + } + burst_len <<= config->byte_per_chan; + hw_chan = 1; + } else { + snd_soc_set_runtime_hwparams(substream, &sprd_pcm_hardware); + burst_len = (VBC_FIFO_FRAME_NUM * 4); + hw_chan = 2; + } + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + burst_len); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + burst_len); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + ret = -ENOMEM; + rtd = kzalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + goto out; + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + runtime->private_data = rtd; + ret = 0; + goto out; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (sprd_is_i2s(srtd->cpu_dai) + || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + && 0 == sprd_buffer_iram_backup())) { +#endif + rtd->dma_desc_array_orig = + dma_alloc_coherent(substream->pcm->card->dev, + hw_chan * (PAGE_SIZE+32), + &rtd->dma_desc_array_phys_orig, + GFP_KERNEL); + + rtd->dma_desc_array_phys = (rtd->dma_desc_array_phys_orig+31)&(~31); + rtd->dma_desc_array = (unsigned int)rtd->dma_desc_array_orig + (rtd->dma_desc_array_phys - rtd->dma_desc_array_phys_orig); + + if (atomic_inc_return(&lightsleep_refcnt) == 1) + sprd_lightsleep_disable("audio", 1); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } else { + runtime->hw.periods_max = + SPRD_AUDIO_DMA_NODE_SIZE / DMA_LINKLIST_CFG_NODE_SIZE; + runtime->hw.buffer_bytes_max = + SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE); + rtd->dma_desc_array = + (void *)(s_iram_remap_base + runtime->hw.buffer_bytes_max); + rtd->dma_desc_array_phys = + SPRD_IRAM_ALL_PHYS + runtime->hw.buffer_bytes_max; + rtd->dma_desc_array_orig = rtd->dma_desc_array; + rtd->dma_desc_array_phys_orig = rtd->dma_desc_array_phys; + rtd->buffer_in_iram = 1; + /*must clear the dma_desc_array first here */ + memset(rtd->dma_desc_array, 0, (2 * SPRD_AUDIO_DMA_NODE_SIZE)); + } +#endif + + if (!rtd->dma_desc_array_orig) + goto err1; + + rtd->dma_cfg_array = + kzalloc(hw_chan * runtime->hw.periods_max * sizeof(sprd_dma_desc), + GFP_KERNEL); + if (!rtd->dma_cfg_array) + goto err2; + + rtd->uid_cid_map[0] = rtd->uid_cid_map[1] = -1; + + rtd->burst_len = burst_len; + rtd->hw_chan = hw_chan; + + runtime->private_data = rtd; + ret = 0; + goto out; + +err2: + pr_err("ERR:dma_cfg_array alloc failed!\n"); + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (rtd->buffer_in_iram) + sprd_buffer_iram_restore(); + else +#endif + dma_free_coherent(substream->pcm->card->dev, + hw_chan * (PAGE_SIZE+32), + rtd->dma_desc_array_orig, + rtd->dma_desc_array_phys_orig); +err1: + pr_err("ERR:dma_desc_array alloc failed!\n"); + kfree(rtd); + if (!atomic_dec_return(&lightsleep_refcnt)) + sprd_lightsleep_disable("audio", 0); +out: + return ret; +} + +static int sprd_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + + sp_asoc_pr_info("%s Close %s\n", sprd_dai_pcm_name(srtd->cpu_dai), + PCM_DIR_NAME(substream->stream)); + + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + kfree(rtd); + return 0; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (rtd->buffer_in_iram) + sprd_buffer_iram_restore(); + else { +#endif + dma_free_coherent(substream->pcm->card->dev, + rtd->hw_chan * (PAGE_SIZE+32), + rtd->dma_desc_array_orig, + rtd->dma_desc_array_phys_orig); + if (!atomic_dec_return(&lightsleep_refcnt)) + sprd_lightsleep_disable("audio", 0); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } +#endif + + kfree(rtd->dma_cfg_array); + kfree(rtd); + + return 0; +} + +static void sprd_pcm_dma_irq_ch(int dma_ch, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + int i = 0; + + if (!rtd->irq_called) { + rtd->irq_called = 1; + sp_asoc_pr_info("IRQ CALL\n"); + } + + if (rtd->hw_chan == 1) + goto irq_fast; + + for (i = 0; i < 2; i++) { + if (dma_ch == rtd->uid_cid_map[i]) { + rtd->int_pos_update[i] = 1; + + if (rtd->uid_cid_map[1 - i] >= 0) { + if (rtd->int_pos_update[1 - i]) + goto irq_ready; + } else { + goto irq_ready; + } + } + } + goto irq_ret; +irq_ready: + rtd->int_pos_update[0] = 0; + rtd->int_pos_update[1] = 0; +irq_fast: + snd_pcm_period_elapsed(dev_id); +irq_ret: + return; +} + +/* + * proc interface + */ + +#ifdef CONFIG_SND_VERBOSE_PROCFS +static void sprd_pcm_proc_dump_reg(int id, struct snd_info_buffer *buffer) +{ + u32 reg, base_reg; + u32 offset = sci_dma_dump_reg(id, &base_reg); + for (reg = base_reg; reg < base_reg + offset; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%08x 0x%08x 0x%08x 0x%08x\n", + (unsigned int)(reg - base_reg), + __raw_readl((void __iomem *)(reg + 0x00)), + __raw_readl((void __iomem *)(reg + 0x04)), + __raw_readl((void __iomem *)(reg + 0x08)), + __raw_readl((void __iomem *)(reg + 0x0C)) + ); + } +} + +static void sprd_pcm_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct sprd_runtime_data *rtd = substream->runtime->private_data; + int i; + + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->uid_cid_map[i] >= 0) { + snd_iprintf(buffer, "Channel%d Config\n", + rtd->uid_cid_map[i]); + sprd_pcm_proc_dump_reg(rtd->uid_cid_map[i], buffer); + } + } +} + +static void sprd_pcm_proc_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + struct snd_pcm_str *pstr = substream->pstr; + struct snd_pcm *pcm = pstr->pcm; + struct sprd_runtime_data *rtd = substream->runtime->private_data; + + if ((entry = + snd_info_create_card_entry(pcm->card, "DMA", + pstr->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, sprd_pcm_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + rtd->proc_info_entry = entry; +} + +static void sprd_pcm_proc_done(struct snd_pcm_substream *substream) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + snd_info_free_entry(rtd->proc_info_entry); + rtd->proc_info_entry = NULL; +} +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline void sprd_pcm_proc_init(struct snd_pcm_substream *substream) +{ +} + +static void sprd_pcm_proc_done(struct snd_pcm_substream *substream) +{ +} +#endif + +static int sprd_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + struct sprd_pcm_dma_params *dma; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + sprd_dma_desc *dma_desc[2]; + dma_addr_t dma_buff_phys[2]; /*, next_desc_phys[2]; */ + struct i2s_config *config = NULL; + int ret = 0; + int i, j = 0; + int used_chan_count; + int chan_id; + struct reg_cfg_addr dma_reg_addr[2]; + + sp_asoc_pr_dbg("%s\n", __func__); + + dma = snd_soc_dai_get_dma_data(srtd->cpu_dai, substream); + if (!dma) + goto no_dma; + + used_chan_count = params_channels(params); + sp_asoc_pr_info("chan=%d totsize=%d period=%d\n", used_chan_count, + totsize, period); + if (sprd_is_i2s(srtd->cpu_dai)) { + config = srtd->cpu_dai->ac97_pdata; + used_chan_count = rtd->hw_chan; + } else { + rtd->interleaved = (used_chan_count == 2) + && sprd_pcm_is_interleaved(runtime); + if (rtd->interleaved) { + sp_asoc_pr_dbg("Interleaved Access\n"); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma->desc.src_step = 4; + } else { + dma->desc.des_step = 4; + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma->desc.src_step = 2; + } else { + dma->desc.des_step = 2; + } + } + } + + /* this may get called several times by oss emulation + * with different params */ + if (rtd->params == NULL) { + rtd->params = dma; + for (i = 0; i < used_chan_count; i++) { + chan_id = sci_dma_request(dma->name, FULL_DMA_CHN); + if (chan_id < 0) { + pr_err("ERR:PCM Request DMA Error %d\n", + dma->channels[i]); + for (i--; i >= 0; i--) { + sci_dma_free(rtd->uid_cid_map[i]); + rtd->uid_cid_map[i] = -1; + rtd->params = NULL; + } + goto hw_param_err; + } + rtd->uid_cid_map[i] = chan_id; + sp_asoc_pr_dbg("Chan%d DMA ID=%d\n", + rtd->uid_cid_map[i], + rtd->params->channels[i]); + } + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totsize; + + dma_desc[0] = rtd->dma_cfg_array; + dma_desc[1] = rtd->dma_cfg_array + runtime->hw.periods_max; + + dma_buff_phys[0] = runtime->dma_addr; + rtd->dma_addr_offset = (totsize / used_chan_count); + if (sprd_pcm_is_interleaved(runtime)) + rtd->dma_addr_offset = 2; + dma_buff_phys[1] = runtime->dma_addr + rtd->dma_addr_offset; + + do { + for (i = 0; i < used_chan_count; i++) { + dma_desc[i]->datawidth = dma->desc.datawidth; + if (sprd_is_i2s(srtd->cpu_dai)) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + dma_desc[i]->fragmens_len = + (I2S_FIFO_DEPTH - + config->tx_watermark) * + dma_desc[i]->datawidth; + } else { + dma_desc[i]->fragmens_len = + config->rx_watermark * + dma_desc[i]->datawidth; + } + } else { + dma_desc[i]->fragmens_len = + dma->desc.fragmens_len; + } + dma_desc[i]->block_len = period / used_chan_count; + dma_desc[i]->transcation_len = 0; + dma_desc[i]->req_mode = FRAG_REQ_MODE; + dma_desc[i]->src_step = dma->desc.src_step; + dma_desc[i]->des_step = dma->desc.des_step; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_desc[i]->src_addr = dma_buff_phys[i]; + dma_desc[i]->des_addr = dma->dev_paddr[i]; + } else { + dma_desc[i]->src_addr = dma->dev_paddr[i]; + dma_desc[i]->des_addr = dma_buff_phys[i]; + } + + dma_buff_phys[i] += dma_desc[i]->block_len; + if (rtd->interleaved) + dma_buff_phys[i] += dma_desc[i]->block_len; + dma_desc[i]++; + } + + if (period > totsize) + period = totsize; + j++; + } while (totsize -= period); + + sp_asoc_pr_dbg("Node Size:%d\n", j); + + dma_reg_addr[0].phys_addr = (u32) (rtd->dma_desc_array_phys); + dma_reg_addr[0].virt_addr = rtd->dma_desc_array; + sp_asoc_pr_dbg("dma_reg_addr[0].virt_addr:0x%x\n", + dma_reg_addr[0].virt_addr); + sp_asoc_pr_dbg("dma_reg_addr[0].phys_addr:0x%x\n", + dma_reg_addr[0].phys_addr); + + sci_dma_config((u32) (rtd->uid_cid_map[0]), + (struct sci_dma_cfg *)(rtd->dma_cfg_array), + params_periods(params), &dma_reg_addr[0]); + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("Register IRQ for DMA chan ID %d\n", + rtd->uid_cid_map[0]); + ret = + sci_dma_register_irqhandle(rtd->uid_cid_map[0], + rtd->params->irq_type, + sprd_pcm_dma_irq_ch, substream); + } + + if (used_chan_count > 1) { + dma_reg_addr[1].phys_addr = + (u32) (dma_reg_addr[0].phys_addr) + + runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE; + dma_reg_addr[1].virt_addr = + (dma_reg_addr[0].virt_addr) + + runtime->hw.periods_max * DMA_LINKLIST_CFG_NODE_SIZE; + sp_asoc_pr_dbg("dma_reg_addr[1].virt_addr:0x%x\n", + dma_reg_addr[1].virt_addr); + sp_asoc_pr_dbg("dma_reg_addr[1].phys_addr:0x%x\n", + dma_reg_addr[1].phys_addr); + + sci_dma_config((u32) (rtd->uid_cid_map[1]), + (struct sci_dma_cfg *)(rtd->dma_cfg_array + + runtime->hw.periods_max), + params_periods(params), &dma_reg_addr[1]); + if (!(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)) { + sp_asoc_pr_info("Register IRQ for DMA Chan ID %d\n", + rtd->uid_cid_map[1]); + ret = + sci_dma_register_irqhandle(rtd->uid_cid_map[1], + rtd->params->irq_type, + sprd_pcm_dma_irq_ch, + substream); + } + + } + + sprd_pcm_proc_init(substream); + + goto ok_go_out; + +no_dma: + sp_asoc_pr_dbg("no dma\n"); + rtd->params = NULL; + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = totsize; + return ret; +hw_param_err: + sp_asoc_pr_dbg("hw_param_err\n"); +ok_go_out: + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static int sprd_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + struct sprd_pcm_dma_params *dma = rtd->params; + int i; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (dma) { + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->uid_cid_map[i] >= 0) { + sci_dma_free(rtd->uid_cid_map[i]); + rtd->uid_cid_map[i] = -1; + } + } + rtd->params = NULL; + } + + sprd_pcm_proc_done(substream); + + return 0; +} + +static int sprd_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int sprd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct sprd_runtime_data *rtd = substream->runtime->private_data; + struct sprd_pcm_dma_params *dma = rtd->params; + int ret = 0; + int i; + + if (!dma) { + sp_asoc_pr_dbg("no trigger"); + return 0; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->uid_cid_map[i] >= 0) { + sci_dma_start(rtd->uid_cid_map[i], + dma->channels[i]); + } + } + sp_asoc_pr_info("S\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = 0; i < rtd->hw_chan; i++) { + if (rtd->uid_cid_map[i] >= 0) { + sci_dma_stop(rtd->uid_cid_map[i], + dma->channels[i]); + } + } + rtd->irq_called = 0; + sp_asoc_pr_info("E\n"); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t sprd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sprd_runtime_data *rtd = runtime->private_data; + snd_pcm_uframes_t x; + int now_pointer; + int bytes_of_pointer = 0; + int shift = 1; + int sel_max = 0; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + sp_asoc_pr_dbg("no pointer"); + return 0; + } + + if (rtd->interleaved) + shift = 0; + + if (rtd->uid_cid_map[0] >= 0) { + now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[0], + substream) - + runtime->dma_addr; + bytes_of_pointer = now_pointer; + } + if (rtd->uid_cid_map[1] >= 0) { + now_pointer = sprd_pcm_dma_get_addr(rtd->uid_cid_map[1], + substream) - + runtime->dma_addr - rtd->dma_addr_offset; + if (!bytes_of_pointer) { + bytes_of_pointer = now_pointer; + } else { + sel_max = (bytes_of_pointer < rtd->dma_pos_pre[0]); + sel_max ^= (now_pointer < rtd->dma_pos_pre[1]); + rtd->dma_pos_pre[0] = bytes_of_pointer; + rtd->dma_pos_pre[1] = now_pointer; + if (sel_max) { + bytes_of_pointer = + max(bytes_of_pointer, now_pointer) << shift; + } else { + bytes_of_pointer = + min(bytes_of_pointer, now_pointer) << shift; + } + } + } + + x = bytes_to_frames(runtime, bytes_of_pointer); + + if (x == runtime->buffer_size) + x = 0; + +#if 0 + sp_asoc_pr_dbg("p=%d f=%d\n", bytes_of_pointer, x); +#endif + return x; +} + +static int sprd_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *srtd = substream->private_data; + + if (sprd_is_dfm(srtd->cpu_dai) || sprd_is_vaudio(srtd->cpu_dai)) { + sp_asoc_pr_dbg("no mmap"); + return 0; + } +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +#else + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + return remap_pfn_range(vma, vma->vm_start, + runtime->dma_addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +#endif +} + +static struct snd_pcm_ops sprd_pcm_ops = { + .open = sprd_pcm_open, + .close = sprd_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sprd_pcm_hw_params, + .hw_free = sprd_pcm_hw_free, + .prepare = sprd_pcm_prepare, + .trigger = sprd_pcm_trigger, + .pointer = sprd_pcm_pointer, + .mmap = sprd_pcm_mmap, +}; + +static int sprd_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = AUDIO_BUFFER_BYTES_MAX; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + if (sprd_is_i2s(rtd->cpu_dai)) { + size = I2S_BUFFER_BYTES_MAX; + } else { + size = VBC_BUFFER_BYTES_MAX; + } +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (sprd_is_i2s(rtd->cpu_dai) + || !((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + && 0 == sprd_buffer_iram_backup())) { +#endif + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + } else { + buf->private_data = buf; + buf->area = (void *)(s_iram_remap_base); + buf->addr = SPRD_IRAM_ALL_PHYS; + size = SPRD_IRAM_ALL_SIZE - (2 * SPRD_AUDIO_DMA_NODE_SIZE); + } +#endif + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static u64 sprd_pcm_dmamask = DMA_BIT_MASK(32); +static struct snd_dma_buffer *save_p_buf = 0; +#define VBC_CHAN (2) +static struct snd_dma_buffer *save_c_buf[VBC_CHAN] = { 0 }; + +static int sprd_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_substream *substream; + int ret = 0; + + sp_asoc_pr_dbg("%s %s\n", __func__, sprd_dai_pcm_name(cpu_dai)); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &sprd_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + if (sprd_is_i2s(cpu_dai) || !save_p_buf) { + ret = sprd_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + if (!sprd_is_i2s(cpu_dai)) { + save_p_buf = buf; + } + sp_asoc_pr_dbg("Playback alloc memery\n"); + } else { + memcpy(buf, save_p_buf, sizeof(*buf)); + sp_asoc_pr_dbg("Playback share memery\n"); + } + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + int id = cpu_dai->driver->id; + struct snd_dma_buffer *buf = &substream->dma_buffer; + if (sprd_is_vaudio(cpu_dai)) { + id -= VAUDIO_MAGIC_ID; + } + if (sprd_is_i2s(cpu_dai) || !save_c_buf[id]) { + ret = sprd_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + if (!sprd_is_i2s(cpu_dai)) { + save_c_buf[id] = buf; + } + sp_asoc_pr_dbg("Capture alloc memery %d\n", id); + } else { + memcpy(buf, save_c_buf[id], sizeof(*buf)); + sp_asoc_pr_dbg("Capture share memery %d\n", id); + } + } +out: + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static void sprd_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + int i; + + sp_asoc_pr_dbg("%s\n", __func__); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + if (buf->private_data) + sprd_buffer_iram_restore(); + else +#endif + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + if (buf == save_p_buf) { + save_p_buf = 0; + } + for (i = 0; i < VBC_CHAN; i++) { + if (buf == save_c_buf[i]) { + save_c_buf[i] = 0; + } + } + } +} + +static struct snd_soc_platform_driver sprd_soc_platform = { + .ops = &sprd_pcm_ops, + .pcm_new = sprd_pcm_new, + .pcm_free = sprd_pcm_free_dma_buffers, +}; + +static int sprd_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &sprd_soc_platform); +} + +static int sprd_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sprd_pcm_of_match[] = { + {.compatible = "sprd,sprd-pcm",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, sprd_pcm_of_match); +#endif + +static struct platform_driver sprd_pcm_driver = { + .driver = { + .name = "sprd-pcm-audio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sprd_pcm_of_match), + }, + + .probe = sprd_soc_platform_probe, + .remove = sprd_soc_platform_remove, +}; + +static int __init sprd_pcm_driver_init(void) +{ + return platform_driver_register(&sprd_pcm_driver); +} + +late_initcall(sprd_pcm_driver_init); + + +MODULE_DESCRIPTION("SPRD ASoC PCM DMA"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sprd-audio"); diff --git a/sound/soc/sprd/dai/sprd-pcm.h b/sound/soc/sprd/dai/sprd-pcm.h new file mode 100644 index 00000000..149081e1 --- /dev/null +++ b/sound/soc/sprd/dai/sprd-pcm.h @@ -0,0 +1,49 @@ +/* + * sound/soc/sprd/dai/sprd-pcm.h + * + * SpreadTrum DMA for the pcm stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __SPRD_PCM_H +#define __SPRD_PCM_H + +#include <soc/sprd/dma.h> + +#define VBC_PCM_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define VBC_FIFO_FRAME_NUM (160) +#define VBC_BUFFER_BYTES_MAX (128 * 1024) + +#define I2S_BUFFER_BYTES_MAX (64 * 1024) + +#define AUDIO_BUFFER_BYTES_MAX (VBC_BUFFER_BYTES_MAX + I2S_BUFFER_BYTES_MAX) + +struct sprd_pcm_dma_params { + char *name; /* stream identifier */ + int channels[2]; /* channel id */ + int irq_type; /* dma interrupt type */ + struct sci_dma_cfg desc; /* dma description struct */ + u32 dev_paddr[2]; /* device physical address for DMA */ +}; + +typedef struct sci_dma_cfg sprd_dma_desc; + +static inline int sprd_pcm_dma_get_addr(int dma_ch, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return sci_dma_get_src_addr(dma_ch); + else + return sci_dma_get_dst_addr(dma_ch); +} + +#endif /* __SPRD_PCM_H */ diff --git a/sound/soc/sprd/dai/vaudio/Kconfig b/sound/soc/sprd/dai/vaudio/Kconfig new file mode 100644 index 00000000..7249ff93 --- /dev/null +++ b/sound/soc/sprd/dai/vaudio/Kconfig @@ -0,0 +1,2 @@ +config SND_SOC_SPRD_VAUDIO + tristate# "Vaudio" diff --git a/sound/soc/sprd/dai/vaudio/Makefile b/sound/soc/sprd/dai/vaudio/Makefile new file mode 100644 index 00000000..35056496 --- /dev/null +++ b/sound/soc/sprd/dai/vaudio/Makefile @@ -0,0 +1,6 @@ +# SPRD Vaudio Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai + +snd-soc-sprd-vaudio-objs := vaudio.o +obj-$(CONFIG_SND_SOC_SPRD_VAUDIO) += snd-soc-sprd-vaudio.o diff --git a/sound/soc/sprd/dai/vaudio/vaudio.c b/sound/soc/sprd/dai/vaudio/vaudio.c new file mode 100644 index 00000000..5f42cc1b --- /dev/null +++ b/sound/soc/sprd/dai/vaudio/vaudio.c @@ -0,0 +1,157 @@ +/* + * sound/soc/sprd/dai/vaudio/vaudio.c + * + * SpreadTrum Vaudio for the dsp stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" DSP ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "sprd-asoc-common.h" +#include "vaudio.h" + +static int sprd_vaudio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = dai->card; + int i; + + sp_asoc_pr_dbg("%s\n", __func__); + + kfree(snd_soc_dai_get_dma_data(dai, substream)); + snd_soc_dai_set_dma_data(dai, substream, NULL); + + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 1; + } + + return 0; +} + +static void sprd_vaudio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = dai->card; + int i; + + sp_asoc_pr_dbg("%s\n", __func__); + + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 0; + } +} + +static struct snd_soc_dai_ops sprd_vaudio_dai_ops = { + .startup = sprd_vaudio_startup, + .shutdown = sprd_vaudio_shutdown, +}; + +struct snd_soc_dai_driver sprd_vaudio_dai[] = { + { + .id = VAUDIO_MAGIC_ID, + .name = "vaudio", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &sprd_vaudio_dai_ops, + }, + { + .id = VAUDIO_MAGIC_ID + 1, + .name = "vaudio-ad23", + .capture = { + .channels_min = 1, + .channels_max = 2, /*ad23 */ + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sprd_vaudio_dai_ops, + }, +}; + +static const struct snd_soc_component_driver sprd_vaudio_component = { + .name = "vaudio", +}; + +static int sprd_vaudio_drv_probe(struct platform_device *pdev) +{ + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + ret = + snd_soc_register_component(&pdev->dev, &sprd_vaudio_component, + sprd_vaudio_dai, + ARRAY_SIZE(sprd_vaudio_dai)); + + sp_asoc_pr_dbg("return %i\n", ret); + + return ret; +} + +static int sprd_vaudio_drv_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id vaudio_of_match[] = { + {.compatible = "sprd,vaudio",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vaudio_of_match); +#endif + +static struct platform_driver sprd_vaudio_driver = { + .driver = { + .name = "vaudio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(vaudio_of_match), + }, + + .probe = sprd_vaudio_drv_probe, + .remove = sprd_vaudio_drv_remove, +}; + +module_platform_driver(sprd_vaudio_driver); + +MODULE_DESCRIPTION("SPRD ASoC Vaudio CUP-DAI driver for the DSP"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cpu-dai:sprd-vaudio"); diff --git a/sound/soc/sprd/dai/vaudio/vaudio.h b/sound/soc/sprd/dai/vaudio/vaudio.h new file mode 100644 index 00000000..05377885 --- /dev/null +++ b/sound/soc/sprd/dai/vaudio/vaudio.h @@ -0,0 +1,22 @@ +/* + * sound/soc/sprd/dai/vaudio/vaudio.h + * + * SpreadTrum Vaudio for the dsp stream. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __SPRD_VAUDIO_PCM_H +#define __SPRD_VAUDIO_PCM_H + +#define VAUDIO_MAGIC_ID (0x5AD) + +#endif /* __SPRD_VAUDIO_PCM_H */ diff --git a/sound/soc/sprd/dai/vbc/Kconfig b/sound/soc/sprd/dai/vbc/Kconfig new file mode 100644 index 00000000..73af1cb7 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/Kconfig @@ -0,0 +1,19 @@ +menu "VBC" + +source "sound/soc/sprd/dai/vbc/r1p0/Kconfig" +source "sound/soc/sprd/dai/vbc/r2p0/Kconfig" + +config SND_SOC_SPRD_VBC_LR_INVERT + bool "VBC Support L/R invert" + help + say Y then the VBC will support invert the L/R channel. + +config SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + bool "VBC SUPPORT DYNAMIC EQ" + default n if ARCH_SCX30G + help + say Y if it is r3p0 vbc, N in r2p0 & r1p0 vbc. + Now r2p0 and r3p0 use the same vbc driver, so we need + to add it to separate them. + +endmenu diff --git a/sound/soc/sprd/dai/vbc/dfm.h b/sound/soc/sprd/dai/vbc/dfm.h new file mode 100644 index 00000000..cdebaf8e --- /dev/null +++ b/sound/soc/sprd/dai/vbc/dfm.h @@ -0,0 +1,27 @@ +/* + * sound/soc/sprd/dai/vbc/dfm.h + * + * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __DFM_H +#define __DFM_H + +#define DFM_MAGIC_ID 0x7BC + +struct sprd_dfm_priv { + int hw_rate; + int sample_rate; +}; + +#endif /* __DFM_H */ diff --git a/sound/soc/sprd/dai/vbc/r1p0/Kconfig b/sound/soc/sprd/dai/vbc/r1p0/Kconfig new file mode 100644 index 00000000..67ad7147 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/Kconfig @@ -0,0 +1,3 @@ +config SND_SOC_SPRD_VBC_R1P0 + tristate # "VBC_R1P0" + help diff --git a/sound/soc/sprd/dai/vbc/r1p0/Makefile b/sound/soc/sprd/dai/vbc/r1p0/Makefile new file mode 100644 index 00000000..3c813974 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/Makefile @@ -0,0 +1,6 @@ +# SPRD VBC Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai -I$(SPRD_SOUND_TREE)/dai/vbc/ + +snd-soc-sprd-vbc-r1p0-objs := vbc.o +obj-$(CONFIG_SND_SOC_SPRD_VBC_R1P0) += snd-soc-sprd-vbc-r1p0.o diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c new file mode 100644 index 00000000..0f735867 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c @@ -0,0 +1,1571 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c + * + * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "sprd-asoc-common.h" +#include "vbc-codec.h" + +#define FUN_REG(f) ((unsigned short)(-((f) + 1))) +#define SOC_REG(r) ((unsigned short)(r)) + +struct vbc_fw_header { + char magic[VBC_EQ_FIRMWARE_MAGIC_LEN]; + u32 profile_version; + u32 num_profile; +}; + +struct vbc_eq_profile { + char magic[VBC_EQ_FIRMWARE_MAGIC_LEN]; + char name[VBC_EQ_PROFILE_NAME_MAX]; + /* TODO */ + u32 effect_paras[VBC_EFFECT_PARAS_LEN]; +}; + +static const u32 vbc_eq_profile_default[VBC_EFFECT_PARAS_LEN] = { +/* TODO the default register value */ + 0x00000000, /* DAPATCHCTL */ + 0x00001818, /* DADGCTL */ + 0x0000007F, /* DAHPCTL */ + 0x00000000, /* DAALCCTL0 */ + 0x00000000, /* DAALCCTL1 */ + 0x00000000, /* DAALCCTL2 */ + 0x00000000, /* DAALCCTL3 */ + 0x00000000, /* DAALCCTL4 */ + 0x00000000, /* DAALCCTL5 */ + 0x00000000, /* DAALCCTL6 */ + 0x00000000, /* DAALCCTL7 */ + 0x00000000, /* DAALCCTL8 */ + 0x00000000, /* DAALCCTL9 */ + 0x00000000, /* DAALCCTL10 */ + 0x00000183, /* STCTL0 */ + 0x00000183, /* STCTL1 */ + 0x00000000, /* ADPATCHCTL */ + 0x00001818, /* ADDGCTL */ + 0x00000000, /* HPCOEF0 */ +}; + +struct vbc_equ { + struct device *dev; + struct snd_soc_codec *codec; + int is_active; + int is_loading; + int valid; + int now_profile; + struct vbc_fw_header hdr; + struct vbc_eq_profile *data; + void (*vbc_eq_apply) (struct snd_soc_codec * codec, void *data, + int vbc_idx); +}; + +static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx); + +#define VBC_DG_VAL_MAX (0x7F) + +typedef int (*vbc_dg_set) (int enable, int dg); +struct vbc_dg { + int dg_switch[2]; + int dg_val[2]; + vbc_dg_set dg_set[2]; +}; + +struct st_hpf_dg { + int hpf_switch[2]; + int dg_val[2]; + int hpf_val[2]; +}; + +/*vbc mux setting*/ +enum { + SPRD_VBC_MUX_START = 0, + SPRD_VBC_AD0_INMUX, + SPRD_VBC_AD1_INMUX, + SPRD_VBC_AD_IISMUX, + SPRD_VBC_MUX_MAX +}; + +#define IS_SPRD_VBC_MUX_RANG(reg) ((reg) >= SPRD_VBC_MUX_START && (reg) < (SPRD_VBC_MUX_MAX)) +#define SPRD_VBC_MUX_IDX(reg) (reg - SPRD_VBC_MUX_START) + +static const char *vbc_mux_debug_str[SPRD_VBC_MUX_MAX] = { + "ad0 inmux", + "ad1 inmux", + "ad iis mux", +}; + +typedef int (*sprd_vbc_mux_set) (int sel); +struct sprd_vbc_mux_op { + int val; + sprd_vbc_mux_set set; +}; + +enum { + VBC_LOOP_SWITCH_START = SPRD_VBC_MUX_MAX, + VBC_AD_LOOP_SWITCH = VBC_LOOP_SWITCH_START, + VBC_LOOP_SWITCH_MAX +}; + +#define IS_SPRD_VBC_SWITCH_RANG(reg) ((reg) >= VBC_LOOP_SWITCH_START && (reg) < (VBC_LOOP_SWITCH_MAX)) +#define SPRD_VBC_SWITCH_IDX(reg) (reg - VBC_LOOP_SWITCH_START) + +struct vbc_codec_priv { + struct snd_soc_codec *codec; + struct vbc_equ vbc_eq_setting; + struct mutex load_mutex; + int vbc_control; + int vbc_loop_switch[SPRD_VBC_SWITCH_IDX(VBC_LOOP_SWITCH_MAX)]; + struct vbc_dg dg[VBC_IDX_MAX]; + int vbc_da_iis_port; + int adc_dgmux_val[ADC_DGMUX_MAX]; + struct st_hpf_dg st_dg; + struct sprd_vbc_mux_op sprd_vbc_mux[SPRD_VBC_MUX_MAX]; +}; + +static int vbc_ad0_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_INMUX_SHIFT, + VBADPATH_AD0_INMUX_MASK); + return 0; +} + +static int vbc_ad1_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_INMUX_SHIFT, + VBADPATH_AD1_INMUX_MASK); + return 0; +} + +static int vbc_ad0_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_DGMUX_SHIFT, + VBADPATH_AD0_DGMUX_MASK); + return 0; +} + +static int vbc_ad1_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_DGMUX_SHIFT, + VBADPATH_AD1_DGMUX_MASK); + return 0; +} + +static int vbc_ad_iismux_set(int port) +{ + vbc_reg_update(VBIISSEL, port << VBIISSEL_AD_PORT_SHIFT, + VBIISSEL_AD_PORT_MASK); + return 0; +} + +static int vbc_da_iismux_set(int port) +{ + vbc_reg_update(VBIISSEL, port << VBIISSEL_DA_PORT_SHIFT, + VBIISSEL_DA_PORT_MASK); + return 0; +} + +static int vbc_try_ad_iismux_set(int port); + +static sprd_vbc_mux_set vbc_mux_cfg[SPRD_VBC_MUX_MAX] = { + vbc_ad0_inmux_set, + vbc_ad1_inmux_set, + vbc_try_ad_iismux_set, +}; + +static inline int vbc_da0_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(DADGCTL, 0x80 | (0xFF & dg), 0xFF); + } else { + vbc_reg_update(DADGCTL, 0, 0x80); + } + return 0; +} + +static inline int vbc_da1_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(DADGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00); + } else { + vbc_reg_update(DADGCTL, 0, 0x8000); + } + return 0; +} + +static inline int vbc_ad0_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDGCTL, 0x80 | (0xFF & dg), 0xFF); + } else { + vbc_reg_update(ADDGCTL, 0, 0x80); + } + return 0; +} + +static inline int vbc_ad1_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00); + } else { + vbc_reg_update(ADDGCTL, 0, 0x8000); + } + return 0; +} + +static inline int vbc_st0_dg_set(int dg) +{ + vbc_reg_update(STCTL0, (0x7F & dg) << 4, 0x7F0); + return 0; +} + +static inline int vbc_st1_dg_set(int dg) +{ + vbc_reg_update(STCTL1, (0x7F & dg) << 4, 0x7F0); + return 0; +} + +static inline int vbc_st0_hpf_set(int enable, int hpf_val) +{ + if (enable) { + vbc_reg_update(STCTL0, BIT(VBST_HPF_0), BIT(VBST_HPF_0)); + vbc_reg_update(STCTL0, 0xF & hpf_val, 0xF); + } else { + vbc_reg_update(STCTL0, 0, BIT(VBST_HPF_0)); + vbc_reg_update(STCTL0, 3, 0xF); + } + return 0; +} + +static inline int vbc_st1_hpf_set(int enable, int hpf_val) +{ + if (enable) { + vbc_reg_update(STCTL1, BIT(VBST_HPF_1), BIT(VBST_HPF_1)); + vbc_reg_update(STCTL1, 0xF & hpf_val, 0xF); + } else { + vbc_reg_update(STCTL1, 0, BIT(VBST_HPF_1)); + vbc_reg_update(STCTL1, 3, 0xF); + } + return 0; +} + +static int vbc_try_dg_set(struct vbc_codec_priv *vbc_codec, int vbc_idx, int id) +{ + struct vbc_dg *p_dg = &vbc_codec->dg[vbc_idx]; + int dg = p_dg->dg_val[id]; + if (p_dg->dg_switch[id]) { + p_dg->dg_set[id] (1, dg); + } else { + p_dg->dg_set[id] (0, dg); + } + return 0; +} + +static int vbc_try_st_dg_set(struct vbc_codec_priv *vbc_codec, int id) +{ + struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg; + if (id == VBC_LEFT) { + vbc_st0_dg_set(p_st_dg->dg_val[id]); + } else { + vbc_st1_dg_set(p_st_dg->dg_val[id]); + } + return 0; +} + +static int vbc_try_st_hpf_set(struct vbc_codec_priv *vbc_codec, int id) +{ + struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg; + if (id == VBC_LEFT) { + vbc_st0_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]); + } else { + vbc_st1_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]); + } + return 0; +} + +static int vbc_try_da_iismux_set(int port) +{ + return vbc_da_iismux_set(port ? (port + 1) : 0); +} + +static int vbc_try_ad_iismux_set(int port) +{ + return vbc_ad_iismux_set(port); +} + +static int vbc_try_ad_dgmux_set(struct vbc_codec_priv *vbc_codec, int id) +{ + switch (id) { + case ADC0_DGMUX: + vbc_ad0_dgmux_set(vbc_codec->adc_dgmux_val[ADC0_DGMUX]); + break; + case ADC1_DGMUX: + vbc_ad1_dgmux_set(vbc_codec->adc_dgmux_val[ADC1_DGMUX]); + break; + default: + break; + } + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DEBUG +static const char *get_event_name(int event) +{ + const char *ev_name; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ev_name = "PRE_PMU"; + break; + case SND_SOC_DAPM_POST_PMU: + ev_name = "POST_PMU"; + break; + case SND_SOC_DAPM_PRE_PMD: + ev_name = "PRE_PMD"; + break; + case SND_SOC_DAPM_POST_PMD: + ev_name = "POST_PMD"; + break; + default: + BUG(); + return 0; + } + return ev_name; +} +#endif + +static const char *ad0_inmux_txt[] = { + "IIS0AD0", "IIS1AD0", "NOINPUT", +}; + +static const char *ad1_inmux_txt[] = { + "IIS1AD1", "IIS0AD1", "NOINPUT", +}; + +static const char *ad_iis_txt[] = { + "AUDIIS0", "DIGFM", +}; + +#define SPRD_VBC_ENUM(xreg, xmax, xtexts)\ + SOC_ENUM_SINGLE(FUN_REG(xreg), 0, xmax, xtexts) + +static const struct soc_enum vbc_mux_sel_enum[SPRD_VBC_MUX_MAX] = { + /*AD INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_AD0_INMUX, 4, ad0_inmux_txt), + SPRD_VBC_ENUM(SPRD_VBC_AD1_INMUX, 4, ad1_inmux_txt), + /*IIS INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_AD_IISMUX, 4, ad_iis_txt), +}; + +static int vbc_chan_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int vbc_idx = FUN_REG(w->reg); + int chan = w->shift; + int ret = 0; + + sp_asoc_pr_dbg("%s(%s%d) Event is %s\n", __func__, + vbc_get_name(vbc_idx), chan, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + vbc_chan_enable(1, vbc_idx, chan); + break; + case SND_SOC_DAPM_POST_PMD: + vbc_chan_enable(0, vbc_idx, chan); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + + return ret; +} + +static int vbc_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + vbc_power(1); + break; + case SND_SOC_DAPM_POST_PMD: + vbc_power(0); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + + return ret; +} + +static int dfm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + fm_set_vbc_buffer_size(); /*No use in FM function, just for debug VBC */ + vbc_try_st_dg_set(vbc_codec, VBC_LEFT); + vbc_try_st_dg_set(vbc_codec, VBC_RIGHT); + if (!p_eq_setting->codec) + p_eq_setting->codec = codec; + /*eq setting */ + if (p_eq_setting->is_active && p_eq_setting->data) + vbc_eq_try_apply(codec, VBC_PLAYBACK); + vbc_enable(1); + break; + case SND_SOC_DAPM_PRE_PMD: + vbc_enable(0); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int aud_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + unsigned int vbc_idx = FUN_REG(w->reg); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n Chan is %s", __func__, + get_event_name(event), vbc_get_name(vbc_idx)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /*eq setting */ + if (!p_eq_setting->codec) + p_eq_setting->codec = codec; + if (p_eq_setting->is_active && p_eq_setting->data) + vbc_eq_try_apply(codec, vbc_idx); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = SPRD_VBC_MUX_IDX(FUN_REG(w->reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]); + int ret = 0; + + sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__, + vbc_mux_debug_str[id], mux->val, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mux->set = vbc_mux_cfg[id]; + ret = mux->set(mux->val); + break; + case SND_SOC_DAPM_PRE_PMD: + mux->set = 0; + ret = vbc_mux_cfg[id] (0); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int sprd_vbc_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]); + + ucontrol->value.enumerated.item[0] = mux->val; + + return 0; +} + +static int sprd_vbc_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg)); + unsigned int max = e->max; + unsigned int mask = (1 << fls(max)) - 1; + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]); + int ret = 0; + + if (mux->val == ucontrol->value.enumerated.item[0]) + return 0; + + sp_asoc_pr_info("Set MUX[%s] to %d\n", vbc_mux_debug_str[reg], + ucontrol->value.enumerated.item[0]); + + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + /*notice the sequence */ + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + /*update reg: must be set after snd_soc_dapm_put_enum_double->change = snd_soc_test_bits(widget->codec, e->reg, mask, val); */ + mux->val = (ucontrol->value.enumerated.item[0] & mask); + if (mux->set) { + ret = mux->set(mux->val); + } + + return ret; +} + +#define SPRD_VBC_MUX(xname, xenum) \ + SOC_DAPM_ENUM_EXT(xname, xenum, sprd_vbc_mux_get, sprd_vbc_mux_put) + +static const struct snd_kcontrol_new vbc_mux[SPRD_VBC_MUX_MAX] = { + SPRD_VBC_MUX("AD0 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD0_INMUX]), + SPRD_VBC_MUX("AD1 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD1_INMUX]), + SPRD_VBC_MUX("AD IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD_IISMUX]), +}; + +#define VBC_DAPM_MUX_E(wname, wreg) \ + SND_SOC_DAPM_MUX_E(wname, FUN_REG(wreg), 0, 0, &vbc_mux[wreg], mux_event, \ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD) + +static int vbc_loop_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg)); + ucontrol->value.integer.value[0] = vbc_codec->vbc_loop_switch[id]; + return 0; +} + +static int vbc_loop_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg)); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->vbc_loop_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC AD LOOP Switch Set %x\n", + (int)ucontrol->value.integer.value[0]); + + snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + vbc_codec->vbc_loop_switch[id] = ret; + + return ret; +} + +static const struct snd_kcontrol_new vbc_loop_control[] = { + SOC_SINGLE_EXT("Switch", + FUN_REG(VBC_AD_LOOP_SWITCH), + 0, 1, 0, + vbc_loop_switch_get, + vbc_loop_switch_put), +}; + +static const struct snd_soc_dapm_widget vbc_codec_dapm_widgets[] = { + /*power */ + SND_SOC_DAPM_SUPPLY("VBC Power", SND_SOC_NOPM, 0, 0, + vbc_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /*ADC inmux */ + VBC_DAPM_MUX_E("AD0 INMUX", SPRD_VBC_AD0_INMUX), + VBC_DAPM_MUX_E("AD1 INMUX", SPRD_VBC_AD1_INMUX), + + /*IIS MUX */ + VBC_DAPM_MUX_E("AD IISMUX", SPRD_VBC_AD_IISMUX), + + /*ST Switch */ + SND_SOC_DAPM_PGA_S("ST0 Switch", 4, SOC_REG(STCTL0), VBST_EN_0, 0, NULL, + 0), + SND_SOC_DAPM_PGA_S("ST1 Switch", 4, SOC_REG(STCTL1), VBST_EN_1, 0, NULL, + 0), + + /*FM Mixer */ + SND_SOC_DAPM_PGA_S("DA0 FM Mixer", 4, SOC_REG(DAPATCHCTL), + VBDAPATH_DA0_ADDFM_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("DA1 FM Mixer", 4, SOC_REG(DAPATCHCTL), + VBDAPATH_DA1_ADDFM_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_LINE("DFM", dfm_event), + + /*VBC Chan Switch */ + SND_SOC_DAPM_PGA_S("DA0 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_LEFT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("DA1 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_RIGHT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD0 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_LEFT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD1 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_RIGHT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /*AUD loop var vbc switch */ + SND_SOC_DAPM_PGA_S("Aud input", 4, FUN_REG(VBC_CAPTRUE), 0, 0, + aud_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SWITCH("Aud Loop in VBC", SND_SOC_NOPM, 0, 0, + &vbc_loop_control[SPRD_VBC_SWITCH_IDX + (VBC_AD_LOOP_SWITCH)]), +}; + +/* sprd_vbc supported interconnection*/ +static const struct snd_soc_dapm_route vbc_codec_intercon[] = { + /************************power********************************/ + /* digital fm playback need to open DA Clk and DA power */ + {"DFM", NULL, "VBC Power"}, + + /********************** capture in path in vbc ********************/ + /* AD input route */ + {"Aud Loop in VBC", "Switch", "Aud input"}, + + /* AD */ + {"AD IISMUX", "DIGFM", "Dig FM Jack"}, + {"AD IISMUX", "AUDIIS0", "Aud Loop in VBC"}, + + {"AD0 INMUX", "IIS0AD0", "AD IISMUX"}, + {"AD0 INMUX", "IIS1AD0", "AD IISMUX"}, + {"AD1 INMUX", "IIS1AD1", "AD IISMUX"}, + {"AD1 INMUX", "IIS0AD1", "AD IISMUX"}, + + {"AD0 Switch", NULL, "AD0 INMUX"}, + {"AD1 Switch", NULL, "AD1 INMUX"}, + + /********************** fm playback route ********************/ + /*ST route */ + {"ST0 INMUX", NULL, "AD0 Switch"}, + {"ST1 INMUX", NULL, "AD1 Switch"}, + + {"ST0 Switch", NULL, "ST0 INMUX"}, + {"ST1 Switch", NULL, "ST1 INMUX"}, + {"DA0 Switch", NULL, "ST0 Switch"}, + {"DA1 Switch", NULL, "ST1 Switch"}, + {"DA0 FM Mixer", NULL, "DA0 Switch"}, + {"DA1 FM Mixer", NULL, "DA1 Switch"}, + {"DFM", NULL, "DA0 FM Mixer"}, + {"DFM", NULL, "DA1 FM Mixer"}, +}; + +int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai) +{ + struct vbc_codec_priv *vbc_codec = dev_get_drvdata(dai->dev); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + sp_asoc_pr_dbg("%s %s\n", __func__, vbc_get_name(vbc_idx)); + + switch (vbc_idx) { + case VBC_PLAYBACK: + vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port); + break; + case VBC_CAPTRUE: + vbc_try_ad_iismux_set(vbc_codec->sprd_vbc_mux + [SPRD_VBC_AD_IISMUX].val); + vbc_try_ad_dgmux_set(vbc_codec, 0); + vbc_try_ad_dgmux_set(vbc_codec, 1); + break; + } + + if (p_eq_setting->is_active && p_eq_setting->data) + vbc_eq_try_apply(vbc_codec->codec, vbc_idx); + + vbc_try_dg_set(vbc_codec, vbc_idx, VBC_LEFT); + vbc_try_dg_set(vbc_codec, vbc_idx, VBC_RIGHT); + + return 0; +} + +/* + * proc interface + */ + +#ifdef CONFIG_PROC_FS +static void vbc_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int reg; + + snd_iprintf(buffer, "vbc register dump\n"); + for (reg = ARM_VB_BASE + 0x10; reg < ARM_VB_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - ARM_VB_BASE) + , vbc_reg_read(reg + 0x00) + , vbc_reg_read(reg + 0x04) + , vbc_reg_read(reg + 0x08) + , vbc_reg_read(reg + 0x0C) + ); + } +} + +static void vbc_proc_init(struct snd_soc_codec *codec) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(codec->card->snd_card, "vbc", &entry)) + snd_info_set_text_ops(entry, NULL, vbc_proc_read); +} +#else /* !CONFIG_PROC_FS */ +static inline void vbc_proc_init(struct snd_soc_codec *codec) +{ +} +#endif + +static int vbc_eq_reg_offset(u32 reg) +{ + int i = 0; + if ((reg >= DAPATCHCTL) && (reg <= ADDGCTL)) { + i = (reg - DAPATCHCTL) >> 2; + } else if ((reg >= HPCOEF0) && (reg <= HPCOEF42)) { + i = ((reg - HPCOEF0) >> 2) + ((ADDGCTL - DAPATCHCTL) >> 2) + 1; + } + BUG_ON(i >= VBC_EFFECT_PARAS_LEN); + return i; +} + +static inline void vbc_eq_reg_set(u32 reg, void *data) +{ + u32 *effect_paras = (u32 *) data; + vbc_reg_write(reg, effect_paras[vbc_eq_reg_offset(reg)]); +} + +static inline void vbc_eq_reg_set_range(u32 reg_start, u32 reg_end, void *data) +{ + u32 reg_addr; + for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) { + vbc_eq_reg_set(reg_addr, data); + } +} + +static void vbc_eq_reg_apply(struct snd_soc_codec *codec, void *data, + int vbc_idx) +{ + if (vbc_idx == VBC_PLAYBACK) { + vbc_eq_reg_set_range(DAALCCTL0, DAALCCTL10, data); + vbc_eq_reg_set_range(HPCOEF0, HPCOEF42, data); + + vbc_eq_reg_set(DAHPCTL, data); + /*FM function maybe use this regs,and EQ setting don't use them */ + /*vbc_da_eq_reg_set(DAPATCHCTL, data); */ + + /*vbc_da_eq_reg_set(STCTL0, data); */ + /*vbc_da_eq_reg_set(STCTL1, data); */ + + } else { + vbc_eq_reg_set(ADPATCHCTL, data); + } +} + +static void vbc_eq_profile_apply(struct snd_soc_codec *codec, void *data, + int vbc_idx) +{ + if (codec) { + vbc_eq_reg_apply(codec, data, vbc_idx); + } +} + +static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx) +{ + vbc_eq_profile_apply(codec, &vbc_eq_profile_default, vbc_idx); +} + +static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + u32 *data; + + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)p_eq_setting->vbc_eq_apply); + if (p_eq_setting->vbc_eq_apply) { + struct vbc_eq_profile *now; + mutex_lock(&vbc_codec->load_mutex); + now = &p_eq_setting->data[p_eq_setting->now_profile]; + data = now->effect_paras; + sp_asoc_pr_info("VBC %s EQ Apply '%s'\n", + vbc_get_name(vbc_idx), now->name); + p_eq_setting->vbc_eq_apply(codec, data, vbc_idx); + mutex_unlock(&vbc_codec->load_mutex); + } +} + +static int vbc_eq_profile_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + ucontrol->value.integer.value[0] = p_eq_setting->now_profile; + return 0; +} + +static int vbc_eq_profile_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int vbc_idx = FUN_REG(mc->reg); + int profile_max; + + profile_max = p_eq_setting->hdr.num_profile; + + ret = ucontrol->value.integer.value[0]; + if (ret == p_eq_setting->now_profile) { + return ret; + } + + sp_asoc_pr_info("VBC %s EQ Select %ld max %d\n", + vbc_get_name(vbc_idx), + ucontrol->value.integer.value[0], profile_max); + + if (ret < profile_max) { + p_eq_setting->now_profile = ret; + } + + if (p_eq_setting->is_active && p_eq_setting->data) + vbc_eq_try_apply(codec, vbc_idx); + + return ret; +} + +static int vbc_eq_loading(struct snd_soc_codec *codec) +{ + int ret; + const u8 *fw_data; + const struct firmware *fw; + int i; + int offset = 0; + int len = 0; + int old_num_profile; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + + sp_asoc_pr_dbg("%s\n", __func__); + mutex_lock(&vbc_codec->load_mutex); + p_eq_setting->is_loading = 1; + + /* request firmware for VBC EQ */ + ret = request_firmware(&fw, "vbc_eq", p_eq_setting->dev); + if (ret != 0) { + pr_err("ERR:Failed to load firmware: %d\n", ret); + goto req_fw_err; + } + fw_data = fw->data; + old_num_profile = p_eq_setting->hdr.num_profile; + + memcpy(&p_eq_setting->hdr, fw_data, sizeof(p_eq_setting->hdr)); + + if (strncmp + (p_eq_setting->hdr.magic, VBC_EQ_FIRMWARE_MAGIC_ID, + VBC_EQ_FIRMWARE_MAGIC_LEN)) { + pr_err("ERR:Firmware magic error!\n"); + ret = -EINVAL; + goto eq_out; + } + + if (p_eq_setting->hdr.profile_version != VBC_EQ_PROFILE_VERSION) { + pr_err("ERR:Firmware support version is 0x%x!\n", + VBC_EQ_PROFILE_VERSION); + ret = -EINVAL; + goto eq_out; + } + + if (p_eq_setting->hdr.num_profile > VBC_EQ_PROFILE_CNT_MAX) { + pr_err + ("ERR:Firmware profile to large at %d, max count is %d!\n", + p_eq_setting->hdr.num_profile, VBC_EQ_PROFILE_CNT_MAX); + ret = -EINVAL; + goto eq_out; + } + + if (old_num_profile != p_eq_setting->hdr.num_profile) { + vbc_safe_kfree(&p_eq_setting->data); + } + + len = p_eq_setting->hdr.num_profile * sizeof(struct vbc_eq_profile); + if (p_eq_setting->data == NULL) { + p_eq_setting->data = kzalloc(len, GFP_KERNEL); + if (p_eq_setting->data == NULL) { + ret = -ENOMEM; + goto eq_out; + } + } + offset = sizeof(struct vbc_fw_header); + memcpy(p_eq_setting->data, fw_data + offset, len); + + for (i = 0; i < p_eq_setting->hdr.num_profile; i++) { + const char *magic = (char *)p_eq_setting->data[i].magic; + if (strncmp + (magic, VBC_EQ_FIRMWARE_MAGIC_ID, + VBC_EQ_FIRMWARE_MAGIC_LEN)) { + pr_err + ("ERR:Firmware profile[%d] magic error!magic: %s \n", + i, magic); + ret = -EINVAL; + goto eq_out; + } + } + + ret = 0; + goto eq_out; + +eq_out: + release_firmware(fw); +req_fw_err: + p_eq_setting->is_loading = 0; + mutex_unlock(&vbc_codec->load_mutex); + if (ret >= 0) { + if (p_eq_setting->is_active && p_eq_setting->data) + vbc_eq_try_apply(codec, 0); + } + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static int vbc_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret; + ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE); + if (ret >= 0) + ucontrol->value.integer.value[0] = + (ret == AUDIO_TO_DSP_CTRL ? 0 : 1); + return ret; +} + +static int vbc_switch_reg_val[]; +static int vbc_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret; + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + + sp_asoc_pr_info("VBC Switch to %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + ret = ucontrol->value.integer.value[0]; + ret = arch_audio_vbc_switch(ret == 0 ? + AUDIO_TO_DSP_CTRL : AUDIO_TO_ARM_CTRL); + + return ret; +} + +static int vbc_eq_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + ucontrol->value.integer.value[0] = p_eq_setting->is_active; + return 0; +} + +static int vbc_eq_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int ret; + + ret = ucontrol->value.integer.value[0]; + if (ret == p_eq_setting->is_active) { + return ret; + } + + sp_asoc_pr_info("VBC EQ Switch %s\n", + STR_ON_OFF(ucontrol->value.integer.value[0])); + + if ((ret == 0) || (ret == 1)) { + p_eq_setting->is_active = ret; + if (p_eq_setting->is_active && p_eq_setting->data) { + p_eq_setting->vbc_eq_apply = vbc_eq_profile_apply; + vbc_eq_try_apply(codec, id); + } else { + p_eq_setting->vbc_eq_apply = 0; + vbc_eq_profile_close(codec, id); + } + } + + return ret; +} + +static int vbc_eq_load_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + ucontrol->value.integer.value[0] = p_eq_setting->is_loading; + return 0; +} + +static int vbc_eq_load_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int ret; + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + + sp_asoc_pr_info("VBC EQ %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + ret = ucontrol->value.integer.value[0]; + if (ret == 1) { + ret = vbc_eq_loading(codec); + } + + return ret; +} + +static int vbc_dg_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_val[id]; + return 0; +} + +static int vbc_dg_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->dg[vbc_idx].dg_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC %s%d DG set 0x%02x\n", + vbc_get_name(vbc_idx), id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->dg[vbc_idx].dg_val[id] = ret; + } + + vbc_try_dg_set(vbc_codec, vbc_idx, id); + + return ret; +} + +static int vbc_st_dg_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.dg_val[id]; + return 0; +} + +static int vbc_st_dg_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.dg_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d DG set 0x%02x\n", id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->st_dg.dg_val[id] = ret; + } + + vbc_try_st_dg_set(vbc_codec, id); + + return ret; +} + +static int vbc_dg_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_switch[id]; + return 0; +} + +static int vbc_dg_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->dg[vbc_idx].dg_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC %s%d DG Switch %s\n", + vbc_get_name(vbc_idx), id, + STR_ON_OFF(ucontrol->value.integer.value[0])); + + vbc_codec->dg[vbc_idx].dg_switch[id] = ret; + + vbc_try_dg_set(vbc_codec, vbc_idx, id); + + return ret; +} + +static int vbc_st_hpf_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_switch[id]; + return 0; +} + +static int vbc_st_hpf_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.hpf_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d HPF Switch %s\n", id, + STR_ON_OFF(ucontrol->value.integer.value[0])); + + vbc_codec->st_dg.hpf_switch[id] = ret; + + vbc_try_st_hpf_set(vbc_codec, id); + + return ret; +} + +static int vbc_st_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_val[id]; + return 0; +} + +static int vbc_st_hpf_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.hpf_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d HPF set 0x%02x\n", id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->st_dg.hpf_val[id] = ret; + } + + vbc_try_st_hpf_set(vbc_codec, id); + + return ret; +} + +static int adc_dgmux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->adc_dgmux_val[id]; + return 0; +} + +static int adc_dgmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->adc_dgmux_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC AD%d DG mux : %ld\n", id, + ucontrol->value.integer.value[0]); + + vbc_codec->adc_dgmux_val[id] = ret; + vbc_try_ad_dgmux_set(vbc_codec, id); + + return ret; +} + +static int dac_iismux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = vbc_codec->vbc_da_iis_port; + return 0; +} + +static int dac_iismux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + + if (vbc_codec->vbc_da_iis_port == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("VBC DA output to %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + vbc_codec->vbc_da_iis_port = ucontrol->value.integer.value[0]; + + vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port); + + return 1; +} + +static const char *switch_function[] = { "dsp", "arm" }; +static const char *eq_load_function[] = { "idle", "loading" }; +static const char *da_iis_mux_function[] = + { "sprd-codec", "ext-codec-4", "ext-codec-6" }; +static const char *fm_sample_rate_function[] = { "32000", "48000" }; + +static const struct soc_enum vbc_enum[] = { + SOC_ENUM_SINGLE_EXT(2, switch_function), + SOC_ENUM_SINGLE_EXT(2, eq_load_function), + SOC_ENUM_SINGLE_EXT(3, da_iis_mux_function), + SOC_ENUM_SINGLE_EXT(2, fm_sample_rate_function), +}; + +static const struct snd_kcontrol_new vbc_codec_snd_controls[] = { + SOC_ENUM_EXT("VBC Switch", vbc_enum[0], vbc_switch_get, + vbc_switch_put), + SOC_SINGLE_EXT("VBC EQ Switch", FUN_REG(VBC_PLAYBACK), 0, 1, 0, + vbc_eq_switch_get, + vbc_eq_switch_put), + SOC_ENUM_EXT("VBC EQ Update", vbc_enum[1], vbc_eq_load_get, + vbc_eq_load_put), + + SOC_SINGLE_EXT("VBC DACL DG Set", FUN_REG(VBC_LEFT), + VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC DACR DG Set", FUN_REG(VBC_RIGHT), + VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADCL DG Set", FUN_REG(VBC_LEFT), + VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADCR DG Set", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC STL DG Set", FUN_REG(VBC_LEFT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put), + SOC_SINGLE_EXT("VBC STR DG Set", FUN_REG(VBC_RIGHT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put), + + SOC_SINGLE_EXT("VBC DACL DG Switch", FUN_REG(VBC_LEFT), + VBC_PLAYBACK, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC DACR DG Switch", FUN_REG(VBC_RIGHT), + VBC_PLAYBACK, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADCL DG Switch", FUN_REG(VBC_LEFT), + VBC_CAPTRUE, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADCR DG Switch", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC STL HPF Switch", FUN_REG(VBC_LEFT), + 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put), + SOC_SINGLE_EXT("VBC STR HPF Switch", FUN_REG(VBC_RIGHT), + 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put), + SOC_SINGLE_EXT("VBC STL HPF Set", FUN_REG(VBC_LEFT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put), + SOC_SINGLE_EXT("VBC STR HPF Set", FUN_REG(VBC_RIGHT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put), + + SOC_SINGLE_EXT("VBC AD0 DG Mux", FUN_REG(ADC0_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_SINGLE_EXT("VBC AD1 DG Mux", FUN_REG(ADC1_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_ENUM_EXT("VBC DA IIS Mux", vbc_enum[2], dac_iismux_get, + dac_iismux_put), + SOC_SINGLE_EXT("VBC DA EQ Profile Select", FUN_REG(VBC_PLAYBACK), 0, + VBC_EQ_PROFILE_CNT_MAX, 0, + vbc_eq_profile_get, vbc_eq_profile_put), + +}; + +static unsigned int vbc_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + /* Because snd_soc_update_bits reg is 16 bits short type, + so muse do following convert + */ + if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) { + reg |= SPRD_VBC_BASE_HI; + return vbc_reg_read(reg); + } else if (IS_SPRD_VBC_MUX_RANG(FUN_REG(reg))) { + int id = SPRD_VBC_MUX_IDX(FUN_REG(reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]); + return mux->val; + } else if (IS_SPRD_VBC_SWITCH_RANG(FUN_REG(reg))) { + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(reg)); + return vbc_codec->vbc_loop_switch[id]; + } + + sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg); + return 0; +} + +static int vbc_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) { + reg |= SPRD_VBC_BASE_HI; + return vbc_reg_write(reg, val); + } + sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg); + return 0; +} + +static int vbc_codec_soc_probe(struct snd_soc_codec *codec) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + codec->dapm.idle_bias_off = 1; + + vbc_codec->codec = codec; + p_eq_setting->codec = codec; + + vbc_proc_init(codec); + + return ret; +} + +/* power down chip */ +static int vbc_codec_soc_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_vbc_codec = { + .probe = vbc_codec_soc_probe, + .remove = vbc_codec_soc_remove, + .read = vbc_codec_read, + .write = vbc_codec_write, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .dapm_widgets = vbc_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(vbc_codec_dapm_widgets), + .dapm_routes = vbc_codec_intercon, + .num_dapm_routes = ARRAY_SIZE(vbc_codec_intercon), + .controls = vbc_codec_snd_controls, + .num_controls = ARRAY_SIZE(vbc_codec_snd_controls), +}; + +static int sprd_vbc_codec_probe(struct platform_device *pdev) +{ + struct vbc_codec_priv *vbc_codec; + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + vbc_codec = devm_kzalloc(&pdev->dev, sizeof(struct vbc_codec_priv), + GFP_KERNEL); + if (vbc_codec == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, vbc_codec); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_vbc_codec, NULL, 0); + if (ret != 0) { + pr_err("ERR:Failed to register VBC-CODEC: %d\n", ret); + } + + /* AP refer to array vbc_switch_reg_val */ + vbc_codec->vbc_control = 2; + vbc_codec->vbc_eq_setting.dev = &pdev->dev; + vbc_codec->st_dg.dg_val[0] = 0x18; + vbc_codec->st_dg.dg_val[1] = 0x18; + vbc_codec->st_dg.hpf_val[0] = 0x3; + vbc_codec->st_dg.hpf_val[1] = 0x3; + vbc_codec->dg[0].dg_val[0] = 0x18; + vbc_codec->dg[0].dg_val[1] = 0x18; + vbc_codec->dg[1].dg_val[0] = 0x18; + vbc_codec->dg[1].dg_val[1] = 0x18; + vbc_codec->dg[0].dg_set[0] = vbc_da0_dg_set; + vbc_codec->dg[0].dg_set[1] = vbc_da1_dg_set; + vbc_codec->dg[1].dg_set[0] = vbc_ad0_dg_set; + vbc_codec->dg[1].dg_set[1] = vbc_ad1_dg_set; + mutex_init(&vbc_codec->load_mutex); + + return ret; +} + +static int sprd_vbc_codec_remove(struct platform_device *pdev) +{ + struct vbc_codec_priv *vbc_codec = platform_get_drvdata(pdev); + vbc_codec->vbc_eq_setting.dev = 0; + + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +MODULE_DESCRIPTION("SPRD ASoC VBC Codec Driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_AUTHOR("Ken.Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:VBC Codec"); diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h new file mode 100644 index 00000000..277445ee --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h @@ -0,0 +1,29 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h + * + * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_CODEC_H +#define __VBC_CODEC_H + +#include "vbc-comm.h" + +#define VBC_EQ_FIRMWARE_MAGIC_LEN (4) +#define VBC_EQ_FIRMWARE_MAGIC_ID ("VBEQ") +#define VBC_EQ_PROFILE_VERSION (0x00000001) +#define VBC_EQ_PROFILE_CNT_MAX (100) +#define VBC_EQ_PROFILE_NAME_MAX (32) +#define VBC_EFFECT_PARAS_LEN (61) + +#endif /* __VBC_CODEC_H */ diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c new file mode 100644 index 00000000..6123ded2 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c @@ -0,0 +1,229 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c + * + * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <sound/soc.h> + +#include "sprd-asoc-common.h" +#include "vbc-comm.h" + +static DEFINE_SPINLOCK(vbc_lock); + +/* vbc local power suppliy and chan on */ +static struct vbc_refcount { + atomic_t vbc_power_on; /*vbc reg power enable */ + atomic_t vbc_on; /*vbc enable */ + atomic_t chan_on[VBC_IDX_MAX][2]; +} vbc_refcnt; + +static const char *sprd_vbc_name[VBC_IDX_MAX] = { + "DAC", + "ADC", +}; + +static inline const char *vbc_get_name(int vbc_idx) +{ + return sprd_vbc_name[vbc_idx]; +} + +static inline int vbc_reg_read(unsigned int reg) +{ + return __raw_readl((void *__iomem)reg); +} + +static inline void vbc_reg_raw_write(unsigned int reg, int val) +{ + __raw_writel(val, (void *__iomem)reg); +} + +static int vbc_reg_write(unsigned int reg, int val) +{ + spin_lock(&vbc_lock); + vbc_reg_raw_write(reg, val); + spin_unlock(&vbc_lock); + sp_asoc_pr_reg("[0x%04x] W:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, val, + vbc_reg_read(reg)); + return 0; +} + +/* + * Returns 1 for change, 0 for no change, or negative error code. + */ +static int vbc_reg_update(unsigned int reg, int val, int mask) +{ + int new, old; + spin_lock(&vbc_lock); + old = vbc_reg_read(reg); + new = (old & ~mask) | (val & mask); + vbc_reg_raw_write(reg, new); + spin_unlock(&vbc_lock); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new, + vbc_reg_read(reg)); + return old != new; +} + +static struct clk *s_vbc_clk = 0; +static void vbc_clk_set(struct clk *clk) +{ + s_vbc_clk = clk; +} + +static struct clk *vbc_clk_get(void) +{ + return s_vbc_clk; +} + +static inline void vbc_reg_enable(void) +{ + if (s_vbc_clk) { + clk_enable(s_vbc_clk); + } else { + arch_audio_vbc_reg_enable(); + } +} + +static inline void vbc_reg_disable(void) +{ + if (s_vbc_clk) { + clk_disable(s_vbc_clk); + } else { + arch_audio_vbc_reg_disable(); + } +} + +static int vbc_power(int enable) +{ + int i; + atomic_t *vbc_power_on = &vbc_refcnt.vbc_power_on; + if (enable) { + atomic_inc(vbc_power_on); + if (atomic_read(vbc_power_on) == 1) { + arch_audio_vbc_enable(); + vbc_reg_enable(); + for (i = 0; i < SPRD_VBC_PLAYBACK_COUNT; i++) { + vbc_da_buffer_clear_all(i); + } + sp_asoc_pr_dbg("VBC Power On\n"); + } + } else { + if (atomic_dec_and_test(vbc_power_on)) { + arch_audio_vbc_reset(); + arch_audio_vbc_disable(); + vbc_reg_disable(); + sp_asoc_pr_dbg("VBC Power Off\n"); + } + if (atomic_read(vbc_power_on) < 0) { + atomic_set(vbc_power_on, 0); + } + } + sp_asoc_pr_dbg("VBC Power REF: %d", atomic_read(vbc_power_on)); + return 0; +} + +static inline int vbc_da_enable_raw(int enable, int chan) +{ + int ret; + if (enable) { + ret = arch_audio_vbc_da_enable(chan); + } else { + ret = arch_audio_vbc_da_disable(chan); + } + return ret; +} + +static inline int vbc_ad_enable_raw(int enable, int chan) +{ + int ret; + if (enable) { + ret = arch_audio_vbc_ad_enable(chan); + } else { + ret = arch_audio_vbc_ad_disable(chan); + } + return ret; +} + +static inline int vbc_enable_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBENABLE), + (1 << VBENABLE)); + return 0; +} + +typedef int (*vbc_chan_enable_raw) (int enable, int chan); +static vbc_chan_enable_raw vbc_chan_enable_fun[VBC_IDX_MAX] = { + vbc_da_enable_raw, + vbc_ad_enable_raw, +}; + +static int vbc_chan_enable(int enable, int vbc_idx, int chan) +{ + atomic_t *chan_on = &vbc_refcnt.chan_on[vbc_idx][chan]; + if (enable) { + atomic_inc(chan_on); + if (atomic_read(chan_on) == 1) { + vbc_chan_enable_fun[vbc_idx] (1, chan); + sp_asoc_pr_dbg("VBC %s%d On\n", vbc_get_name(vbc_idx), + chan); + } + } else { + if (atomic_dec_and_test(chan_on)) { + vbc_chan_enable_fun[vbc_idx] (0, chan); + sp_asoc_pr_dbg("VBC %s%d Off\n", vbc_get_name(vbc_idx), + chan); + } + if (atomic_read(chan_on) < 0) { + atomic_set(chan_on, 0); + } + } + sp_asoc_pr_dbg("%s%d REF: %d", vbc_get_name(vbc_idx), chan, + atomic_read(chan_on)); + return 0; +} + +static int vbc_enable(int enable) +{ + atomic_t *vbc_on = &vbc_refcnt.vbc_on; + if (enable) { + atomic_inc(vbc_on); + if (atomic_read(vbc_on) == 1) { + vbc_enable_set(1); + sp_asoc_pr_dbg("VBC Enable\n"); + } + } else { + if (atomic_dec_and_test(vbc_on)) { + vbc_enable_set(0); + sp_asoc_pr_dbg("VBC Disable"); + } + if (atomic_read(vbc_on) < 0) { + atomic_set(vbc_on, 0); + } + } + + sp_asoc_pr_dbg("VBC EN REF: %d", atomic_read(vbc_on)); + return 0; +} diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h new file mode 100644 index 00000000..47299ea6 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h @@ -0,0 +1,219 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h + * + * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_COMM_H +#define __VBC_COMM_H + +#include <mach/sprd-audio.h> + +#define VBC_VERSION "vbc.r1p0" + +/* VBADBUFFDTA */ +enum { + VBISADCK_INV = 9, + VBISDACK_INV, + VBLSB_EB, + VBIIS_DLOOP = 13, + VBPCM_MODE, + VBIIS_LRCK, +}; +/* VBDABUFFDTA */ +enum { + RAMSW_NUMB = 9, + RAMSW_EN, + VBAD0DMA_EN, + VBAD1DMA_EN, + VBDA0DMA_EN, + VBDA1DMA_EN, + VBENABLE, +}; + +/* VBDAPATH FM MIXER*/ +enum { + DAPATH_NO_MIX = 0, + DAPATH_ADD_FM, + DAPATH_SUB_FM, +}; + +/* AD DGMUX NUM*/ +enum { + ADC0_DGMUX = 0, + ADC1_DGMUX, + ADC_DGMUX_MAX +}; + +/* -------------------------- */ + +#define PHYS_VBDA0 (VBC_PHY_BASE + 0x0000) /* 0x0000 Voice band DAC0 data buffer */ +#define PHYS_VBDA1 (VBC_PHY_BASE + 0x0004) /* 0x0004 Voice band DAC1 data buffer */ +#define PHYS_VBAD0 (VBC_PHY_BASE + 0x0008) /* 0x0008 Voice band ADC0 data buffer */ +#define PHYS_VBAD1 (VBC_PHY_BASE + 0x000C) /* 0x000C Voice band ADC1 data buffer */ + +#define ARM_VB_BASE VBC_BASE +#define VBDA0 (ARM_VB_BASE + 0x0000) /* Voice band DAC0 data buffer */ +#define VBDA1 (ARM_VB_BASE + 0x0004) /* Voice band DAC1 data buffer */ +#define VBAD0 (ARM_VB_BASE + 0x0008) /* Voice band ADC0 data buffer */ +#define VBAD1 (ARM_VB_BASE + 0x000C) /* Voice band ADC1 data buffer */ +#define VBBUFFSIZE (ARM_VB_BASE + 0x0010) /* Voice band buffer size */ +#define VBADBUFFDTA (ARM_VB_BASE + 0x0014) /* Voice band AD buffer control */ +#define VBDABUFFDTA (ARM_VB_BASE + 0x0018) /* Voice band DA buffer control */ +#define VBADCNT (ARM_VB_BASE + 0x001C) /* Voice band AD buffer counter */ +#define VBDACNT (ARM_VB_BASE + 0x0020) /* Voice band DA buffer counter */ +#define VBINTTYPE (ARM_VB_BASE + 0x0034) +#define VBDATASWT (ARM_VB_BASE + 0x0038) +#define VBIISSEL (ARM_VB_BASE + 0x003C) + +#define DAPATCHCTL (ARM_VB_BASE + 0x0040) +#define DADGCTL (ARM_VB_BASE + 0x0044) +#define DAHPCTL (ARM_VB_BASE + 0x0048) +#define DAALCCTL0 (ARM_VB_BASE + 0x004C) +#define DAALCCTL1 (ARM_VB_BASE + 0x0050) +#define DAALCCTL2 (ARM_VB_BASE + 0x0054) +#define DAALCCTL3 (ARM_VB_BASE + 0x0058) +#define DAALCCTL4 (ARM_VB_BASE + 0x005C) +#define DAALCCTL5 (ARM_VB_BASE + 0x0060) +#define DAALCCTL6 (ARM_VB_BASE + 0x0064) +#define DAALCCTL7 (ARM_VB_BASE + 0x0068) +#define DAALCCTL8 (ARM_VB_BASE + 0x006C) +#define DAALCCTL9 (ARM_VB_BASE + 0x0070) +#define DAALCCTL10 (ARM_VB_BASE + 0x0074) +#define STCTL0 (ARM_VB_BASE + 0x0078) +#define STCTL1 (ARM_VB_BASE + 0x007C) +#define ADPATCHCTL (ARM_VB_BASE + 0x0080) +#define ADDGCTL (ARM_VB_BASE + 0x0084) +#define HPCOEF0 (ARM_VB_BASE + 0x0100) +#define HPCOEF1 (ARM_VB_BASE + 0x0104) +#define HPCOEF2 (ARM_VB_BASE + 0x0108) +#define HPCOEF3 (ARM_VB_BASE + 0x010C) +#define HPCOEF4 (ARM_VB_BASE + 0x0110) +#define HPCOEF5 (ARM_VB_BASE + 0x0114) +#define HPCOEF6 (ARM_VB_BASE + 0x0118) +#define HPCOEF7 (ARM_VB_BASE + 0x011C) +#define HPCOEF8 (ARM_VB_BASE + 0x0120) +#define HPCOEF9 (ARM_VB_BASE + 0x0124) +#define HPCOEF10 (ARM_VB_BASE + 0x0128) +#define HPCOEF11 (ARM_VB_BASE + 0x012C) +#define HPCOEF12 (ARM_VB_BASE + 0x0130) +#define HPCOEF13 (ARM_VB_BASE + 0x0134) +#define HPCOEF14 (ARM_VB_BASE + 0x0138) +#define HPCOEF15 (ARM_VB_BASE + 0x013C) +#define HPCOEF16 (ARM_VB_BASE + 0x0140) +#define HPCOEF17 (ARM_VB_BASE + 0x0144) +#define HPCOEF18 (ARM_VB_BASE + 0x0148) +#define HPCOEF19 (ARM_VB_BASE + 0x014C) +#define HPCOEF20 (ARM_VB_BASE + 0x0150) +#define HPCOEF21 (ARM_VB_BASE + 0x0154) +#define HPCOEF22 (ARM_VB_BASE + 0x0158) +#define HPCOEF23 (ARM_VB_BASE + 0x015C) +#define HPCOEF24 (ARM_VB_BASE + 0x0160) +#define HPCOEF25 (ARM_VB_BASE + 0x0164) +#define HPCOEF26 (ARM_VB_BASE + 0x0168) +#define HPCOEF27 (ARM_VB_BASE + 0x016C) +#define HPCOEF28 (ARM_VB_BASE + 0x0170) +#define HPCOEF29 (ARM_VB_BASE + 0x0174) +#define HPCOEF30 (ARM_VB_BASE + 0x0178) +#define HPCOEF31 (ARM_VB_BASE + 0x017C) +#define HPCOEF32 (ARM_VB_BASE + 0x0180) +#define HPCOEF33 (ARM_VB_BASE + 0x0184) +#define HPCOEF34 (ARM_VB_BASE + 0x0188) +#define HPCOEF35 (ARM_VB_BASE + 0x018C) +#define HPCOEF36 (ARM_VB_BASE + 0x0190) +#define HPCOEF37 (ARM_VB_BASE + 0x0194) +#define HPCOEF38 (ARM_VB_BASE + 0x0198) +#define HPCOEF39 (ARM_VB_BASE + 0x019C) +#define HPCOEF40 (ARM_VB_BASE + 0x01A0) +#define HPCOEF41 (ARM_VB_BASE + 0x01A4) +#define HPCOEF42 (ARM_VB_BASE + 0x01A8) + +#define ARM_VB_END (ARM_VB_BASE + 0x01AC) +#define IS_SPRD_VBC_RANG(reg) (((reg) >= ARM_VB_BASE) && ((reg) < ARM_VB_END)) +#define SPRD_VBC_BASE_HI (ARM_VB_BASE & 0xFFFF0000) + +#define VBADBUFFERSIZE_SHIFT (0) +#define VBADBUFFERSIZE_MASK (0xFF<<VBADBUFFERSIZE_SHIFT) +#define VBDABUFFERSIZE_SHIFT (8) +#define VBDABUFFERSIZE_MASK (0xFF<<VBDABUFFERSIZE_SHIFT) + +#define VBDACHEN_SHIFT (0) +#define VBADCHEN_SHIFT (2) + + /*VBIISSEL*/ +#define VBIISSEL_AD_PORT_SHIFT (0) +#define VBIISSEL_AD_PORT_MASK (0x7) +#define VBIISSEL_DA_PORT_SHIFT (3) +#define VBIISSEL_DA_PORT_MASK (0x7<<VBIISSEL_DA_PORT_SHIFT) +#define VBIISSEL_DA_LRCK (9) +#define VBIISSEL_AD_LRCK (8) + /*DAPATHCTL*/ +#define VBDAPATH_DA0_ADDFM_SHIFT (0) +#define VBDAPATH_DA0_ADDFM_MASK (0x3<<VBDAPATH_DA0_ADDFM_SHIFT) +#define VBDAPATH_DA1_ADDFM_SHIFT (2) +#define VBDAPATH_DA1_ADDFM_MASK (0x3<<VBDAPATH_DA1_ADDFM_SHIFT) + /*ADPATHCTL*/ +#define VBADPATH_AD0_INMUX_SHIFT (0) +#define VBADPATH_AD0_INMUX_MASK (0x3<<VBADPATH_AD0_INMUX_SHIFT) +#define VBADPATH_AD1_INMUX_SHIFT (2) +#define VBADPATH_AD1_INMUX_MASK (0x3<<VBADPATH_AD1_INMUX_SHIFT) +#define VBADPATH_AD0_DGMUX_SHIFT (4) +#define VBADPATH_AD0_DGMUX_MASK (0x1<<VBADPATH_AD0_DGMUX_SHIFT) +#define VBADPATH_AD1_DGMUX_SHIFT (5) +#define VBADPATH_AD1_DGMUX_MASK (0x1<<VBADPATH_AD1_DGMUX_SHIFT) +/*STCTL0 */ +#define VBST_EN_0 (12) +#define VBST_HPF_0 (11) +/*STCTL1 */ +#define VBST_HPF_1 (11) +#define VBST_EN_1 (12) +#define VBST0_SEL_CHN (13) +#define VBST1_SEL_CHN (14) + enum { + VBC_LEFT = 0, + VBC_RIGHT = 1, +}; + +enum { + VBC_PLAYBACK = 0, + SPRD_VBC_PLAYBACK_COUNT = 1, + VBC_CAPTRUE = 1, + VBC_IDX_MAX +}; + +static const char *vbc_get_name(int vbc_idx); + +static int vbc_reg_read(unsigned int reg); +static void vbc_reg_raw_write(unsigned int reg, int val); +static int vbc_reg_write(unsigned int reg, int val); +static int vbc_reg_update(unsigned int reg, int val, int mask); +static void vbc_da_buffer_clear_all(int vbc_idx); +static int vbc_enable(int enable); +static void vbc_clk_set(struct clk *clk); +static struct clk *vbc_clk_get(void); +static int vbc_power(int enable); +static int vbc_chan_enable(int enable, int vbc_idx, int chan); +static int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai); +static int fm_set_vbc_buffer_size(void); + +static inline void vbc_safe_mem_release(void **free) +{ + if (*free) { + kfree(*free); + *free = NULL; + } +} + +#define vbc_safe_kfree(p) vbc_safe_mem_release((void**)p) + +#endif /* __VBC_COMM_H */ diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc.c b/sound/soc/sprd/dai/vbc/r1p0/vbc.c new file mode 100644 index 00000000..5c857b90 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc.c @@ -0,0 +1,637 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc.c + * + * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" VBC ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "sprd-asoc-common.h" +#include "sprd-pcm.h" +#include "vbc.h" +#include "dfm.h" + +typedef int (*vbc_dma_set) (int enable); + +struct sprd_vbc_buffer_info { + int reg; + int shift; + int mask; + int max; +}; + +struct sprd_vbc_priv { + vbc_dma_set dma_set[2]; + int (*arch_enable) (int chan); + int (*arch_disable) (int chan); + int used_chan_count; + struct sprd_vbc_buffer_info buf_info; + int rate; +}; + +struct sprd_dfm_priv dfm = { 0, 0 }; + +EXPORT_SYMBOL_GPL(dfm); + +static struct sprd_pcm_dma_params vbc_pcm_stereo_out = { + .name = "VBC PCM Stereo out", + .irq_type = BLK_DONE, + .desc = { + .datawidth = SHORT_WIDTH, + .fragmens_len = VBC_FIFO_FRAME_NUM * 2, + .src_step = 2, + .des_step = 0, + }, + .dev_paddr = {PHYS_VBDA0, PHYS_VBDA1}, +}; + +static struct sprd_pcm_dma_params vbc_pcm_stereo_in = { + .name = "VBC PCM Stereo in", + .irq_type = BLK_DONE, + .desc = { + .datawidth = SHORT_WIDTH, + .fragmens_len = VBC_FIFO_FRAME_NUM * 2, + .src_step = 0, + .des_step = 2, + }, + .dev_paddr = {PHYS_VBAD0, PHYS_VBAD1}, +}; + +static struct sprd_vbc_priv vbc[VBC_IDX_MAX]; + +static int vbc_iis_high_for_da1(int enable) +{ + vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_DA_LRCK, + 1 << VBIISSEL_DA_LRCK); + return 0; +} + +#if 0 +static int vbc_iis_high_for_ad1(int enable) +{ + vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_AD_LRCK, + 1 << VBIISSEL_AD_LRCK); + return 0; +} +#endif + +static int vbc_set_buffer_size(int vbc_idx, int buffer_size) +{ + int val = vbc_reg_read(vbc[vbc_idx].buf_info.reg); + WARN_ON(buffer_size > vbc[vbc_idx].buf_info.max); + if ((buffer_size > 0) + && (buffer_size <= vbc[vbc_idx].buf_info.max)) { + val &= ~(vbc[vbc_idx].buf_info.mask); + val |= (((buffer_size - 1) << vbc[vbc_idx].buf_info.shift) + & vbc[vbc_idx].buf_info.mask); + vbc_reg_update(vbc[vbc_idx].buf_info.reg, val, + vbc[vbc_idx].buf_info.mask); + } else { + pr_err("ERR:buffer_size error! %d\n", buffer_size); + return -EINVAL; + } + return 0; +} + +static int fm_set_vbc_buffer_size(void) +{ + int i; + for (i = SPRD_VBC_PLAYBACK_COUNT; i < VBC_IDX_MAX; i++) { + vbc_set_buffer_size(i, VBC_FIFO_FRAME_NUM); + } + return 0; +} + +static inline int vbc_sw_write_buffer(int enable) +{ + /* Software access ping-pong buffer enable when VBENABE bit low */ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << RAMSW_EN), + (1 << RAMSW_EN)); + return 0; +} + +static inline int vbc_ad0_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD0DMA_EN), + (1 << VBAD0DMA_EN)); + return 0; +} + +static inline int vbc_ad1_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD1DMA_EN), + (1 << VBAD1DMA_EN)); + return 0; +} + +static inline int vbc_da0_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA0DMA_EN), + (1 << VBDA0DMA_EN)); + return 0; +} + +static inline int vbc_da1_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA1DMA_EN), + (1 << VBDA1DMA_EN)); + return 0; +} + +static void vbc_da_buffer_clear(int id) +{ + int i; + vbc_reg_update(VBDABUFFDTA, ((id ? 1 : 0) << RAMSW_NUMB), + (1 << RAMSW_NUMB)); + for (i = 0; i < VBC_FIFO_FRAME_NUM; i++) { + vbc_reg_raw_write(VBDA0, 0); + vbc_reg_raw_write(VBDA1, 0); + } +} + +static void vbc_da_buffer_clear_all(int vbc_idx) +{ + int i; + + for (i = 0; i < 2; i++) { + vbc[vbc_idx].arch_enable(i); + } + + vbc_sw_write_buffer(true); + vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM); + vbc_da_buffer_clear(1); /* clear data buffer 1 */ + vbc_da_buffer_clear(0); /* clear data buffer 0 */ + vbc_sw_write_buffer(false); + + for (i = 0; i < 2; i++) { + vbc[vbc_idx].arch_disable(i); + } +} + +static int vbc_da_arch_enable(int chan) +{ + return vbc_chan_enable(1, VBC_PLAYBACK, chan); +} + +static int vbc_da_arch_disable(int chan) +{ + return vbc_chan_enable(0, VBC_PLAYBACK, chan); +} + +static int vbc_ad_arch_enable(int chan) +{ + return vbc_chan_enable(1, VBC_CAPTRUE, chan); +} + +static int vbc_ad_arch_disable(int chan) +{ + return vbc_chan_enable(0, VBC_CAPTRUE, chan); +} + +static struct sprd_vbc_priv vbc[VBC_IDX_MAX] = { + /* All Playback */ + { /*PlayBack */ + .dma_set = {vbc_da0_dma_set, vbc_da1_dma_set}, + .arch_enable = vbc_da_arch_enable, + .arch_disable = vbc_da_arch_disable, + .buf_info = {VBBUFFSIZE, VBDABUFFERSIZE_SHIFT, VBDABUFFERSIZE_MASK, + VBC_FIFO_FRAME_NUM}, + }, + + /* All Capture */ + { /*Capture for ad01 */ + .dma_set = {vbc_ad0_dma_set, vbc_ad1_dma_set}, + .arch_enable = vbc_ad_arch_enable, + .arch_disable = vbc_ad_arch_disable, + .buf_info = {VBBUFFSIZE, VBADBUFFERSIZE_SHIFT, VBADBUFFERSIZE_MASK, + VBC_FIFO_FRAME_NUM}, + }, +}; + +/* NOTE: + this index need use for the [struct sprd_vbc_priv] vbc[2] index + default MUST return 0. + */ +static inline int vbc_str_2_index(int stream, int id) +{ + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + return id + SPRD_VBC_PLAYBACK_COUNT; + } + return id; +} + +static int vbc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + int ret; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + sp_asoc_pr_dbg("dfm.hw_rate:%d, dfm.sample_rate:%d", dfm.hw_rate, + dfm.sample_rate); + + if (dfm.sample_rate != 0) { + if ((vbc_idx == VBC_PLAYBACK) || (vbc_idx == VBC_CAPTRUE)) { + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + dfm.sample_rate, + dfm.sample_rate); + if (ret < 0) { + pr_err("constraint error"); + return ret; + } + } + } + + vbc_power(1); + + vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM); + vbc_codec_startup(vbc_idx, dai); + + WARN_ON(!vbc[vbc_idx].arch_enable); + WARN_ON(!vbc[vbc_idx].arch_disable); + WARN_ON(!vbc[vbc_idx].dma_set[0]); + WARN_ON(!vbc[vbc_idx].dma_set[1]); + + return 0; +} + +static void vbc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + /* vbc da close MUST clear da buffer */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + vbc_da_buffer_clear_all(vbc_idx); + } + + vbc_power(0); +} + +static int vbc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int vbc_idx; + struct sprd_pcm_dma_params *dma_data[VBC_IDX_MAX] = { + &vbc_pcm_stereo_out, + &vbc_pcm_stereo_in, + }; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + snd_soc_dai_set_dma_data(dai, substream, dma_data[vbc_idx]); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + pr_err("ERR:VBC Only Supports Format S16_LE\n"); + break; + } + + vbc[vbc_idx].used_chan_count = params_channels(params); + if (vbc[vbc_idx].used_chan_count > 2) { + pr_err("ERR:VBC Can NOT Supports Grate 2 Channels\n"); + } + + vbc_iis_high_for_da1(1); +#ifdef CONFIG_SND_SOC_SPRD_VBC_LR_INVERT + if (vbc[vbc_idx].used_chan_count == 2) { + vbc_iis_high_for_da1(0); + } +#endif + vbc[vbc_idx].rate = params_rate(params); + return 0; +} + +static int vbc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + vbc[vbc_idx].rate = 0; + + return 0; +} + +static int vbc_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int vbc_idx; + int ret = 0; + int i; + +#if 0 + sp_asoc_pr_dbg("%s\n", __func__); +#endif + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) { + vbc[vbc_idx].arch_enable(i); + vbc[vbc_idx].dma_set[i] (1); + } + vbc_enable(1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) { + vbc[vbc_idx].arch_disable(i); + vbc[vbc_idx].dma_set[i] (0); + } + vbc_enable(0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct snd_soc_dai_ops vbc_dai_ops = { + .startup = vbc_startup, + .shutdown = vbc_shutdown, + .hw_params = vbc_hw_params, + .trigger = vbc_trigger, + .hw_free = vbc_hw_free, +}; + +static int dfm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + static const unsigned int dfm_all_rates[] = { 32000, 44100, 48000 }; + static const struct snd_pcm_hw_constraint_list dfm_rates_constraint = { + .count = ARRAY_SIZE(dfm_all_rates), + .list = dfm_all_rates, + }; + int ret; + int vbc_idx; + struct snd_soc_card *card = dai->card; + int i; + + sp_asoc_pr_dbg("%s\n, vbc[vbc_idx].rate:da:%d, ad:%d, ad23:%d", + __func__, vbc[0].rate, vbc[1].rate, vbc[2].rate); + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &dfm_rates_constraint); + if (ret < 0) + return ret; + + for (vbc_idx = VBC_PLAYBACK; vbc_idx < VBC_CAPTRUE1; vbc_idx++) { + if (vbc[vbc_idx].rate != 0) { + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + vbc[vbc_idx].rate, + vbc[vbc_idx].rate); + if (ret < 0) { + pr_err("constraint error"); + return ret; + } + } + } + + kfree(snd_soc_dai_get_dma_data(dai, substream)); + snd_soc_dai_set_dma_data(dai, substream, NULL); + + snd_soc_dapm_enable_pin(&dai->codec->dapm, "DFM"); + snd_soc_dapm_sync(&dai->codec->dapm); + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 1; + } + snd_soc_dapm_ignore_suspend(&dai->codec->dapm, "DFM"); + + return 0; +} + +static void dfm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = dai->card; + int i; + sp_asoc_pr_dbg("%s\n", __func__); + + snd_soc_dapm_disable_pin(&dai->codec->dapm, "DFM"); + snd_soc_dapm_sync(&dai->codec->dapm); + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 0; + } +} + +static int dfm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + sp_asoc_pr_dbg("%s \n", __func__); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + pr_err("ERR:VBC Only Supports Format S16_LE\n"); + break; + } + + return 0; +} + +static int dfm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + sp_asoc_pr_dbg("%s \n", __func__); + dfm.sample_rate = 0; + dfm.hw_rate = 0; + return 0; +} + +static struct snd_soc_dai_ops dfm_dai_ops = { + .startup = dfm_startup, + .shutdown = dfm_shutdown, + .hw_params = dfm_hw_params, + .hw_free = dfm_hw_free, +}; + +static struct snd_soc_dai_driver vbc_dai[] = { + { + .name = "vbc-r1p0", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, /* AD01 */ + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &vbc_dai_ops, + }, + { + .name = "vbc-dfm", + .id = DFM_MAGIC_ID, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &dfm_dai_ops, + } +}; + +static const struct snd_soc_component_driver sprd_vbc_component = { + .name = "vbc", +}; + +static int sprd_vbc_codec_probe(struct platform_device *pdev); +static int vbc_drv_probe(struct platform_device *pdev) +{ + int i; + int ret; + struct clk *vbc_clk; + + sp_asoc_pr_dbg("%s\n", __func__); + + arch_audio_vbc_switch(AUDIO_TO_ARM_CTRL); + ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE); + if (ret != AUDIO_TO_ARM_CTRL) { + pr_err("Failed to Switch VBC to AP\n"); + return -1; + } + + for (i = 0; i < 2; i++) { + vbc_pcm_stereo_out.channels[i] = arch_audio_vbc_da_dma_info(i); + vbc_pcm_stereo_in.channels[i] = arch_audio_vbc_ad_dma_info(i); + } + + /* 1. probe CODEC */ + ret = sprd_vbc_codec_probe(pdev); + + if (ret < 0) { + goto probe_err; + } + + /* 2. probe DAIS */ + ret = + snd_soc_register_component(&pdev->dev, &sprd_vbc_component, vbc_dai, + ARRAY_SIZE(vbc_dai)); + + if (ret < 0) { + pr_err("ERR:Register VBC to DAIS Failed!\n"); + goto probe_err; + } + + vbc_clk = clk_get(&pdev->dev, "clk_vbc"); + if (IS_ERR(vbc_clk)) { + pr_err("Cannot request clk_vbc\n"); + } else { + vbc_clk_set(vbc_clk); + } + +probe_err: + sp_asoc_pr_dbg("return %i\n", ret); + + return ret; +} + +static int sprd_vbc_codec_remove(struct platform_device *pdev); +static int vbc_drv_remove(struct platform_device *pdev) +{ + struct clk *vbc_clk = vbc_clk_get(); + + if (vbc_clk) { + clk_put(vbc_clk); + vbc_clk_set(0); + } + + snd_soc_unregister_component(&pdev->dev); + + sprd_vbc_codec_remove(pdev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id vbc_of_match[] = { + {.compatible = "sprd,vbc-r1p0",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vbc_of_match); +#endif + +static struct platform_driver vbc_driver = { + .driver = { + .name = "vbc-r1p0", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(vbc_of_match), + }, + + .probe = vbc_drv_probe, + .remove = vbc_drv_remove, +}; + +module_platform_driver(vbc_driver); + +/* include the other module of VBC */ +#include "vbc-comm.c" +#include "vbc-codec.c" + +MODULE_DESCRIPTION("SPRD ASoC VBC CUP-DAI driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cpu-dai:vbc-r1p0"); diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc.h b/sound/soc/sprd/dai/vbc/r1p0/vbc.h new file mode 100644 index 00000000..56dc1989 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r1p0/vbc.h @@ -0,0 +1,22 @@ +/* + * sound/soc/sprd/dai/vbc/r1p0/vbc.h + * + * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_H +#define __VBC_H + +#include "vbc-comm.h" + +#endif /* __VBC_H */ diff --git a/sound/soc/sprd/dai/vbc/r2p0/Kconfig b/sound/soc/sprd/dai/vbc/r2p0/Kconfig new file mode 100644 index 00000000..68306682 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/Kconfig @@ -0,0 +1,20 @@ +config SND_SOC_SPRD_VBC_R2P0 + tristate # "VBC_R2P0" + help + +config SND_SOC_SPRD_VBC_SRC_OPEN + bool "VBC ADC/ADC23 SRC open" + depends on SND_SOC_SPRD_VBC_R2P0 + help + say Y then the VBC will open adc SRC for FM function. + +config SND_SOC_VBC_SRC_SAMPLE_RATE + int "VBC SRC Source Rate (32000 or 48000) unit(HZ)" + range 32000 48000 + default 32000 + depends on SND_SOC_SPRD_VBC_R2P0 + help + when capture sample rate is 44100HZ, + we cann't set 44100 HZ for CODEC adc sample rate, + so need to set CODEC sample to 32000/48000, + then open VBC SRC changing to 44100HZ.. diff --git a/sound/soc/sprd/dai/vbc/r2p0/Makefile b/sound/soc/sprd/dai/vbc/r2p0/Makefile new file mode 100644 index 00000000..a269a3c5 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/Makefile @@ -0,0 +1,6 @@ +# SPRD VBC Support +SPRD_SOUND_TREE := sound/soc/sprd/ +KBUILD_CFLAGS += -I$(SPRD_SOUND_TREE) -I$(SPRD_SOUND_TREE)/dai -I$(SPRD_SOUND_TREE)/dai/vbc/ + +snd-soc-sprd-vbc-r2p0-objs := vbc.o +obj-$(CONFIG_SND_SOC_SPRD_VBC_R2P0) += snd-soc-sprd-vbc-r2p0.o diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c new file mode 100644 index 00000000..a029523a --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c @@ -0,0 +1,2596 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c + * + * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "sprd-asoc-common.h" +#include "vbc-codec.h" + +#define FUN_REG(f) ((unsigned short)(-((f) + 1))) +#define SOC_REG(r) ((unsigned short)(r)) + +struct vbc_fw_header { + char magic[VBC_EQ_FIRMWARE_MAGIC_LEN]; + u32 profile_version; + u32 num_profile; /*total num */ + u32 num_da[VBC_IDX_MAX]; +}; + +struct vbc_da_eq_profile { + char magic[VBC_EQ_FIRMWARE_MAGIC_LEN]; + char name[VBC_EQ_PROFILE_NAME_MAX]; + /* TODO */ + u32 effect_paras[VBC_DA_EFFECT_PARAS_LEN]; +}; + +struct vbc_ad_eq_profile { + char magic[VBC_EQ_FIRMWARE_MAGIC_LEN]; + char name[VBC_EQ_PROFILE_NAME_MAX]; + /* TODO */ + u32 effect_paras[VBC_AD_EFFECT_PARAS_LEN]; +}; + +static const u32 vbc_da_eq_profile_default[VBC_DA_EFFECT_PARAS_LEN] = { +/* TODO the default register value */ + 0x00000000, /* DAPATCHCTL */ + 0x00000d7F, /* DAHPCTL */ + /*ALC default para */ + 0x000001e0, /* DAALCCTL0 */ + 0x00002000, /* DAALCCTL1 */ + 0x000004fe, /* DAALCCTL2 */ + 0x0000001f, /* DAALCCTL3 */ + 0x0000ffe2, /* DAALCCTL4 */ + 0x00007fff, /* DAALCCTL5 */ + 0x0000028c, /* DAALCCTL6 */ + 0x00000010, /* DAALCCTL7 */ + 0x000004dd, /* DAALCCTL8 */ + 0x00000000, /* DAALCCTL9 */ + 0x00000062, /* DAALCCTL10 */ + + 0x00000183, /* STCTL0 */ + 0x00000183, /* STCTL1 */ + 0x00000000, /* DACSRCCTL */ + 0x00000000, /* MIXERCTL */ + 0x00000000, /* VBNGCVTHD */ + 0x00000000, /* VBNGCTTHD */ + 0x00000000, /* VBNGCTL */ + + /*DA eq6 */ + /*0x100 -- 0x134 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x138 -- 0x16c */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x170 -- 0x1a4 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x1a8 -- 0x1dc */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x1e0 -- 0x214 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x218 -- 0x24c */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x250 -- 0x254 */ + 0x00001000, + 0x00000000, + + /*DA eq4 */ + /*0x258 -- 0x28c */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x290 -- 0x2c4 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x2c8 -- 0x2fc */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x300 -- 0x334 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x338 -- 0x33c */ + 0x00001000, + 0x00000000, +}; + +static const u32 vbc_ad_eq_profile_default[VBC_AD_EFFECT_PARAS_LEN] = { +/* TODO the default register value */ + 0x00000000, /* ADPATCHCTL */ + 0x00000000, /* ADHPCTL */ + /*AD01/23 eq6 */ + /*0x400 -- 0x434 */ + 0x00001000, /* ADC01_HPCOEF0_H or ADC23_HPCOEF0_H */ + 0x00000000, /* ADC01_HPCOEF0_L or ADC23_HPCOEF0_L */ + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x438 -- 0x46c */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x470 -- 0x4a4 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x4a8 -- 0x4dc */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x4e0 -- 0x514 */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x518 -- 0x54c */ + 0x00001000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00004000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + /*0x550 -- 0x554 */ + 0x00001000, + 0x00000000, +}; + +struct vbc_equ { + struct device *dev; + struct snd_soc_codec *codec; + int is_active[VBC_IDX_MAX]; + int is_loading; + int valid; + int now_profile[VBC_IDX_MAX]; + struct vbc_fw_header hdr; + void *data[VBC_IDX_MAX]; + void (*vbc_eq_apply) (struct snd_soc_codec * codec, void *data, + int vbc_idx); +}; + +static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx); +static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx); + +#define VBC_DG_VAL_MAX (0x7F) + +typedef int (*vbc_dg_set) (int enable, int dg); +struct vbc_dg { + int dg_switch[2]; + int dg_val[2]; + vbc_dg_set dg_set[2]; +}; + +struct st_hpf_dg { + int hpf_switch[2]; + int dg_val[2]; + int hpf_val[2]; +}; + +/*vbc mux setting*/ +enum { + SPRD_VBC_MUX_START = 0, + SPRD_VBC_ST0_CHAN_MUX = SPRD_VBC_MUX_START, + SPRD_VBC_ST1_CHAN_MUX, + SPRD_VBC_ST0_MUX, + SPRD_VBC_ST1_MUX, + SPRD_VBC_AD0_INMUX, + SPRD_VBC_AD1_INMUX, + SPRD_VBC_AD2_INMUX, + SPRD_VBC_AD3_INMUX, + SPRD_VBC_AD_IISMUX, + SPRD_VBC_AD23_IISMUX, + SPRD_VBC_DFM_DA0_ADDFM_INMUX, + SPRD_VBC_DFM_DA1_ADDFM_INMUX, + SPRD_VBC_DFM_DA0_ADDST_INMUX, + SPRD_VBC_DFM_DA1_ADDST_INMUX, + SPRD_VBC_MUX_MAX +}; + +#define IS_SPRD_VBC_MUX_RANG(reg) ((reg) >= SPRD_VBC_MUX_START && (reg) < (SPRD_VBC_MUX_MAX)) +#define SPRD_VBC_MUX_IDX(reg) (reg - SPRD_VBC_MUX_START) +#define IS_SPRD_VBC_DFM_ST_MUX_RANG(id) (((id) >= SPRD_VBC_DFM_DA0_ADDFM_INMUX) && ((id) <= SPRD_VBC_DFM_DA1_ADDST_INMUX)) + +static const char *vbc_mux_debug_str[SPRD_VBC_MUX_MAX] = { + "st0 chan mux", + "st1 chan mux", + "st0 mux", + "st1 mux", + "ad0 inmux", + "ad1 inmux", + "ad2 inmux", + "ad3 inmux", + "ad iis mux", + "ad23 iis mux", + "da0 addfm mux", + "da1 addfm mux", + "da0 addst mux", + "da1 addfm mux" +}; + +typedef int (*sprd_vbc_mux_set) (int sel); +struct sprd_vbc_mux_op { + int val; + sprd_vbc_mux_set set; +}; + +enum { + VBC_LOOP_SWITCH_START = SPRD_VBC_MUX_MAX, + VBC_AD01_LOOP_SWITCH = VBC_LOOP_SWITCH_START, + VBC_AD23_LOOP_SWITCH, + VBC_LOOP_SWITCH_MAX +}; + +#define IS_SPRD_VBC_SWITCH_RANG(reg) ((reg) >= VBC_LOOP_SWITCH_START && (reg) < (VBC_LOOP_SWITCH_MAX)) +#define SPRD_VBC_SWITCH_IDX(reg) (reg - VBC_LOOP_SWITCH_START) + +struct vbc_codec_priv { + struct snd_soc_codec *codec; + struct vbc_equ vbc_eq_setting; + struct mutex load_mutex; + int vbc_control; + int vbc_loop_switch[SPRD_VBC_SWITCH_IDX(VBC_LOOP_SWITCH_MAX)]; + struct vbc_dg dg[VBC_IDX_MAX]; + int vbc_da_iis_port; + int adc_dgmux_val[ADC_DGMUX_MAX]; + struct st_hpf_dg st_dg; + struct sprd_vbc_mux_op sprd_vbc_mux[SPRD_VBC_MUX_MAX]; + int alc_dp_t_mode; +}; + +static int vbc_da0_addfm_set(int val) +{ + vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA0_ADDFM_SHIFT, VBDAPATH_DA0_ADDFM_MASK); + return 0; +} + +static int vbc_da1_addfm_set(int val) +{ + vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA1_ADDFM_SHIFT, VBDAPATH_DA1_ADDFM_MASK); + return 0; +} + +static int vbc_da0_addst_set(int val) +{ + vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA0_ADDST_SHIFT, VBDAPATH_DA0_ADDST_MASK); + return 0; +} + +static int vbc_da1_addst_set(int val) +{ + vbc_reg_update(DAPATCHCTL, val << VBDAPATH_DA1_ADDST_SHIFT, VBDAPATH_DA1_ADDST_MASK); + return 0; +} + +static int vbc_st0_chnmux_set(int val) +{ + vbc_reg_update(STCTL1, val << VBST0_SEL_CHN, (1 << VBST0_SEL_CHN)); + return 0; +} + +static int vbc_st1_chnmux_set(int val) +{ + vbc_reg_update(STCTL1, val << VBST1_SEL_CHN, (1 << VBST1_SEL_CHN)); + return 0; +} + +static int vbc_st0_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_ST0_INMUX_SHIFT, + VBADPATH_ST0_INMUX_MASK); + return 0; +} + +static int vbc_st1_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_ST1_INMUX_SHIFT, + VBADPATH_ST1_INMUX_MASK); + return 0; +} + +static int vbc_ad0_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_INMUX_SHIFT, + VBADPATH_AD0_INMUX_MASK); + return 0; +} + +static int vbc_ad1_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_INMUX_SHIFT, + VBADPATH_AD1_INMUX_MASK); + return 0; +} + +static int vbc_ad2_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD2_INMUX_SHIFT, + VBADPATH_AD2_INMUX_MASK); + return 0; +} + +static int vbc_ad3_inmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD3_INMUX_SHIFT, + VBADPATH_AD3_INMUX_MASK); + return 0; +} + +static int vbc_ad0_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD0_DGMUX_SHIFT, + VBADPATH_AD0_DGMUX_MASK); + return 0; +} + +static int vbc_ad1_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD1_DGMUX_SHIFT, + VBADPATH_AD1_DGMUX_MASK); + return 0; +} + +static int vbc_ad2_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD2_DGMUX_SHIFT, + VBADPATH_AD2_DGMUX_MASK); + return 0; +} + +static int vbc_ad3_dgmux_set(int val) +{ + vbc_reg_update(ADPATCHCTL, val << VBADPATH_AD3_DGMUX_SHIFT, + VBADPATH_AD3_DGMUX_MASK); + return 0; +} + +static int vbc_ad_iismux_set(int port) +{ + vbc_reg_update(VBIISSEL, port << VBIISSEL_AD01_PORT_SHIFT, + VBIISSEL_AD01_PORT_MASK); + return 0; +} + +static int vbc_ad23_iismux_set(int port) +{ + vbc_reg_update(VBIISSEL, port << VBIISSEL_AD23_PORT_SHIFT, + VBIISSEL_AD23_PORT_MASK); + return 0; +} + +static int vbc_da_iismux_set(int port) +{ + vbc_reg_update(VBIISSEL, port << VBIISSEL_DA_PORT_SHIFT, + VBIISSEL_DA_PORT_MASK); + return 0; +} + +static int vbc_try_ad_iismux_set(int port); +static int vbc_try_ad23_iismux_set(int port); + +static sprd_vbc_mux_set vbc_mux_cfg[SPRD_VBC_MUX_MAX] = { + vbc_st0_chnmux_set, + vbc_st1_chnmux_set, + vbc_st0_inmux_set, + vbc_st1_inmux_set, + vbc_ad0_inmux_set, + vbc_ad1_inmux_set, + vbc_ad2_inmux_set, + vbc_ad3_inmux_set, + vbc_try_ad_iismux_set, + vbc_try_ad23_iismux_set, + vbc_da0_addfm_set, + vbc_da1_addfm_set, + vbc_da0_addst_set, + vbc_da1_addst_set +}; + +static inline int vbc_da0_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(DADGCTL, 0x80 | (0xFF & dg), 0xFF); + } else { + vbc_reg_update(DADGCTL, 0, 0x80); + } + return 0; +} + +static inline int vbc_da1_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(DADGCTL, (0x80 | (0xFF & dg)) << 8, 0xFF00); + } else { + vbc_reg_update(DADGCTL, 0, 0x8000); + } + return 0; +} + +static inline int vbc_ad0_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDG01CTL, 0x80 | (0xFF & dg), 0xFF); + } else { + vbc_reg_update(ADDG01CTL, 0, 0x80); + } + return 0; +} + +static inline int vbc_ad1_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDG01CTL, (0x80 | (0xFF & dg)) << 8, 0xFF00); + } else { + vbc_reg_update(ADDG01CTL, 0, 0x8000); + } + return 0; +} + +static inline int vbc_ad2_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDG23CTL, 0x80 | (0xFF & dg), 0xFF); + } else { + vbc_reg_update(ADDG23CTL, 0, 0x80); + } + return 0; +} + +static inline int vbc_ad3_dg_set(int enable, int dg) +{ + if (enable) { + vbc_reg_update(ADDG23CTL, (0x80 | (0xFF & dg)) << 8, 0xFF00); + } else { + vbc_reg_update(ADDG23CTL, 0, 0x8000); + } + return 0; +} + +static inline int vbc_st0_dg_set(int dg) +{ + vbc_reg_update(STCTL0, (0x7F & dg) << 4, 0x7F0); + return 0; +} + +static inline int vbc_st1_dg_set(int dg) +{ + vbc_reg_update(STCTL1, (0x7F & dg) << 4, 0x7F0); + return 0; +} + +static inline int vbc_st0_hpf_set(int enable, int hpf_val) +{ + if (enable) { + vbc_reg_update(STCTL0, BIT(VBST_HPF_0), BIT(VBST_HPF_0)); + vbc_reg_update(STCTL0, 0xF & hpf_val, 0xF); + } else { + vbc_reg_update(STCTL0, 0, BIT(VBST_HPF_0)); + vbc_reg_update(STCTL0, 3, 0xF); + } + return 0; +} + +static inline int vbc_st1_hpf_set(int enable, int hpf_val) +{ + if (enable) { + vbc_reg_update(STCTL1, BIT(VBST_HPF_1), BIT(VBST_HPF_1)); + vbc_reg_update(STCTL1, 0xF & hpf_val, 0xF); + } else { + vbc_reg_update(STCTL1, 0, BIT(VBST_HPF_1)); + vbc_reg_update(STCTL1, 3, 0xF); + } + return 0; +} + +static inline void vbc_da_alc_mode_set(int dp_t_mode) +{ + vbc_reg_update(DAHPCTL, dp_t_mode ? BIT(VBDAC_ALC_DP_T_MODE) : 0, + BIT(VBDAC_ALC_DP_T_MODE)); +} + +static inline void vbc_da_eq4_pos_sel(int pos) +{ + /*EQ4 pos sel */ + vbc_reg_update(DAHPCTL, pos ? BIT(VBDAC_EQ4_POS_SEL) : 0, + BIT(VBDAC_EQ4_POS_SEL)); +} + +static int vbc_try_dg_set(struct vbc_codec_priv *vbc_codec, int vbc_idx, int id) +{ + struct vbc_dg *p_dg = &vbc_codec->dg[vbc_idx]; + int dg = p_dg->dg_val[id]; + if (p_dg->dg_switch[id]) { + p_dg->dg_set[id] (1, dg); + } else { + p_dg->dg_set[id] (0, dg); + } + return 0; +} + +static int vbc_try_st_dg_set(struct vbc_codec_priv *vbc_codec, int id) +{ + struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg; + if (id == VBC_LEFT) { + vbc_st0_dg_set(p_st_dg->dg_val[id]); + } else { + vbc_st1_dg_set(p_st_dg->dg_val[id]); + } + return 0; +} + +static int vbc_try_st_hpf_set(struct vbc_codec_priv *vbc_codec, int id) +{ + struct st_hpf_dg *p_st_dg = &vbc_codec->st_dg; + if (id == VBC_LEFT) { + vbc_st0_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]); + } else { + vbc_st1_hpf_set(p_st_dg->hpf_switch[id], p_st_dg->hpf_val[id]); + } + return 0; +} + +static int vbc_try_src_set(struct vbc_codec_priv *vbc_codec, int vbc_idx) +{ +#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN + vbc_src_set(dfm.hw_rate, vbc_idx); +#else + vbc_src_set(0, vbc_idx); +#endif + return 0; +} + +static int vbc_try_da_iismux_set(int port) +{ + return vbc_da_iismux_set(port ? (port + 1) : 0); +} + +static int vbc_try_ad_iismux_set(int port) +{ + return vbc_ad_iismux_set(port); +} + +static int vbc_try_ad23_iismux_set(int port) +{ + return vbc_ad23_iismux_set(port); +} + +static int vbc_try_ad_dgmux_set(struct vbc_codec_priv *vbc_codec, int id) +{ + switch (id) { + case ADC0_DGMUX: + vbc_ad0_dgmux_set(vbc_codec->adc_dgmux_val[ADC0_DGMUX]); + break; + case ADC1_DGMUX: + vbc_ad1_dgmux_set(vbc_codec->adc_dgmux_val[ADC1_DGMUX]); + break; + case ADC2_DGMUX: + vbc_ad2_dgmux_set(vbc_codec->adc_dgmux_val[ADC2_DGMUX]); + break; + case ADC3_DGMUX: + vbc_ad3_dgmux_set(vbc_codec->adc_dgmux_val[ADC3_DGMUX]); + break; + default: + break; + } + return 0; +} + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DEBUG +static const char *get_event_name(int event) +{ + const char *ev_name; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ev_name = "PRE_PMU"; + break; + case SND_SOC_DAPM_POST_PMU: + ev_name = "POST_PMU"; + break; + case SND_SOC_DAPM_PRE_PMD: + ev_name = "PRE_PMD"; + break; + case SND_SOC_DAPM_POST_PMD: + ev_name = "POST_PMD"; + break; + default: + BUG(); + return 0; + } + return ev_name; +} +#endif + +static const char *st_chan_sel_txt[] = { + "AD01", "AD23" +}; + +static const char *st0_sel_txt[] = { + "AD0(2)ST0", "AD1(3)ST0", "NOINPUT", +}; + +static const char *st1_sel_txt[] = { + "AD1(3)ST1", "AD0(2)ST1", "NOINPUT", +}; + +static const char *ad0_inmux_txt[] = { + "IIS0AD0", "IIS1AD0", "NOINPUT", +}; + +static const char *ad1_inmux_txt[] = { + "IIS1AD1", "IIS0AD1", "NOINPUT", +}; + +static const char *ad2_inmux_txt[] = { + "IIS2AD2", "IIS3AD2", "NOINPUT", +}; + +static const char *ad3_inmux_txt[] = { + "IIS3AD3", "IIS2AD3", "NOINPUT", +}; + +static const char *ad_iis_txt[] = { + "AUDIIS0", "DIGFM", "EXTDIGFM", "EXTIIS6", "AUDIIS1", +}; + +static const char *ad23_iis_txt[] = { + "AUDIIS1", "DIGFM", "EXTDIGFM", "EXTIIS6", "AUDIIS0", +}; + +static const char *dfm_dac_st_txt[] = { + "BYPASS(ST)", "ADD(ST)", "SUBTRACT(ST)", +}; + +#define SPRD_VBC_ENUM(xreg, xmax, xtexts)\ + SOC_ENUM_SINGLE(FUN_REG(xreg), 0, xmax, xtexts) + +static const struct soc_enum vbc_mux_sel_enum[SPRD_VBC_MUX_MAX] = { + /*ST CHAN MUX */ + SPRD_VBC_ENUM(SPRD_VBC_ST0_CHAN_MUX, 2, st_chan_sel_txt), + SPRD_VBC_ENUM(SPRD_VBC_ST1_CHAN_MUX, 2, st_chan_sel_txt), + /*ST INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_ST0_MUX, 4, st0_sel_txt), + SPRD_VBC_ENUM(SPRD_VBC_ST1_MUX, 4, st1_sel_txt), + /*AD INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_AD0_INMUX, 4, ad0_inmux_txt), + SPRD_VBC_ENUM(SPRD_VBC_AD1_INMUX, 4, ad1_inmux_txt), + SPRD_VBC_ENUM(SPRD_VBC_AD2_INMUX, 4, ad2_inmux_txt), + SPRD_VBC_ENUM(SPRD_VBC_AD3_INMUX, 4, ad3_inmux_txt), + /*IIS INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_AD_IISMUX, 5, ad_iis_txt), + SPRD_VBC_ENUM(SPRD_VBC_AD23_IISMUX, 5, ad23_iis_txt), + /* DFM DAC INMUX */ + SPRD_VBC_ENUM(SPRD_VBC_DFM_DA0_ADDFM_INMUX, 3, dfm_dac_st_txt), + SPRD_VBC_ENUM(SPRD_VBC_DFM_DA1_ADDFM_INMUX, 3, dfm_dac_st_txt), + SPRD_VBC_ENUM(SPRD_VBC_DFM_DA0_ADDST_INMUX, 3, dfm_dac_st_txt), + SPRD_VBC_ENUM(SPRD_VBC_DFM_DA1_ADDST_INMUX, 3, dfm_dac_st_txt), +}; + +static int vbc_chan_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + int vbc_idx = FUN_REG(w->reg); + int chan = w->shift; + int ret = 0; + + sp_asoc_pr_dbg("%s(%s%d) Event is %s\n", __func__, + vbc_get_name(vbc_idx), chan, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + vbc_chan_enable(1, vbc_idx, chan); + vbc_try_src_set(vbc_codec, vbc_idx); + break; + case SND_SOC_DAPM_POST_PMD: + vbc_src_set(0, vbc_idx); + vbc_chan_enable(0, vbc_idx, chan); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + + return ret; +} + +static int vbc_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + vbc_power(1); + break; + case SND_SOC_DAPM_POST_PMD: + vbc_power(0); + break; + default: + BUG(); + ret = -EINVAL; + break; + } + + return ret; +} + +static int dfm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n", __func__, get_event_name(event)); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + fm_set_vbc_buffer_size(); /*No use in FM function, just for debug VBC */ + vbc_try_st_dg_set(vbc_codec, VBC_LEFT); + vbc_try_st_dg_set(vbc_codec, VBC_RIGHT); + if (!p_eq_setting->codec) + p_eq_setting->codec = codec; + /*eq setting */ + if (p_eq_setting->is_active[VBC_PLAYBACK] + && p_eq_setting->data[VBC_PLAYBACK]) + vbc_eq_try_apply(codec, VBC_PLAYBACK); + else + vbc_eq_profile_close(vbc_codec->codec, VBC_PLAYBACK); + vbc_enable(1); + break; + case SND_SOC_DAPM_PRE_PMD: + vbc_enable(0); + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int aud_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + unsigned int vbc_idx = FUN_REG(w->reg); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s Event is %s\n Chan is %s", __func__, + get_event_name(event), vbc_get_name(vbc_idx)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /*eq setting */ + if (!p_eq_setting->codec) + p_eq_setting->codec = codec; + if (p_eq_setting->is_active[vbc_idx] + && p_eq_setting->data[vbc_idx]) + vbc_eq_try_apply(codec, vbc_idx); + else + vbc_eq_profile_close(vbc_codec->codec, vbc_idx); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + unsigned int id = SPRD_VBC_MUX_IDX(FUN_REG(w->reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]); + int ret = 0; + + sp_asoc_pr_dbg("%s Set %s(%d) Event is %s\n", __func__, + vbc_mux_debug_str[id], mux->val, get_event_name(event)); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mux->set = vbc_mux_cfg[id]; + ret = mux->set(mux->val); + break; + case SND_SOC_DAPM_PRE_PMD: + mux->set = 0; + if (IS_SPRD_VBC_DFM_ST_MUX_RANG(id)) { + ret = vbc_mux_cfg[id] (0); + } + /*ret = vbc_mux_cfg[id] (0);*/ + break; + default: + BUG(); + ret = -EINVAL; + } + + return ret; +} + +static int sprd_vbc_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]); + + ucontrol->value.enumerated.item[0] = mux->val; + + return 0; +} + +static int sprd_vbc_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = SPRD_VBC_MUX_IDX(FUN_REG(e->reg)); + unsigned int max = e->max; + unsigned int mask = (1 << fls(max)) - 1; + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[reg]); + int ret = 0; + + if (mux->val == ucontrol->value.enumerated.item[0]) + return 0; + + sp_asoc_pr_info("Set MUX[%s] to %d\n", vbc_mux_debug_str[reg], + ucontrol->value.enumerated.item[0]); + + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + /*notice the sequence */ + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + /*update reg: must be set after snd_soc_dapm_put_enum_double->change = snd_soc_test_bits(widget->codec, e->reg, mask, val); */ + mux->val = (ucontrol->value.enumerated.item[0] & mask); + if (mux->set) { + ret = mux->set(mux->val); + } + + return ret; +} + +#define SPRD_VBC_MUX(xname, xenum) \ + SOC_DAPM_ENUM_EXT(xname, xenum, sprd_vbc_mux_get, sprd_vbc_mux_put) + +static const struct snd_kcontrol_new vbc_mux[SPRD_VBC_MUX_MAX] = { + SPRD_VBC_MUX("ST0 CHAN MUX", vbc_mux_sel_enum[SPRD_VBC_ST0_CHAN_MUX]), + SPRD_VBC_MUX("ST1 CHAN MUX", vbc_mux_sel_enum[SPRD_VBC_ST1_CHAN_MUX]), + SPRD_VBC_MUX("ST0 INMUX", vbc_mux_sel_enum[SPRD_VBC_ST0_MUX]), + SPRD_VBC_MUX("ST1 INMUX", vbc_mux_sel_enum[SPRD_VBC_ST1_MUX]), + SPRD_VBC_MUX("AD0 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD0_INMUX]), + SPRD_VBC_MUX("AD1 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD1_INMUX]), + SPRD_VBC_MUX("AD2 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD2_INMUX]), + SPRD_VBC_MUX("AD3 INMUX", vbc_mux_sel_enum[SPRD_VBC_AD3_INMUX]), + SPRD_VBC_MUX("AD IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD_IISMUX]), + SPRD_VBC_MUX("AD23 IISMUX", vbc_mux_sel_enum[SPRD_VBC_AD23_IISMUX]), + SPRD_VBC_MUX("DA0 ADDFM MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA0_ADDFM_INMUX]), + SPRD_VBC_MUX("DA1 ADDFM MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA1_ADDFM_INMUX]), + SPRD_VBC_MUX("DA0 ADDST MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA0_ADDST_INMUX]), + SPRD_VBC_MUX("DA1 ADDST MUX", vbc_mux_sel_enum[SPRD_VBC_DFM_DA1_ADDST_INMUX]), +}; + +#define VBC_DAPM_MUX_E(wname, wreg) \ + SND_SOC_DAPM_MUX_E(wname, FUN_REG(wreg), 0, 0, &vbc_mux[wreg], mux_event, \ + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +static int vbc_loop_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg)); + ucontrol->value.integer.value[0] = vbc_codec->vbc_loop_switch[id]; + return 0; +} + +static int vbc_loop_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(mc->reg)); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->vbc_loop_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC AD%s LOOP Switch Set %x\n", + id == 0 ? "01" : "23", + (int)ucontrol->value.integer.value[0]); + + snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + vbc_codec->vbc_loop_switch[id] = ret; + + return ret; +} + +static const struct snd_kcontrol_new vbc_loop_control[] = { + SOC_SINGLE_EXT("Switch", + FUN_REG(VBC_AD01_LOOP_SWITCH), + 0, 1, 0, + vbc_loop_switch_get, + vbc_loop_switch_put), + SOC_SINGLE_EXT("Switch", + FUN_REG(VBC_AD23_LOOP_SWITCH), + 0, 1, 0, + vbc_loop_switch_get, + vbc_loop_switch_put), +}; + +static const struct snd_soc_dapm_widget vbc_codec_dapm_widgets[] = { + /*power */ + SND_SOC_DAPM_SUPPLY("VBC Power", SND_SOC_NOPM, 0, 0, + vbc_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* STchan mux */ + VBC_DAPM_MUX_E("ST0 CHAN MUX", SPRD_VBC_ST0_CHAN_MUX), + VBC_DAPM_MUX_E("ST1 CHAN MUX", SPRD_VBC_ST1_CHAN_MUX), + + /* ST inmux */ + VBC_DAPM_MUX_E("ST0 INMUX", SPRD_VBC_ST0_MUX), + VBC_DAPM_MUX_E("ST1 INMUX", SPRD_VBC_ST1_MUX), + + /*ADC inmux */ + VBC_DAPM_MUX_E("AD0 INMUX", SPRD_VBC_AD0_INMUX), + VBC_DAPM_MUX_E("AD1 INMUX", SPRD_VBC_AD1_INMUX), + VBC_DAPM_MUX_E("AD2 INMUX", SPRD_VBC_AD2_INMUX), + VBC_DAPM_MUX_E("AD3 INMUX", SPRD_VBC_AD3_INMUX), + + /*IIS MUX */ + VBC_DAPM_MUX_E("AD IISMUX", SPRD_VBC_AD_IISMUX), + VBC_DAPM_MUX_E("AD23 IISMUX", SPRD_VBC_AD23_IISMUX), + + /*ST Switch */ + SND_SOC_DAPM_PGA_S("ST0 Switch", 4, SOC_REG(STCTL0), VBST_EN_0, 0, NULL, + 0), + SND_SOC_DAPM_PGA_S("ST1 Switch", 4, SOC_REG(STCTL1), VBST_EN_1, 0, NULL, + 0), + + /* DFM DAC inmux */ + VBC_DAPM_MUX_E("DA0 ADDFM MUX", SPRD_VBC_DFM_DA0_ADDFM_INMUX), + VBC_DAPM_MUX_E("DA1 ADDFM MUX", SPRD_VBC_DFM_DA1_ADDFM_INMUX), + VBC_DAPM_MUX_E("DA0 ADDST MUX", SPRD_VBC_DFM_DA0_ADDST_INMUX), + VBC_DAPM_MUX_E("DA1 ADDST MUX", SPRD_VBC_DFM_DA1_ADDST_INMUX), + + SND_SOC_DAPM_LINE("DFM", dfm_event), + /*VBC Chan Switch */ + SND_SOC_DAPM_PGA_S("DA0 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_LEFT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("DA1 Switch", 5, FUN_REG(VBC_PLAYBACK), VBC_RIGHT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD0 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_LEFT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD1 Switch", 5, FUN_REG(VBC_CAPTRUE), VBC_RIGHT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD2 Switch", 5, FUN_REG(VBC_CAPTRUE1), VBC_LEFT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("AD3 Switch", 5, FUN_REG(VBC_CAPTRUE1), VBC_RIGHT, 0, + vbc_chan_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /*AUD loop var vbc switch */ + SND_SOC_DAPM_PGA_S("Aud input", 4, FUN_REG(VBC_CAPTRUE), 0, 0, + aud_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("Aud1 input", 4, FUN_REG(VBC_CAPTRUE1), 0, 0, + aud_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SWITCH("Aud Loop in VBC", SND_SOC_NOPM, 0, 0, + &vbc_loop_control[SPRD_VBC_SWITCH_IDX + (VBC_AD01_LOOP_SWITCH)]), + SND_SOC_DAPM_SWITCH("Aud1 Loop in VBC", SND_SOC_NOPM, 0, 0, + &vbc_loop_control[SPRD_VBC_SWITCH_IDX + (VBC_AD23_LOOP_SWITCH)]), +}; + +/* sprd_vbc supported interconnection*/ +static const struct snd_soc_dapm_route vbc_codec_intercon[] = { + /************************power********************************/ + /* digital fm playback need to open DA Clk and DA power */ + {"DFM", NULL, "VBC Power"}, + + /********************** capture in path in vbc ********************/ + /* AD input route */ + {"Aud Loop in VBC", "Switch", "Aud input"}, + + {"Aud1 Loop in VBC", "Switch", "Aud1 input"}, + + /* AD01 */ + {"AD IISMUX", "DIGFM", "Dig FM Jack"}, + {"AD IISMUX", "EXTDIGFM", "Dig FM Jack"}, + {"AD IISMUX", "AUDIIS0", "Aud Loop in VBC"}, + + /* AD23 */ + {"AD23 IISMUX", "DIGFM", "Dig FM Jack"}, + {"AD23 IISMUX", "EXTDIGFM", "Dig FM Jack"}, + {"AD23 IISMUX", "AUDIIS1", "Aud1 Loop in VBC"}, + + {"AD0 INMUX", "IIS0AD0", "AD IISMUX"}, + {"AD0 INMUX", "IIS1AD0", "AD IISMUX"}, + {"AD1 INMUX", "IIS1AD1", "AD IISMUX"}, + {"AD1 INMUX", "IIS0AD1", "AD IISMUX"}, + {"AD2 INMUX", "IIS2AD2", "AD23 IISMUX"}, + {"AD2 INMUX", "IIS3AD2", "AD23 IISMUX"}, + {"AD3 INMUX", "IIS3AD3", "AD23 IISMUX"}, + {"AD3 INMUX", "IIS2AD3", "AD23 IISMUX"}, + + {"AD0 Switch", NULL, "AD0 INMUX"}, + {"AD1 Switch", NULL, "AD1 INMUX"}, + {"AD2 Switch", NULL, "AD2 INMUX"}, + {"AD3 Switch", NULL, "AD3 INMUX"}, + + /********************** fm playback route ********************/ + /*ST route */ + {"ST0 CHAN MUX", "AD01", "AD0 Switch"}, + {"ST0 CHAN MUX", "AD01", "AD1 Switch"}, + {"ST0 CHAN MUX", "AD23", "AD2 Switch"}, + {"ST0 CHAN MUX", "AD23", "AD3 Switch"}, + {"ST1 CHAN MUX", "AD01", "AD0 Switch"}, + {"ST1 CHAN MUX", "AD01", "AD1 Switch"}, + {"ST1 CHAN MUX", "AD23", "AD2 Switch"}, + {"ST1 CHAN MUX", "AD23", "AD3 Switch"}, + + {"ST0 INMUX", "AD0(2)ST0", "ST0 CHAN MUX"}, + {"ST0 INMUX", "AD1(3)ST0", "ST0 CHAN MUX"}, + {"ST1 INMUX", "AD1(3)ST1", "ST1 CHAN MUX"}, + {"ST1 INMUX", "AD0(2)ST1", "ST1 CHAN MUX"}, + + {"ST0 Switch", NULL, "ST0 INMUX"}, + {"ST1 Switch", NULL, "ST1 INMUX"}, + {"DA0 Switch", NULL, "ST0 Switch"}, + {"DA1 Switch", NULL, "ST1 Switch"}, + {"DA0 ADDFM MUX", "ADD(ST)", "DA0 Switch"}, + {"DA0 ADDFM MUX", "BYPASS(ST)", "DA0 Switch"}, + {"DA0 ADDFM MUX", "SUBTRACT(ST)", "DA0 Switch"}, + {"DA1 ADDFM MUX", "ADD(ST)", "DA1 Switch"}, + {"DA1 ADDFM MUX", "BYPASS(ST)", "DA1 Switch"}, + {"DA1 ADDFM MUX", "SUBTRACT(ST)", "DA1 Switch"}, + {"DA0 ADDST MUX", "ADD(ST)", "DA0 Switch"}, + {"DA0 ADDST MUX", "BYPASS(ST)", "DA0 Switch"}, + {"DA0 ADDST MUX", "SUBTRACT(ST)", "DA0 Switch"}, + {"DA1 ADDST MUX", "ADD(ST)", "DA1 Switch"}, + {"DA1 ADDST MUX", "BYPASS(ST)", "DA1 Switch"}, + {"DA1 ADDST MUX", "SUBTRACT(ST)", "DA1 Switch"}, + {"DFM", NULL, "DA0 ADDFM MUX"}, + {"DFM", NULL, "DA1 ADDFM MUX"}, + {"DFM", NULL, "DA0 ADDST MUX"}, + {"DFM", NULL, "DA1 ADDST MUX"}, +}; + +int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai) +{ + struct vbc_codec_priv *vbc_codec = dev_get_drvdata(dai->dev); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + sp_asoc_pr_dbg("%s %s\n", __func__, vbc_get_name(vbc_idx)); + + switch (vbc_idx) { + case VBC_PLAYBACK: + vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port); + break; + case VBC_CAPTRUE: + vbc_try_ad_iismux_set(vbc_codec->sprd_vbc_mux + [SPRD_VBC_AD_IISMUX].val); + vbc_try_ad_dgmux_set(vbc_codec, 0); + vbc_try_ad_dgmux_set(vbc_codec, 1); + break; + case VBC_CAPTRUE1: + vbc_try_ad23_iismux_set(vbc_codec->sprd_vbc_mux + [SPRD_VBC_AD23_IISMUX].val); + vbc_try_ad_dgmux_set(vbc_codec, 2); + vbc_try_ad_dgmux_set(vbc_codec, 3); + break; + } + + if (p_eq_setting->is_active[vbc_idx] + && p_eq_setting->data[vbc_idx]) + vbc_eq_try_apply(vbc_codec->codec, vbc_idx); + else + vbc_eq_profile_close(vbc_codec->codec, vbc_idx); + + vbc_da_alc_mode_set(vbc_codec->alc_dp_t_mode); + vbc_try_dg_set(vbc_codec, vbc_idx, VBC_LEFT); + vbc_try_dg_set(vbc_codec, vbc_idx, VBC_RIGHT); + + return 0; +} + +/* + * proc interface + */ + +#ifdef CONFIG_PROC_FS +static void vbc_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int reg; + + snd_iprintf(buffer, "vbc register dump\n"); + for (reg = ARM_VB_BASE + 0x10; reg < ARM_VB_END; reg += 0x10) { + snd_iprintf(buffer, "0x%04x | 0x%04x 0x%04x 0x%04x 0x%04x\n", + (reg - ARM_VB_BASE) + , vbc_reg_read(reg + 0x00) + , vbc_reg_read(reg + 0x04) + , vbc_reg_read(reg + 0x08) + , vbc_reg_read(reg + 0x0C) + ); + } +} + +static void vbc_proc_init(struct snd_soc_codec *codec) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(codec->card->snd_card, "vbc", &entry)) + snd_info_set_text_ops(entry, NULL, vbc_proc_read); +} +#else /* !CONFIG_PROC_FS */ +static inline void vbc_proc_init(struct snd_soc_codec *codec) +{ +} +#endif + +static int vbc_da_eq_reg_offset(u32 reg) +{ + int i = 0; + if (reg == DAPATCHCTL) { + i = 0; + } else if ((reg >= DAHPCTL) && (reg <= STCTL1)) { + i = ((reg - DAHPCTL) >> 2) + 1; + } else if ((reg >= DACSRCCTL) && (reg <= MIXERCTL)) { + i = ((reg - DACSRCCTL) >> 2) + ((STCTL1 - DAHPCTL) >> 2) + 2; + } else if ((reg >= VBNGCVTHD) && (reg <= VBNGCTL)) { + i = ((reg - VBNGCVTHD) >> 2) + ((MIXERCTL - DACSRCCTL) >> 2) + + ((STCTL1 - DAHPCTL) >> 2) + 3; + } else if ((reg >= HPCOEF0_H) && (reg <= HPCOEF71_L)) { + i = ((reg - HPCOEF0_H) >> 2) + ((VBNGCTL - VBNGCVTHD) >> 2) + + ((MIXERCTL - DACSRCCTL) >> 2) + ((STCTL1 - DAHPCTL) >> 2) + + 4; + } + BUG_ON(i >= VBC_DA_EFFECT_PARAS_LEN); + return i; +} + +static int vbc_ad_eq_reg_offset(u32 reg) +{ + int i = 0; + if (reg == ADPATCHCTL) { + i = 0; + } else if (reg == ADHPCTL) { + i = 1; + } else if ((reg >= AD01_HPCOEF0_H) && (reg <= AD01_HPCOEF42_L)) { + i = ((reg - AD01_HPCOEF0_H) >> 2) + 2; + } else if ((reg >= AD23_HPCOEF0_H) && (reg <= AD23_HPCOEF42_L)) { + i = ((reg - AD23_HPCOEF0_H) >> 2) + 2; + } + BUG_ON(i >= VBC_AD_EFFECT_PARAS_LEN); + return i; +} + +static inline void vbc_da_eq_reg_set(u32 reg, void *data) +{ + u32 *effect_paras = (u32 *) data; + if (reg == DAHPCTL) { + /*EQ set */ + vbc_reg_update(DAHPCTL, effect_paras[vbc_da_eq_reg_offset(reg)], + 0x1FF); + } else { + vbc_reg_write(reg, effect_paras[vbc_da_eq_reg_offset(reg)]); + } +} + +static inline void vbc_da_eq_reg_set_range(u32 reg_start, u32 reg_end, + void *data) +{ + u32 reg_addr; + for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) { + vbc_da_eq_reg_set(reg_addr, data); + } +} + +static inline void vbc_ad_eq_reg_set(u32 reg, void *data) +{ + u32 *effect_paras = (u32 *) data; + if (reg == ADHPCTL) { + vbc_reg_update(ADHPCTL, effect_paras[vbc_da_eq_reg_offset(reg)], + 0xC0); + } else { + vbc_reg_write(reg, effect_paras[vbc_ad_eq_reg_offset(reg)]); + } +} + +static inline void vbc_ad_eq_reg_set_range(u32 reg_start, u32 reg_end, + void *data) +{ + u32 reg_addr; + for (reg_addr = reg_start; reg_addr <= reg_end; reg_addr += 4) { + vbc_ad_eq_reg_set(reg_addr, data); + } +} + +/*gray code change*/ +static void gray_dir(unsigned int *bit, int dir) +{ + if (dir) { + *bit >>= 1; + } else { + *bit <<= 1; + } +} + +static int gray(u32 reg, int from, int to, int (*step_action) (int, int)) +{ + int start_bit = (from > to) ? 31 : 0; + unsigned int bit = (1 << start_bit); + int r = from; + int diff = (from ^ to); + int i; + for (i = 0; i < 32; i++, gray_dir(&bit, start_bit)) { + if ((diff & bit)) { + r &= ~bit; + r |= (bit & to); + step_action(reg, r); + } + } + return 0; +} + +static int step_action_set_reg(int reg, int r) +{ + vbc_reg_write(reg, r); + udelay(10); + return 0; +} + +static void gray_set_reg(u32 reg, int from, int to) +{ + sp_asoc_pr_dbg("gray set reg(0x%x) = (0x%x) from (0x%x))\n", reg, to, + from); + gray(reg, from, to, step_action_set_reg); +} + +static void vbc_eq_iir_ab_clr(u32 reg_addr) +{ + vbc_reg_write(reg_addr + 0x8, 0); /*b0_H */ + vbc_reg_write(reg_addr + 0xC, 0); /*b0_L */ + vbc_reg_write(reg_addr + 0x18, 0); /*b1_H */ + vbc_reg_write(reg_addr + 0x1C, 0); /*b1_L */ + vbc_reg_write(reg_addr + 0x28, 0); /*b2_H */ + vbc_reg_write(reg_addr + 0x2C, 0); /*b2_L */ + vbc_reg_write(reg_addr + 0x10, 0); /*a0_H */ + vbc_reg_write(reg_addr + 0x14, 0); /*a0_L */ + vbc_reg_write(reg_addr + 0x20, 0); /*a1_H */ + vbc_reg_write(reg_addr + 0x24, 0); /*a1_L */ + vbc_reg_write(reg_addr + 0x30, 0); /*a2_H */ + vbc_reg_write(reg_addr + 0x34, 0); /*a2_L */ +} + +static void vbc_eq_iir_s_clr(u32 reg_addr) +{ +#if 0 + vbc_reg_write2(reg_addr, 0); /*s0_H */ + vbc_reg_write2(reg_addr + 0x4, 0); /*s0_L */ +#else + u32 val = vbc_reg_read(reg_addr); + gray_set_reg(reg_addr, val, 0); + val = vbc_reg_read(reg_addr + 0x4); + gray_set_reg(reg_addr + 0x4, val, 0); +#endif +} + +static void vbc_eq_iir_ab_set_data(u32 reg_addr, void *data) +{ + vbc_da_eq_reg_set(reg_addr + 0x30, data); /*a2_H */ + vbc_da_eq_reg_set(reg_addr + 0x34, data); /*a2_L */ + vbc_da_eq_reg_set(reg_addr + 0x20, data); /*a1_H */ + vbc_da_eq_reg_set(reg_addr + 0x24, data); /*a1_L */ + vbc_da_eq_reg_set(reg_addr + 0x10, data); /*a0_H */ + vbc_da_eq_reg_set(reg_addr + 0x14, data); /*a0_L */ + vbc_da_eq_reg_set(reg_addr + 0x28, data); /*b2_H */ + vbc_da_eq_reg_set(reg_addr + 0x2C, data); /*b2_L */ + vbc_da_eq_reg_set(reg_addr + 0x18, data); /*b1_H */ + vbc_da_eq_reg_set(reg_addr + 0x1C, data); /*b1_L */ + vbc_da_eq_reg_set(reg_addr + 0x8, data); /*b0_H */ + vbc_da_eq_reg_set(reg_addr + 0xC, data); /*b0_L */ +} + +static void vbc_eq_iir_s_set_data(u32 reg_addr, void *data) +{ +#if 0 + vbc_da_eq_reg_set(reg_addr, data); /*s0_H */ + vbc_da_eq_reg_set(reg_addr + 0x4, data); /*s0_L */ +#else + u32 *effect_paras = (u32 *) data; + gray_set_reg(reg_addr, 0, effect_paras[vbc_da_eq_reg_offset(reg_addr)]); + gray_set_reg(reg_addr + 0x4, 0, + effect_paras[vbc_da_eq_reg_offset(reg_addr + 0x4)]); +#endif +} + +static void vbc_da_alc_reg_set(void *data) +{ + u32 *effect_paras = (u32 *) data; + u32 reg_addr; + u32 val; +#ifdef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + for (reg_addr = DAHPCTL; reg_addr <= DAALCCTL10; reg_addr += 4) { +#else + for (reg_addr = DAALCCTL0; reg_addr <= DAALCCTL10; reg_addr += 4) { +#endif + val = vbc_reg_read(reg_addr); + gray_set_reg(reg_addr, val, + effect_paras[vbc_da_eq_reg_offset(reg_addr)]); + } +} + +static void vbc_eq_reg_apply(struct snd_soc_codec *codec, void *data, + int vbc_idx) +{ + u32 reg; + void *effect_paras; + int val; + if (vbc_idx == VBC_PLAYBACK) { + val = ((u32 *) data)[vbc_da_eq_reg_offset(DAHPCTL)]; + /*DA EQ6 set */ + /*s6-s0 clear */ + for (reg = HPCOEF42_H; reg >= HPCOEF0_H; reg -= 0x38) { + vbc_eq_iir_s_clr(reg); + } + /*a,b clear */ + for (reg = HPCOEF0_H; reg <= HPCOEF35_H; reg += 0x38) { + vbc_eq_iir_ab_clr(reg); + } + /*iir state clear */ + vbc_reg_update(DAHPCTL, BIT(VBDAEQ_HP_REG_CLR), + BIT(VBDAEQ_HP_REG_CLR)); + + /*a,b set */ +#ifdef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + effect_paras = data; +#else + if (val & VBDAC_EQ6_EN) + effect_paras = data; + else + effect_paras = &vbc_da_eq_profile_default; +#endif + + for (reg = HPCOEF35_H; reg >= HPCOEF0_H; reg -= 0x38) { + vbc_eq_iir_ab_set_data(reg, effect_paras); + } + /*s0-s6 set */ + for (reg = HPCOEF0_H; reg <= HPCOEF42_H; reg += 0x38) { + vbc_eq_iir_s_set_data(reg, effect_paras); + } + + vbc_da_eq_reg_set(DAHPCTL, data); + + /*ALC set */ +#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + if (val & VBDAC_ALC_EN) + effect_paras = data; + else + effect_paras = &vbc_da_eq_profile_default; +#endif + vbc_da_alc_reg_set(effect_paras); + + /*iir state set */ + vbc_reg_update(DAHPCTL, 0, BIT(VBDAEQ_HP_REG_CLR)); +#if 0 + /*DA EQ4 set */ + for (reg = HPCOEF71_H; reg >= HPCOEF43_H; reg -= 0x38) { + vbc_eq_iir_s_clr(reg); + } + for (reg = HPCOEF43_H; reg <= HPCOEF64_H; reg += 0x38) { + vbc_eq_iir_ab_clr(reg); + } + if (val & VBDAC_EQ4_EN) + effect_paras = data; + else + effect_paras = &vbc_da_eq_profile_default; + + for (reg = HPCOEF64_H; reg >= HPCOEF43_H; reg -= 0x38) { + vbc_eq_iir_ab_set_data(reg, effect_paras); + } + for (reg = HPCOEF43_H; reg <= HPCOEF71_H; reg += 0x38) { + vbc_eq_iir_s_set_data(reg, effect_paras); + } + +#endif + /*other */ + vbc_da_eq_reg_set(DACSRCCTL, data); + vbc_da_eq_reg_set(MIXERCTL, data); + vbc_da_eq_reg_set_range(VBNGCVTHD, VBNGCTL, data); + } else { +#if 0 + val = ((u32 *) data)[vbc_ad_eq_reg_offset(ADHPCTL)]; + if (vbc_idx == VBC_CAPTRUE) { + /*AD01 EQ6 set */ + for (reg = AD01_HPCOEF42_H; reg >= AD01_HPCOEF0_H; + reg -= 0x38) { + vbc_eq_iir_s_clr(reg); + } + for (reg = AD01_HPCOEF0_H; reg <= AD01_HPCOEF35_H; + reg += 0x38) { + vbc_eq_iir_ab_clr(reg); + } + /*iir state clear */ + vbc_reg_write(ADHPCTL, BIT(VBADC01EQ_HP_REG_CLR), + BIT(VBADC01EQ_HP_REG_CLR)); + + if (val & VBADC01_EQ6_EN) + effect_paras = data; + else + effect_paras = &vbc_ad_eq_profile_default; + + for (reg = AD01_HPCOEF35_H; reg >= AD01_HPCOEF0_H; + reg -= 0x38) { + vbc_eq_iir_ab_set_data(reg, data); + } + for (reg = AD01_HPCOEF0_H; reg <= AD01_HPCOEF42_H; + reg += 0x38) { + vbc_eq_iir_s_set_data(reg, data); + } + /*iir state set */ + vbc_reg_write(ADHPCTL, 0, BIT(VBADC01EQ_HP_REG_CLR)); + } else { + /*AD23 EQ6 set */ + for (reg = AD23_HPCOEF42_H; reg >= AD23_HPCOEF0_H; + reg -= 0x38) { + vbc_eq_iir_s_clr(reg); + } + for (reg = AD23_HPCOEF0_H; reg <= AD23_HPCOEF35_H; + reg += 0x38) { + vbc_eq_iir_ab_clr(reg); + } + /*iir state clear */ + vbc_reg_write(ADHPCTL, BIT(VBADC23EQ_HP_REG_CLR), + BIT(VBADC23EQ_HP_REG_CLR)); + + if (val & VBADC23_EQ6_EN) + effect_paras = data; + else + effect_paras = &vbc_ad_eq_profile_default; + for (reg = AD23_HPCOEF35_H; reg >= AD23_HPCOEF0_H; + reg -= 0x38) { + vbc_eq_iir_ab_set_data(reg, data); + } + for (reg = AD23_HPCOEF0_H; reg <= AD23_HPCOEF42_H; + reg += 0x38) { + vbc_eq_iir_s_set_data(reg, data); + } + /*iir state set */ + vbc_reg_write(ADHPCTL, 0, BIT(VBADC23EQ_HP_REG_CLR)); + } +#endif + } +} + +static void vbc_eq_profile_apply(struct snd_soc_codec *codec, void *data, + int vbc_idx) +{ + if (codec) { + vbc_eq_reg_apply(codec, data, vbc_idx); + } +} + +static void vbc_eq_profile_close(struct snd_soc_codec *codec, int vbc_idx) +{ + switch (vbc_idx) { + case VBC_PLAYBACK: + vbc_eq_profile_apply(codec, + &vbc_da_eq_profile_default, vbc_idx); + break; + case VBC_CAPTRUE: + case VBC_CAPTRUE1: + vbc_eq_profile_apply(codec, + &vbc_ad_eq_profile_default, vbc_idx); + break; + default: + break; + } +} + +static void vbc_eq_try_apply(struct snd_soc_codec *codec, int vbc_idx) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + u32 *data; + + sp_asoc_pr_dbg("%s 0x%x\n", __func__, (int)p_eq_setting->vbc_eq_apply); + if (p_eq_setting->vbc_eq_apply) { + mutex_lock(&vbc_codec->load_mutex); + if (vbc_idx < SPRD_VBC_PLAYBACK_COUNT) { + struct vbc_da_eq_profile *now = + &(((struct vbc_da_eq_profile + *)(p_eq_setting->data[VBC_PLAYBACK])) + [p_eq_setting->now_profile[VBC_PLAYBACK]]); + data = now->effect_paras; + sp_asoc_pr_info("VBC %s EQ Apply '%s'\n", + vbc_get_name(vbc_idx), now->name); + } else { + struct vbc_ad_eq_profile *now = + &(((struct vbc_ad_eq_profile + *)(p_eq_setting->data[vbc_idx])) + [p_eq_setting->now_profile[vbc_idx]]); + data = now->effect_paras; + sp_asoc_pr_info("VBC %s EQ Apply '%s'\n", + vbc_get_name(vbc_idx), now->name); + } + p_eq_setting->vbc_eq_apply(codec, data, vbc_idx); + mutex_unlock(&vbc_codec->load_mutex); + } +} + +static int vbc_eq_profile_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int vbc_idx = FUN_REG(mc->reg); + ucontrol->value.integer.value[0] = p_eq_setting->now_profile[vbc_idx]; + return 0; +} + +static int vbc_eq_profile_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int vbc_idx = FUN_REG(mc->reg); + int profile_max; + + profile_max = p_eq_setting->hdr.num_da[vbc_idx]; + + ret = ucontrol->value.integer.value[0]; + if (ret == p_eq_setting->now_profile[vbc_idx]) { + return ret; + } + + sp_asoc_pr_info("VBC %s EQ Select %ld max %d\n", + vbc_get_name(vbc_idx), + ucontrol->value.integer.value[0], profile_max); + + if (ret < profile_max) { + p_eq_setting->now_profile[vbc_idx] = ret; + } + + if (p_eq_setting->is_active[vbc_idx] + && p_eq_setting->data[vbc_idx]) + vbc_eq_try_apply(codec, vbc_idx); + + return ret; +} + +static int vbc_eq_loading(struct snd_soc_codec *codec) +{ + int ret; + const u8 *fw_data; + const struct firmware *fw; + int i; + int vbc_idx; + int offset = 0; + int len = 0; + int old_num_profile; + int old_num_da[VBC_IDX_MAX]; + int offset_len[VBC_IDX_MAX + 1]; + int sum_count = 0; + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + + sp_asoc_pr_dbg("%s\n", __func__); + mutex_lock(&vbc_codec->load_mutex); + p_eq_setting->is_loading = 1; + + /* request firmware for VBC EQ */ + ret = request_firmware(&fw, "vbc_eq", p_eq_setting->dev); + if (ret != 0) { + pr_err("ERR:Failed to load firmware: %d\n", ret); + goto req_fw_err; + } + fw_data = fw->data; + old_num_profile = p_eq_setting->hdr.num_profile; + if (fw_data == NULL) { + pr_err("ERR:firmware data error!\n"); + goto eq_out; + } + for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) { + old_num_da[vbc_idx] = p_eq_setting->hdr.num_da[vbc_idx]; + } + + memcpy(&p_eq_setting->hdr, fw_data, sizeof(p_eq_setting->hdr)); + + if (strncmp + (p_eq_setting->hdr.magic, VBC_EQ_FIRMWARE_MAGIC_ID, + VBC_EQ_FIRMWARE_MAGIC_LEN)) { + pr_err("ERR:Firmware magic error!\n"); + ret = -EINVAL; + goto eq_out; + } + + if (p_eq_setting->hdr.profile_version != VBC_EQ_PROFILE_VERSION) { + pr_err("ERR:Firmware support version is 0x%x!\n", + VBC_EQ_PROFILE_VERSION); + ret = -EINVAL; + goto eq_out; + } + + if (p_eq_setting->hdr.num_profile > VBC_EQ_PROFILE_CNT_MAX) { + pr_err + ("ERR:Firmware profile to large at %d, max count is %d!\n", + p_eq_setting->hdr.num_profile, VBC_EQ_PROFILE_CNT_MAX); + ret = -EINVAL; + goto eq_out; + } + + for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) { + sum_count += p_eq_setting->hdr.num_da[vbc_idx]; + } + + if (p_eq_setting->hdr.num_profile != sum_count) { + pr_err("ERR:Firmware profile total number is wrong!\n"); + ret = -EINVAL; + goto eq_out; + } + + offset_len[VBC_PLAYBACK] = sizeof(struct vbc_fw_header); + offset_len[VBC_CAPTRUE] = sizeof(struct vbc_da_eq_profile); + offset_len[VBC_CAPTRUE1] = sizeof(struct vbc_ad_eq_profile); + offset_len[VBC_CAPTRUE1 + 1] = sizeof(struct vbc_ad_eq_profile); + + for (vbc_idx = 0; vbc_idx < VBC_IDX_MAX; vbc_idx++) { + if (old_num_da[vbc_idx] != p_eq_setting->hdr.num_da[vbc_idx]) { + if (p_eq_setting->now_profile[vbc_idx] >= + p_eq_setting->hdr.num_da[vbc_idx]) + p_eq_setting->now_profile[vbc_idx] = 0; + vbc_safe_kfree(&p_eq_setting->data[vbc_idx]); + } + + if (vbc_idx) { + offset += + offset_len[vbc_idx] * + p_eq_setting->hdr.num_da[vbc_idx - 1]; + } else { + offset += offset_len[vbc_idx]; + } + + if (p_eq_setting->hdr.num_da[vbc_idx] == 0) { + continue; + } + + len = + p_eq_setting->hdr.num_da[vbc_idx] * offset_len[vbc_idx + 1]; + if (p_eq_setting->data[vbc_idx] == NULL) { + p_eq_setting->data[vbc_idx] = kzalloc(len, GFP_KERNEL); + if (p_eq_setting->data[vbc_idx] == NULL) { + ret = -ENOMEM; + for (--vbc_idx; vbc_idx >= 0; vbc_idx--) { + vbc_safe_kfree + (&p_eq_setting->data[vbc_idx]); + } + goto eq_out; + } + } + + memcpy(p_eq_setting->data[vbc_idx], fw_data + offset, len); + + for (i = 0; i < p_eq_setting->hdr.num_da[vbc_idx]; i++) { + const char *magic = + (char *)(p_eq_setting->data[vbc_idx]) + + (i * offset_len[vbc_idx + 1]); + if (strncmp + (magic, VBC_EQ_FIRMWARE_MAGIC_ID, + VBC_EQ_FIRMWARE_MAGIC_LEN)) { + pr_err + ("ERR:%s Firmware profile[%d] magic error!magic: %s \n", + vbc_get_name(vbc_idx), i, magic); + ret = -EINVAL; + for (; vbc_idx >= 0; vbc_idx--) { + vbc_safe_kfree + (&p_eq_setting->data[vbc_idx]); + } + goto eq_out; + } + } + } + + ret = 0; + goto eq_out; + +eq_out: + release_firmware(fw); +req_fw_err: + p_eq_setting->is_loading = 0; + mutex_unlock(&vbc_codec->load_mutex); + if (ret >= 0) { + struct vbc_da_eq_profile *profile = + &(((struct vbc_da_eq_profile + *)(p_eq_setting->data[VBC_PLAYBACK])) + [0]); + u32 *data = profile->effect_paras; + if (data[vbc_da_eq_reg_offset(DAHPCTL)] & + BIT(VBDAC_ALC_DP_T_MODE)) + vbc_codec->alc_dp_t_mode = 1; + else + vbc_codec->alc_dp_t_mode = 0; + sp_asoc_pr_dbg("DAHPCTL:%x----alc_dp_t_mode:%d", + data[vbc_da_eq_reg_offset(DAHPCTL)], + vbc_codec->alc_dp_t_mode); + for (i = 0; i <= 2; i++) { + if (p_eq_setting->is_active[i] + && p_eq_setting->data[i]) + vbc_eq_try_apply(codec, i); + } + } + sp_asoc_pr_dbg("return %i\n", ret); + return ret; +} + +static int vbc_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = vbc_codec->vbc_control; + return 0; +} + +static int vbc_switch_reg_val[]; +static int vbc_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + + if (vbc_codec->vbc_control == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("VBC Switch to %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + vbc_codec->vbc_control = ucontrol->value.integer.value[0]; + + arch_audio_vbc_switch(vbc_switch_reg_val[vbc_codec->vbc_control]); + vbc_set_phys_addr(vbc_switch_reg_val[vbc_codec->vbc_control]); + + return 1; +} + +static int vbc_eq_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + ucontrol->value.integer.value[0] = p_eq_setting->is_active[id]; + return 0; +} + +static int vbc_eq_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int ret; + + ret = ucontrol->value.integer.value[0]; + if (ret == p_eq_setting->is_active[id]) { + return ret; + } + + sp_asoc_pr_info("VBC %s EQ Switch %s\n", vbc_get_name(id), + STR_ON_OFF(ucontrol->value.integer.value[0])); + + if ((ret == 0) || (ret == 1)) { + p_eq_setting->is_active[id] = ret; + if (p_eq_setting->is_active[id] + && p_eq_setting->data[id]) { + p_eq_setting->vbc_eq_apply = vbc_eq_profile_apply; + vbc_eq_try_apply(codec, id); + } else { + p_eq_setting->vbc_eq_apply = 0; + vbc_eq_profile_close(codec, id); + } + } + + return ret; +} + +static int vbc_eq_load_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + ucontrol->value.integer.value[0] = p_eq_setting->is_loading; + return 0; +} + +static int vbc_eq_load_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int ret; + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + + sp_asoc_pr_info("VBC EQ %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + ret = ucontrol->value.integer.value[0]; + if (ret == 1) { + ret = vbc_eq_loading(codec); + } + + return ret; +} + +static int vbc_dg_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_val[id]; + return 0; +} + +static int vbc_dg_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->dg[vbc_idx].dg_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC %s%d DG set 0x%02x\n", + vbc_get_name(vbc_idx), id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->dg[vbc_idx].dg_val[id] = ret; + } + + vbc_try_dg_set(vbc_codec, vbc_idx, id); + + return ret; +} + +static int vbc_st_dg_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.dg_val[id]; + return 0; +} + +static int vbc_st_dg_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.dg_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d DG set 0x%02x\n", id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->st_dg.dg_val[id] = ret; + } + + vbc_try_st_dg_set(vbc_codec, id); + + return ret; +} + +static int vbc_dg_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ucontrol->value.integer.value[0] = vbc_codec->dg[vbc_idx].dg_switch[id]; + return 0; +} + +static int vbc_dg_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + int vbc_idx = mc->shift; + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->dg[vbc_idx].dg_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC %s%d DG Switch %s\n", + vbc_get_name(vbc_idx), id, + STR_ON_OFF(ucontrol->value.integer.value[0])); + + vbc_codec->dg[vbc_idx].dg_switch[id] = ret; + + vbc_try_dg_set(vbc_codec, vbc_idx, id); + + return ret; +} + +static int vbc_st_hpf_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_switch[id]; + return 0; +} + +static int vbc_st_hpf_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.hpf_switch[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d HPF Switch %s\n", id, + STR_ON_OFF(ucontrol->value.integer.value[0])); + + vbc_codec->st_dg.hpf_switch[id] = ret; + + vbc_try_st_hpf_set(vbc_codec, id); + + return ret; +} + +static int vbc_st_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->st_dg.hpf_val[id]; + return 0; +} + +static int vbc_st_hpf_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->st_dg.hpf_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC ST%d HPF set 0x%02x\n", id, + (int)ucontrol->value.integer.value[0]); + + if (ret <= VBC_DG_VAL_MAX) { + vbc_codec->st_dg.hpf_val[id] = ret; + } + + vbc_try_st_hpf_set(vbc_codec, id); + + return ret; +} + +static int adc_dgmux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ucontrol->value.integer.value[0] = vbc_codec->adc_dgmux_val[id]; + return 0; +} + +static int adc_dgmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + + ret = ucontrol->value.integer.value[0]; + if (ret == vbc_codec->adc_dgmux_val[id]) { + return ret; + } + + sp_asoc_pr_info("VBC AD%d DG mux : %ld\n", id, + ucontrol->value.integer.value[0]); + + vbc_codec->adc_dgmux_val[id] = ret; + vbc_try_ad_dgmux_set(vbc_codec, id); + + return ret; +} + +static int dac_iismux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + ucontrol->value.integer.value[0] = vbc_codec->vbc_da_iis_port; + return 0; +} + +static int dac_iismux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *texts = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + + if (vbc_codec->vbc_da_iis_port == ucontrol->value.integer.value[0]) + return 0; + + sp_asoc_pr_info("VBC DA output to %s\n", + texts->texts[ucontrol->value.integer.value[0]]); + + vbc_codec->vbc_da_iis_port = ucontrol->value.integer.value[0]; + + vbc_try_da_iismux_set(vbc_codec->vbc_da_iis_port); + + return 1; +} + +static int vbc_switch_reg_val[] = { + AUDIO_TO_CP0_DSP_CTRL, + AUDIO_TO_CP1_DSP_CTRL, + AUDIO_TO_AP_ARM_CTRL, + AUDIO_TO_CP2_ARM_CTRL, +}; + +static const char *switch_function[] = + { "cp0-dsp", "cp1-dsp", "ap", "cp2-arm" }; +static const char *eq_load_function[] = { "idle", "loading" }; +static const char *da_iis_mux_function[] = + { "sprd-codec", "ext-codec-4", "ext-codec-6" }; +static const char *fm_sample_rate_function[] = { "32000", "48000" }; + +static const struct soc_enum vbc_enum[] = { + SOC_ENUM_SINGLE_EXT(4, switch_function), + SOC_ENUM_SINGLE_EXT(2, eq_load_function), + SOC_ENUM_SINGLE_EXT(3, da_iis_mux_function), + SOC_ENUM_SINGLE_EXT(2, fm_sample_rate_function), +}; + +static const struct snd_kcontrol_new vbc_codec_snd_controls[] = { + SOC_ENUM_EXT("VBC Switch", vbc_enum[0], vbc_switch_get, + vbc_switch_put), + SOC_SINGLE_EXT("VBC DA EQ Switch", FUN_REG(VBC_PLAYBACK), 0, 1, 0, + vbc_eq_switch_get, + vbc_eq_switch_put), + SOC_SINGLE_EXT("VBC AD01 EQ Switch", FUN_REG(VBC_CAPTRUE), 0, 1, 0, + vbc_eq_switch_get, + vbc_eq_switch_put), + SOC_SINGLE_EXT("VBC AD02 EQ Switch", FUN_REG(VBC_CAPTRUE1), 0, 1, 0, + vbc_eq_switch_get, + vbc_eq_switch_put), + SOC_ENUM_EXT("VBC EQ Update", vbc_enum[1], vbc_eq_load_get, + vbc_eq_load_put), + + SOC_SINGLE_EXT("VBC DACL DG Set", FUN_REG(VBC_LEFT), + VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC DACR DG Set", FUN_REG(VBC_RIGHT), + VBC_PLAYBACK, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADCL DG Set", FUN_REG(VBC_LEFT), + VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADCR DG Set", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADC23L DG Set", FUN_REG(VBC_LEFT), + VBC_CAPTRUE1, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC ADC23R DG Set", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE1, VBC_DG_VAL_MAX, 0, vbc_dg_get, + vbc_dg_put), + SOC_SINGLE_EXT("VBC STL DG Set", FUN_REG(VBC_LEFT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put), + SOC_SINGLE_EXT("VBC STR DG Set", FUN_REG(VBC_RIGHT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_dg_get, vbc_st_dg_put), + + SOC_SINGLE_EXT("VBC DACL DG Switch", FUN_REG(VBC_LEFT), + VBC_PLAYBACK, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC DACR DG Switch", FUN_REG(VBC_RIGHT), + VBC_PLAYBACK, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADCL DG Switch", FUN_REG(VBC_LEFT), + VBC_CAPTRUE, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADCR DG Switch", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADC23L DG Switch", FUN_REG(VBC_LEFT), + VBC_CAPTRUE1, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC ADC23R DG Switch", FUN_REG(VBC_RIGHT), + VBC_CAPTRUE1, 1, 0, vbc_dg_switch_get, + vbc_dg_switch_put), + SOC_SINGLE_EXT("VBC STL HPF Switch", FUN_REG(VBC_LEFT), + 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put), + SOC_SINGLE_EXT("VBC STR HPF Switch", FUN_REG(VBC_RIGHT), + 0, 1, 0, vbc_st_hpf_switch_get, vbc_st_hpf_switch_put), + SOC_SINGLE_EXT("VBC STL HPF Set", FUN_REG(VBC_LEFT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put), + SOC_SINGLE_EXT("VBC STR HPF Set", FUN_REG(VBC_RIGHT), + 0, VBC_DG_VAL_MAX, 0, vbc_st_hpf_get, vbc_st_hpf_put), + + SOC_SINGLE_EXT("VBC AD0 DG Mux", FUN_REG(ADC0_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_SINGLE_EXT("VBC AD1 DG Mux", FUN_REG(ADC1_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_SINGLE_EXT("VBC AD2 DG Mux", FUN_REG(ADC2_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_SINGLE_EXT("VBC AD3 DG Mux", FUN_REG(ADC3_DGMUX), 0, 1, 0, + adc_dgmux_get, adc_dgmux_put), + SOC_ENUM_EXT("VBC DA IIS Mux", vbc_enum[2], dac_iismux_get, + dac_iismux_put), + SOC_SINGLE_EXT("VBC DA EQ Profile Select", FUN_REG(VBC_PLAYBACK), 0, + VBC_EQ_PROFILE_CNT_MAX, 0, + vbc_eq_profile_get, vbc_eq_profile_put), + SOC_SINGLE_EXT("VBC AD01 EQ Profile Select", FUN_REG(VBC_CAPTRUE), 0, + VBC_EQ_PROFILE_CNT_MAX, 0, + vbc_eq_profile_get, vbc_eq_profile_put), + SOC_SINGLE_EXT("VBC AD23 EQ Profile Select", FUN_REG(VBC_CAPTRUE1), 0, + VBC_EQ_PROFILE_CNT_MAX, 0, + vbc_eq_profile_get, vbc_eq_profile_put), +}; + +static unsigned int vbc_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + /* Because snd_soc_update_bits reg is 16 bits short type, + so muse do following convert + */ + if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) { + reg |= SPRD_VBC_BASE_HI; + return vbc_reg_read(reg); + } else if (IS_SPRD_VBC_MUX_RANG(FUN_REG(reg))) { + int id = SPRD_VBC_MUX_IDX(FUN_REG(reg)); + struct sprd_vbc_mux_op *mux = &(vbc_codec->sprd_vbc_mux[id]); + return mux->val; + } else if (IS_SPRD_VBC_SWITCH_RANG(FUN_REG(reg))) { + int id = SPRD_VBC_SWITCH_IDX(FUN_REG(reg)); + return vbc_codec->vbc_loop_switch[id]; + } + + sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg); + return 0; +} + +static int vbc_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + if (IS_SPRD_VBC_RANG(reg | SPRD_VBC_BASE_HI)) { + reg |= SPRD_VBC_BASE_HI; + return vbc_reg_write(reg, val); + } + sp_asoc_pr_dbg("The Register is NOT VBC Codec's reg = 0x%x\n", reg); + return 0; +} + +static int vbc_codec_soc_probe(struct snd_soc_codec *codec) +{ + struct vbc_codec_priv *vbc_codec = snd_soc_codec_get_drvdata(codec); + struct vbc_equ *p_eq_setting = &vbc_codec->vbc_eq_setting; + int ret = 0; + + sp_asoc_pr_dbg("%s\n", __func__); + + codec->dapm.idle_bias_off = 1; + + vbc_codec->codec = codec; + p_eq_setting->codec = codec; + + vbc_proc_init(codec); + + return ret; +} + +/* power down chip */ +static int vbc_codec_soc_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_vbc_codec = { + .probe = vbc_codec_soc_probe, + .remove = vbc_codec_soc_remove, + .read = vbc_codec_read, + .write = vbc_codec_write, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, + .dapm_widgets = vbc_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(vbc_codec_dapm_widgets), + .dapm_routes = vbc_codec_intercon, + .num_dapm_routes = ARRAY_SIZE(vbc_codec_intercon), + .controls = vbc_codec_snd_controls, + .num_controls = ARRAY_SIZE(vbc_codec_snd_controls), +}; + +static int sprd_vbc_codec_probe(struct platform_device *pdev) +{ + struct vbc_codec_priv *vbc_codec; + int ret; + + sp_asoc_pr_dbg("%s\n", __func__); + + vbc_codec = devm_kzalloc(&pdev->dev, sizeof(struct vbc_codec_priv), + GFP_KERNEL); + if (vbc_codec == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, vbc_codec); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_vbc_codec, NULL, 0); + if (ret != 0) { + pr_err("ERR:Failed to register VBC-CODEC: %d\n", ret); + } + + /* AP refer to array vbc_switch_reg_val */ + vbc_codec->vbc_control = 2; + vbc_codec->vbc_eq_setting.dev = &pdev->dev; + vbc_codec->st_dg.dg_val[0] = 0x18; + vbc_codec->st_dg.dg_val[1] = 0x18; + vbc_codec->st_dg.hpf_val[0] = 0x3; + vbc_codec->st_dg.hpf_val[1] = 0x3; + vbc_codec->dg[0].dg_val[0] = 0x18; + vbc_codec->dg[0].dg_val[1] = 0x18; + vbc_codec->dg[1].dg_val[0] = 0x18; + vbc_codec->dg[1].dg_val[1] = 0x18; + vbc_codec->dg[2].dg_val[0] = 0x18; + vbc_codec->dg[2].dg_val[1] = 0x18; + vbc_codec->dg[0].dg_set[0] = vbc_da0_dg_set; + vbc_codec->dg[0].dg_set[1] = vbc_da1_dg_set; + vbc_codec->dg[1].dg_set[0] = vbc_ad0_dg_set; + vbc_codec->dg[1].dg_set[1] = vbc_ad1_dg_set; + vbc_codec->dg[2].dg_set[0] = vbc_ad2_dg_set; + vbc_codec->dg[2].dg_set[1] = vbc_ad3_dg_set; + mutex_init(&vbc_codec->load_mutex); + + return ret; +} + +static int sprd_vbc_codec_remove(struct platform_device *pdev) +{ + struct vbc_codec_priv *vbc_codec = platform_get_drvdata(pdev); + vbc_codec->vbc_eq_setting.dev = 0; + + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +MODULE_DESCRIPTION("SPRD ASoC VBC Codec Driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_AUTHOR("Ken.Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("codec:VBC Codec"); diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h new file mode 100644 index 00000000..6a973e71 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h @@ -0,0 +1,30 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h + * + * SPRD SoC VBC Codec -- SpreadTrum SOC VBC Codec function. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_CODEC_H +#define __VBC_CODEC_H + +#include "vbc-comm.h" + +#define VBC_EQ_FIRMWARE_MAGIC_LEN (4) +#define VBC_EQ_FIRMWARE_MAGIC_ID ("VBEQ") +#define VBC_EQ_PROFILE_VERSION (0x00000002) +#define VBC_EQ_PROFILE_CNT_MAX (100) +#define VBC_EQ_PROFILE_NAME_MAX (32) +#define VBC_DA_EFFECT_PARAS_LEN (20+72*2) +#define VBC_AD_EFFECT_PARAS_LEN (2+ 43*2) + +#endif /* __VBC_CODEC_H */ diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c new file mode 100644 index 00000000..739a1908 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c @@ -0,0 +1,374 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c + * + * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function. + * + * Copyright (C) 2012 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <sound/soc.h> + +#include "sprd-asoc-common.h" +#include "vbc-comm.h" + +static DEFINE_SPINLOCK(vbc_lock); + +/* vbc local power suppliy and chan on */ +static struct vbc_refcount { + atomic_t vbc_power_on; /*vbc reg power enable */ + atomic_t vbc_on; /*vbc enable */ + atomic_t chan_on[VBC_IDX_MAX][2]; +} vbc_refcnt; + +struct sprd_vbc_src_reg_info { + int reg; + int clr_bit; + int f1f2f3_bp_bit; + int f1_sel_bit; + int en_bit; +}; + +struct sprd_vbc_src_info { + struct sprd_vbc_src_reg_info reg_info; +}; + +static struct sprd_vbc_src_info vbc_src[VBC_IDX_MAX] = { + {{0}}, + {{ADCSRCCTL, VBADCSRC_CLR_01, VBADCSRC_F1F2F3_BP_01, VBADCSRC_F1_SEL_01, + VBADCSRC_EN_01}}, + {{ADCSRCCTL, VBADCSRC_CLR_23, VBADCSRC_F1F2F3_BP_23, VBADCSRC_F1_SEL_23, + VBADCSRC_EN_23}}, +}; + +static const char *sprd_vbc_name[VBC_IDX_MAX] = { + "DAC", + "ADC01", + "ADC23", +}; + +static inline const char *vbc_get_name(int vbc_idx) +{ + return sprd_vbc_name[vbc_idx]; +} + +static inline int vbc_reg_read(unsigned int reg) +{ + return __raw_readl((void *__iomem)(reg - VBC_BASE + sprd_vbc_base)); +} + +static inline void vbc_reg_raw_write(unsigned int reg, int val) +{ + __raw_writel(val, (void *__iomem)(reg - VBC_BASE + sprd_vbc_base)); +} + +static int vbc_reg_write(unsigned int reg, int val) +{ + spin_lock(&vbc_lock); + vbc_reg_raw_write(reg, val); + spin_unlock(&vbc_lock); + sp_asoc_pr_reg("VBC:[0x%04x] W:[0x%08x] R:[0x%08x]\n", (reg - VBC_BASE) & 0xFFFF, val, + vbc_reg_read(reg)); + return 0; +} + +/* + * Returns 1 for change, 0 for no change, or negative error code. + */ +static int vbc_reg_update(unsigned int reg, int val, int mask) +{ + int new, old; + spin_lock(&vbc_lock); + old = vbc_reg_read(reg); + new = (old & ~mask) | (val & mask); + vbc_reg_raw_write(reg, new); + spin_unlock(&vbc_lock); + sp_asoc_pr_reg("[0x%04x] U:[0x%08x] R:[0x%08x]\n", reg & 0xFFFF, new, + vbc_reg_read(reg)); + return old != new; +} + +static struct clk *s_vbc_clk = 0; +static void vbc_clk_set(struct clk *clk) +{ + s_vbc_clk = clk; +} + +static struct clk *vbc_clk_get(void) +{ + return s_vbc_clk; +} + +static inline void vbc_reg_enable(void) +{ + int ret = 0; + if (0/*s_vbc_clk*/) { + ret = clk_prepare(s_vbc_clk); + WARN(ret != 0, "clk_vbc prepare failed, return %d", ret ); + ret = clk_enable(s_vbc_clk); + WARN(ret != 0, "clk_vbc enable failed, return %d", ret ); + } else { + arch_audio_vbc_reg_enable(); + } +} + +static inline void vbc_reg_disable(void) +{ + if (0/*s_vbc_clk*/) { + clk_disable(s_vbc_clk); + clk_unprepare(s_vbc_clk); + } else { + arch_audio_vbc_reg_disable(); + } +} + +static int vbc_power(int enable) +{ + int i; + atomic_t *vbc_power_on = &vbc_refcnt.vbc_power_on; + if (enable) { + atomic_inc(vbc_power_on); + if (atomic_read(vbc_power_on) == 1) { + arch_audio_vbc_enable(); + vbc_reg_enable(); + for (i = 0; i < SPRD_VBC_PLAYBACK_COUNT; i++) { + vbc_da_buffer_clear_all(i); + } + arch_audio_sleep_xtl_enable(); + arch_audio_memory_sleep_enable(); + sp_asoc_pr_dbg("VBC Power On\n"); + } + } else { + if (atomic_dec_and_test(vbc_power_on)) { + arch_audio_vbc_reset(); + arch_audio_vbc_disable(); + vbc_reg_disable(); + arch_audio_sleep_xtl_disable(); + arch_audio_memory_sleep_disable(); + sp_asoc_pr_dbg("VBC Power Off\n"); + } + if (atomic_read(vbc_power_on) < 0) { + atomic_set(vbc_power_on, 0); + } + } + sp_asoc_pr_dbg("VBC Power REF: %d", atomic_read(vbc_power_on)); + return 0; +} + +static inline int vbc_da_enable_raw(int enable, int chan) +{ + vbc_reg_update(VBCHNEN, ((enable ? 1 : 0) << (VBDACHEN_SHIFT + chan)), + (1 << (VBDACHEN_SHIFT + chan))); + return 0; +} + +static inline int vbc_ad_enable_raw(int enable, int chan) +{ + if (enable) { + vbc_reg_update(VBCHNEN, (1 << (VBADCHEN_SHIFT + chan)), + (1 << (VBADCHEN_SHIFT + chan))); + } else { + pr_info("Not close ad%d chan!", (chan ? 1:0)); + } + + return 0; +} + +static inline int vbc_ad23_enable_raw(int enable, int chan) +{ + if (enable) { + vbc_reg_update(VBCHNEN, (1 << (VBAD23CHEN_SHIFT + chan)), + (1 << (VBAD23CHEN_SHIFT + chan))); + } else { + pr_info("Not close ad%d chan!", (chan ? 3:2)); + } + return 0; +} + +static inline void vbc_da_eq6_enable(int enable) +{ + vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ6_EN) : 0), + BIT(VBDAC_EQ6_EN)); +} + +static inline void vbc_da_eq4_enable(int enable) +{ + vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_EQ4_EN) : 0), + BIT(VBDAC_EQ4_EN)); +} + +static inline void vbc_da_alc_enable(int enable) +{ + vbc_reg_update(DAHPCTL, (enable ? BIT(VBDAC_ALC_EN) : 0), + BIT(VBDAC_ALC_EN)); +} + +static inline void vbc_ad01_eq6_enable(int enable) +{ + vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC01_EQ6_EN) : 0), + BIT(VBADC01_EQ6_EN)); +} + +static inline void vbc_ad23_eq6_enable(int enable) +{ + vbc_reg_update(ADHPCTL, (enable ? BIT(VBADC23_EQ6_EN) : 0), + BIT(VBADC23_EQ6_EN)); +} + +static inline int vbc_enable_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBENABLE), + (1 << VBENABLE)); + return 0; +} + +typedef int (*vbc_chan_enable_raw) (int enable, int chan); +static vbc_chan_enable_raw vbc_chan_enable_fun[VBC_IDX_MAX] = { + vbc_da_enable_raw, + vbc_ad_enable_raw, + vbc_ad23_enable_raw, +}; + +static int vbc_chan_enable(int enable, int vbc_idx, int chan) +{ + atomic_t *chan_on = &vbc_refcnt.chan_on[vbc_idx][chan]; + if (enable) { + atomic_inc(chan_on); + if (atomic_read(chan_on) == 1) { + vbc_chan_enable_fun[vbc_idx] (1, chan); + sp_asoc_pr_dbg("VBC %s%d On\n", vbc_get_name(vbc_idx), + chan); + } + } else { + if (atomic_dec_and_test(chan_on)) { + vbc_chan_enable_fun[vbc_idx] (0, chan); + sp_asoc_pr_dbg("VBC %s%d Off\n", vbc_get_name(vbc_idx), + chan); + } + if (atomic_read(chan_on) < 0) { + atomic_set(chan_on, 0); + } + } + sp_asoc_pr_dbg("%s%d REF: %d", vbc_get_name(vbc_idx), chan, + atomic_read(chan_on)); + return 0; +} + +static DEFINE_MUTEX(vbc_mutex); +static int vbc_enable(int enable) +{ + atomic_t *vbc_on = &vbc_refcnt.vbc_on; + mutex_lock(&vbc_mutex); + if (enable) { + atomic_inc(vbc_on); + if (atomic_read(vbc_on) == 1) { +#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + vbc_da_eq6_enable(1); + vbc_da_alc_enable(1); +#endif + /*todo?? */ + /*vbc_da_eq4_enable(1); */ + /*vbc_ad01_eq6_enable(1); */ + /*vbc_ad23_eq6_enable(1); */ + vbc_enable_set(1); + sp_asoc_pr_dbg("VBC Enable\n"); + } + } else { + if (atomic_dec_and_test(vbc_on)) { + vbc_enable_set(0); +#ifndef CONFIG_SND_SOC_VBC_SUPPORT_DYNAMIC_EQ + vbc_da_eq6_enable(0); + vbc_da_alc_enable(0); +#endif + /*vbc_da_eq4_enable(0); */ + /*vbc_ad01_eq6_enable(0); */ + /*vbc_ad23_eq6_enable(0); */ + sp_asoc_pr_dbg("VBC Disable"); + } + if (atomic_read(vbc_on) < 0) { + atomic_set(vbc_on, 0); + } + } + mutex_unlock(&vbc_mutex); + + sp_asoc_pr_dbg("VBC EN REF: %d", atomic_read(vbc_on)); + return 0; +} + +static int vbc_src_set(int rate, int vbc_idx) +{ + int f1f2f3_bp; + int f1_sel; + int en_sel; + int val; + int mask; + struct sprd_vbc_src_reg_info *reg_info = &vbc_src[vbc_idx].reg_info; + + if (!vbc_src[vbc_idx].reg_info.reg) { + return -EINVAL; + } + + sp_asoc_pr_dbg("Rate:%d, Chan: %s", rate, vbc_get_name(vbc_idx)); + + /*src_clr */ + vbc_reg_update(vbc_src[vbc_idx].reg_info.reg, + (1 << reg_info->clr_bit), (1 << reg_info->clr_bit)); + udelay(10); + vbc_reg_update(reg_info->reg, 0, (1 << reg_info->clr_bit)); + + switch (rate) { + case 32000: + f1f2f3_bp = 0; + f1_sel = 1; + en_sel = 1; + break; + case 48000: + f1f2f3_bp = 0; + f1_sel = 0; + en_sel = 1; + break; + case 44100: + f1f2f3_bp = 1; + f1_sel = 0; + en_sel = 1; + break; + default: + f1f2f3_bp = 0; + f1_sel = 0; + en_sel = 0; + break; + } + + /*src_set */ + mask = (1 << reg_info->f1f2f3_bp_bit) + | (1 << reg_info->f1_sel_bit) + | (1 << reg_info->en_bit); + + val = (f1f2f3_bp << reg_info->f1f2f3_bp_bit) + | (f1_sel << reg_info->f1_sel_bit) + | (en_sel << reg_info->en_bit); + + vbc_reg_update(reg_info->reg, val, mask); + + return 0; +} diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h new file mode 100644 index 00000000..50c69258 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h @@ -0,0 +1,299 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h + * + * SPRD SoC VBC -- SpreadTrum SOC DAI for VBC Common function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_COMM_H +#define __VBC_COMM_H + +#include <soc/sprd/sprd-audio.h> + +#define VBC_VERSION "vbc.r2p0" + +/* VBADBUFFDTA */ +enum { + VBISADCK_INV = 9, + VBISDACK_INV, + VBISAD23CK_INV, + VBIIS_DLOOP = 13, +}; +/* VBDABUFFDTA */ +enum { + RAMSW_NUMB = 9, + RAMSW_EN, + VBAD0DMA_EN, + VBAD1DMA_EN, + VBDA0DMA_EN, + VBDA1DMA_EN, + VBENABLE, +}; +/* VBDAPATH FM MIXER*/ +enum { + DAPATH_NO_MIX = 0, + DAPATH_ADD_FM, + DAPATH_SUB_FM, +}; + +/* VBADPATH ST INMUX*/ +enum { + ST_INPUT_NORMAL = 0, + ST_INPUT_INVERSE, + ST_INPUT_ZERO, +}; + +/* AD DGMUX NUM*/ +enum { + ADC0_DGMUX = 0, + ADC1_DGMUX, + ADC2_DGMUX, + ADC3_DGMUX, + ADC_DGMUX_MAX +}; + +/* -------------------------- */ + +#define PHYS_VBDA0 (VBC_PHY_BASE + 0x0000) /* 0x0000 Voice band DAC0 data buffer */ +#define PHYS_VBDA1 (VBC_PHY_BASE + 0x0004) /* 0x0004 Voice band DAC1 data buffer */ +#define PHYS_VBAD0 (VBC_PHY_BASE + 0x0008) /* 0x0008 Voice band ADC0 data buffer */ +#define PHYS_VBAD1 (VBC_PHY_BASE + 0x000C) /* 0x000C Voice band ADC1 data buffer */ +#define PHYS_VBAD2 (VBC_PHY_BASE + 0x00C0) /* 0x00C0 Voice band ADC2 data buffer */ +#define PHYS_VBAD3 (VBC_PHY_BASE + 0x00C4) /* 0x00C4 Voice band ADC3 data buffer */ + +#define ARM_VB_BASE VBC_BASE +#define VBDA0 (ARM_VB_BASE + 0x0000) /* Voice band DAC0 data buffer */ +#define VBDA1 (ARM_VB_BASE + 0x0004) /* Voice band DAC1 data buffer */ +#define VBAD0 (ARM_VB_BASE + 0x0008) /* Voice band ADC0 data buffer */ +#define VBAD1 (ARM_VB_BASE + 0x000C) /* Voice band ADC1 data buffer */ +#define VBBUFFSIZE (ARM_VB_BASE + 0x0010) /* Voice band buffer size */ +#define VBADBUFFDTA (ARM_VB_BASE + 0x0014) /* Voice band AD buffer control */ +#define VBDABUFFDTA (ARM_VB_BASE + 0x0018) /* Voice band DA buffer control */ +#define VBADCNT (ARM_VB_BASE + 0x001C) /* Voice band AD buffer counter */ +#define VBDACNT (ARM_VB_BASE + 0x0020) /* Voice band DA buffer counter */ +#define VBAD23CNT (ARM_VB_BASE + 0x0024) /* Voice band AD23 buffer counter */ +#define VBADDMA (ARM_VB_BASE + 0x0028) /* Voice band AD23 DMA control */ +#define VBBUFFAD23 (ARM_VB_BASE + 0x002C) /* Voice band AD23 buffer size */ +#define VBINTTYPE (ARM_VB_BASE + 0x0034) +#define VBDATASWT (ARM_VB_BASE + 0x0038) +#define VBIISSEL (ARM_VB_BASE + 0x003C) + +#define DAPATCHCTL (ARM_VB_BASE + 0x0040) +#define DADGCTL (ARM_VB_BASE + 0x0044) +#define DAHPCTL (ARM_VB_BASE + 0x0048) +#define DAALCCTL0 (ARM_VB_BASE + 0x004C) +#define DAALCCTL1 (ARM_VB_BASE + 0x0050) +#define DAALCCTL2 (ARM_VB_BASE + 0x0054) +#define DAALCCTL3 (ARM_VB_BASE + 0x0058) +#define DAALCCTL4 (ARM_VB_BASE + 0x005C) +#define DAALCCTL5 (ARM_VB_BASE + 0x0060) +#define DAALCCTL6 (ARM_VB_BASE + 0x0064) +#define DAALCCTL7 (ARM_VB_BASE + 0x0068) +#define DAALCCTL8 (ARM_VB_BASE + 0x006C) +#define DAALCCTL9 (ARM_VB_BASE + 0x0070) +#define DAALCCTL10 (ARM_VB_BASE + 0x0074) +#define STCTL0 (ARM_VB_BASE + 0x0078) +#define STCTL1 (ARM_VB_BASE + 0x007C) +#define ADPATCHCTL (ARM_VB_BASE + 0x0080) +#define ADDG01CTL (ARM_VB_BASE + 0x0084) +#define ADDG23CTL (ARM_VB_BASE + 0x0088) +#define ADHPCTL (ARM_VB_BASE + 0x008C) +#define ADCSRCCTL (ARM_VB_BASE + 0x0090) +#define DACSRCCTL (ARM_VB_BASE + 0x0094) +#define MIXERCTL (ARM_VB_BASE + 0x0098) +#define STFIFOLVL (ARM_VB_BASE + 0x009C) +#define VBIRQEN (ARM_VB_BASE + 0x00A0) +#define VBIRQCLR (ARM_VB_BASE + 0x00A4) +#define VBIRQRAW (ARM_VB_BASE + 0x00A8) +#define VBIRQSTS (ARM_VB_BASE + 0x00AC) +#define VBNGCVTHD (ARM_VB_BASE + 0x00B0) +#define VBNGCTTHD (ARM_VB_BASE + 0x00B4) +#define VBNGCTL (ARM_VB_BASE + 0x00B8) + +/* DA HPF EQ6 start, end */ +#define HPCOEF0_H (ARM_VB_BASE + 0x0100) +#define HPCOEF0_L (ARM_VB_BASE + 0x0104) +#define HPCOEF35_H (ARM_VB_BASE + 0x0218) +#define HPCOEF35_L (ARM_VB_BASE + 0x021c) +#define HPCOEF42_H (ARM_VB_BASE + 0x0250) +#define HPCOEF42_L (ARM_VB_BASE + 0x0254) +/* DA HPF EQ4 start, end*/ +#define HPCOEF43_H (ARM_VB_BASE + 0x0258) +#define HPCOEF43_L (ARM_VB_BASE + 0x025C) +#define HPCOEF64_H (ARM_VB_BASE + 0x0300) +#define HPCOEF64_L (ARM_VB_BASE + 0x0304) +#define HPCOEF71_H (ARM_VB_BASE + 0x0338) +#define HPCOEF71_L (ARM_VB_BASE + 0x033C) + +/* AD01 HPF EQ6 start, end */ +#define AD01_HPCOEF0_H (ARM_VB_BASE + 0x0400) +#define AD01_HPCOEF0_L (ARM_VB_BASE + 0x0404) +#define AD01_HPCOEF35_H (ARM_VB_BASE + 0x0418) +#define AD01_HPCOEF35_L (ARM_VB_BASE + 0x041c) +#define AD01_HPCOEF42_H (ARM_VB_BASE + 0x0550) +#define AD01_HPCOEF42_L (ARM_VB_BASE + 0x0554) +/* AD23 HPF EQ6 start, end */ +#define AD23_HPCOEF0_H (ARM_VB_BASE + 0x0600) +#define AD23_HPCOEF0_L (ARM_VB_BASE + 0x0604) +#define AD23_HPCOEF35_H (ARM_VB_BASE + 0x0618) +#define AD23_HPCOEF35_L (ARM_VB_BASE + 0x061c) +#define AD23_HPCOEF42_H (ARM_VB_BASE + 0x0750) +#define AD23_HPCOEF42_L (ARM_VB_BASE + 0x0754) + +#define ARM_VB_END (ARM_VB_BASE + 0x0758) +#define IS_SPRD_VBC_RANG(reg) (((reg) >= ARM_VB_BASE) && ((reg) < ARM_VB_END)) +#define SPRD_VBC_BASE_HI (ARM_VB_BASE & 0xFFFF0000) + +#define VBADBUFFERSIZE_SHIFT (0) +#define VBADBUFFERSIZE_MASK (0xFF<<VBADBUFFERSIZE_SHIFT) +#define VBDABUFFERSIZE_SHIFT (8) +#define VBDABUFFERSIZE_MASK (0xFF<<VBDABUFFERSIZE_SHIFT) +#define VBAD23BUFFERSIZE_SHIFT (0) +#define VBAD23BUFFERSIZE_MASK (0xFF<<VBAD23BUFFERSIZE_SHIFT) + +#define VBCHNEN (ARM_VB_BASE + 0x00C8) +#define VBDA0_EN (0) +#define VBDA1_EN (1) +#define VBAD0_EN (2) +#define VBAD1_EN (3) +#define VBAD2_EN (4) +#define VBAD3_EN (5) + +#define VBDACHEN_SHIFT (0) +#define VBADCHEN_SHIFT (2) +#define VBAD23CHEN_SHIFT (4) + +#define VBAD2DMA_EN (0) +#define VBAD3DMA_EN (1) + + /* VBIISSEL */ +#define VBIISSEL_AD01_PORT_SHIFT (0) +#define VBIISSEL_AD01_PORT_MASK (0x7) +#define VBIISSEL_AD23_PORT_SHIFT (3) +#define VBIISSEL_AD23_PORT_MASK (0x7<<VBIISSEL_AD23_PORT_SHIFT) +#define VBIISSEL_DA_PORT_SHIFT (6) +#define VBIISSEL_DA_PORT_MASK (0x7<<VBIISSEL_DA_PORT_SHIFT) +#define VBIISSEL_DA_LRCK (14) +#define VBIISSEL_AD01_LRCK (13) + /* VBDATASWT */ +#define VBDATASWT_AD23_LRCK (6) + /* DAPATHCTL */ +#define VBDAPATH_DA0_ADDFM_SHIFT (0) +#define VBDAPATH_DA0_ADDFM_MASK (0x3<<VBDAPATH_DA0_ADDFM_SHIFT) +#define VBDAPATH_DA1_ADDFM_SHIFT (2) +#define VBDAPATH_DA1_ADDFM_MASK (0x3<<VBDAPATH_DA1_ADDFM_SHIFT) +#define VBDAPATH_DA0_ADDST_SHIFT (4) +#define VBDAPATH_DA0_ADDST_MASK (0x3<<VBDAPATH_DA0_ADDST_SHIFT) +#define VBDAPATH_DA1_ADDST_SHIFT (6) +#define VBDAPATH_DA1_ADDST_MASK (0x3<<VBDAPATH_DA1_ADDST_SHIFT) + + /* ADPATHCTL */ +#define VBADPATH_AD0_INMUX_SHIFT (0) +#define VBADPATH_AD0_INMUX_MASK (0x3<<VBADPATH_AD0_INMUX_SHIFT) +#define VBADPATH_AD1_INMUX_SHIFT (2) +#define VBADPATH_AD1_INMUX_MASK (0x3<<VBADPATH_AD1_INMUX_SHIFT) +#define VBADPATH_AD2_INMUX_SHIFT (4) +#define VBADPATH_AD2_INMUX_MASK (0x3<<VBADPATH_AD2_INMUX_SHIFT) +#define VBADPATH_AD3_INMUX_SHIFT (6) +#define VBADPATH_AD3_INMUX_MASK (0x3<<VBADPATH_AD3_INMUX_SHIFT) +#define VBADPATH_AD0_DGMUX_SHIFT (8) +#define VBADPATH_AD0_DGMUX_MASK (0x1<<VBADPATH_AD0_DGMUX_SHIFT) +#define VBADPATH_AD1_DGMUX_SHIFT (9) +#define VBADPATH_AD1_DGMUX_MASK (0x1<<VBADPATH_AD1_DGMUX_SHIFT) +#define VBADPATH_AD2_DGMUX_SHIFT (10) +#define VBADPATH_AD2_DGMUX_MASK (0x1<<VBADPATH_AD2_DGMUX_SHIFT) +#define VBADPATH_AD3_DGMUX_SHIFT (11) +#define VBADPATH_AD3_DGMUX_MASK (0x1<<VBADPATH_AD3_DGMUX_SHIFT) +#define VBADPATH_ST0_INMUX_SHIFT (12) +#define VBADPATH_ST0_INMUX_MASK (0x3<<VBADPATH_ST0_INMUX_SHIFT) +#define VBADPATH_ST1_INMUX_SHIFT (14) +#define VBADPATH_ST1_INMUX_MASK (0x3<<VBADPATH_ST1_INMUX_SHIFT) + /* DACSRCCTL */ +#define VBDACSRC_EN (0) +#define VBDACSRC_CLR (1) +#define VBDACSRC_F1F2F3_BP (3) +#define VBDACSRC_F1_SEL (4) +#define VBDACSRC_F0_BP (5) +#define VBDACSRC_F0_SEL (6) + /* ADCSRCCTL */ +#define VBADCSRC_EN_01 (0) +#define VBADCSRC_CLR_01 (1) +#define VBADCSRC_PAUSE_01 (2) +#define VBADCSRC_F1F2F3_BP_01 (3) +#define VBADCSRC_F1_SEL_01 (4) +#define VBADCSRC_EN_23 (8) +#define VBADCSRC_CLR_23 (9) +#define VBADCSRC_PAUSE_23 (10) +#define VBADCSRC_F1F2F3_BP_23 (11) +#define VBADCSRC_F1_SEL_23 (12) +/* STCTL0 */ +#define VBST_EN_0 (12) +#define VBST_HPF_0 (11) +/* STCTL1 */ +#define VBST_HPF_1 (11) +#define VBST_EN_1 (12) +#define VBST0_SEL_CHN (13) +#define VBST1_SEL_CHN (14) + /* DAHPCTL */ +#define VBDAEQ_HP_REG_CLR (9) +#define VBDAC_EQ6_EN (10) +#define VBDAC_ALC_EN (11) +#define VBDAC_EQ4_EN (12) +#define VBDAC_EQ4_POS_SEL (13) +#define VBDAC_ALC_DP_T_MODE (14) + /* ADHPCTL */ +#define VBADC01_EQ6_EN (0) +#define VBADC23_EQ6_EN (1) +#define VBADC01EQ_HP_REG_CLR (2) +#define VBADC23EQ_HP_REG_CLR (3) + +enum { + VBC_LEFT = 0, + VBC_RIGHT = 1, +}; + +enum { + VBC_PLAYBACK = 0, + SPRD_VBC_PLAYBACK_COUNT = 1, + VBC_CAPTRUE = 1, + VBC_CAPTRUE1 = 2, + VBC_IDX_MAX +}; + +static const char *vbc_get_name(int vbc_idx); + +static int vbc_reg_read(unsigned int reg); +static void vbc_reg_raw_write(unsigned int reg, int val); +static int vbc_reg_write(unsigned int reg, int val); +static int vbc_reg_update(unsigned int reg, int val, int mask); +static void vbc_da_buffer_clear_all(int vbc_idx); +static int vbc_enable(int enable); +static void vbc_clk_set(struct clk *clk); +static struct clk *vbc_clk_get(void); +static int vbc_power(int enable); +static int vbc_chan_enable(int enable, int vbc_idx, int chan); +static int vbc_src_set(int rate, int vbc_idx); +static int vbc_codec_startup(int vbc_idx, struct snd_soc_dai *dai); +static int fm_set_vbc_buffer_size(void); +static int vbc_set_phys_addr(int vbc_switch); + +static inline void vbc_safe_mem_release(void **free) +{ + if (*free) { + kfree(*free); + *free = NULL; + } +} + +#define vbc_safe_kfree(p) vbc_safe_mem_release((void**)p) + +#endif /* __VBC_COMM_H */ diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc.c b/sound/soc/sprd/dai/vbc/r2p0/vbc.c new file mode 100644 index 00000000..84a45b67 --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc.c @@ -0,0 +1,807 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc.c + * + * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "sprd-asoc-debug.h" +#define pr_fmt(fmt) pr_sprd_fmt(" VBC ") fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/workqueue.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "sprd-asoc-common.h" +#ifndef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE +#include "sprd-pcm.h" +#else +#include "sprd-dmaengine-pcm.h" +#endif +#include "vbc.h" +#include "dfm.h" + +typedef int (*vbc_dma_set) (int enable); + +struct sprd_vbc_buffer_info { + int reg; + int shift; + int mask; + int max; +}; + +struct sprd_vbc_priv { + vbc_dma_set dma_set[2]; + int (*arch_enable) (int chan); + int (*arch_disable) (int chan); + int used_chan_count; + struct sprd_vbc_buffer_info buf_info; + int rate; +}; + +struct sprd_dfm_priv dfm = { 0, 0 }; + +EXPORT_SYMBOL_GPL(dfm); + +static struct sprd_pcm_dma_params vbc_pcm_stereo_out = { + .name = "VBC PCM Stereo out", + .irq_type = BLK_DONE, + .desc = { + .datawidth = SHORT_WIDTH, + .fragmens_len = VBC_FIFO_FRAME_NUM * 2, + .src_step = 2, + .des_step = 0, + }, +}; + +static struct sprd_pcm_dma_params vbc_pcm_stereo_in = { + .name = "VBC PCM Stereo in", + .irq_type = BLK_DONE, + .desc = { + .datawidth = SHORT_WIDTH, + .fragmens_len = VBC_FIFO_FRAME_NUM * 2, + .src_step = 0, + .des_step = 2, + }, +}; + +static struct sprd_pcm_dma_params vbc_pcm23_stereo_in = { + .name = "VBC PCM23 Stereo in", + .irq_type = BLK_DONE, + .desc = { + .datawidth = SHORT_WIDTH, + .fragmens_len = VBC_FIFO_FRAME_NUM * 2, + .src_step = 0, + .des_step = 2, + }, +}; + +static struct sprd_vbc_priv vbc[VBC_IDX_MAX]; +static unsigned long sprd_vbc_base = 0; +static unsigned int sprd_vbc_phy_base = 0; + +static int vbc_iis_high_for_da1(int enable) +{ + vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_DA_LRCK, + 1 << VBIISSEL_DA_LRCK); + return 0; +} + +#if 0 +static int vbc_iis_high_for_ad1(int enable) +{ + vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_AD01_LRCK, + 1 << VBIISSEL_AD01_LRCK); + return 0; +} + +static int vbc_iis_high_for_ad2(int enable) +{ + vbc_reg_update(VBDATASWT, (enable ? 1 : 0) << VBDATASWT_AD23_LRCK, + 1 << VBDATASWT_AD23_LRCK); + return 0; +} +#endif + +static int vbc_set_buffer_size(int vbc_idx, int buffer_size) +{ + int val = vbc_reg_read(vbc[vbc_idx].buf_info.reg); + WARN_ON(buffer_size > vbc[vbc_idx].buf_info.max); + if ((buffer_size > 0) + && (buffer_size <= vbc[vbc_idx].buf_info.max)) { + val &= ~(vbc[vbc_idx].buf_info.mask); + val |= (((buffer_size - 1) << vbc[vbc_idx].buf_info.shift) + & vbc[vbc_idx].buf_info.mask); + vbc_reg_update(vbc[vbc_idx].buf_info.reg, val, + vbc[vbc_idx].buf_info.mask); + } else { + pr_err("ERR:buffer_size error! %d\n", buffer_size); + return -EINVAL; + } + return 0; +} + +static int fm_set_vbc_buffer_size(void) +{ + int i; + for (i = SPRD_VBC_PLAYBACK_COUNT; i < VBC_IDX_MAX; i++) { + vbc_set_buffer_size(i, VBC_FIFO_FRAME_NUM); + } + return 0; +} + +static inline int vbc_sw_write_buffer(int enable) +{ + /* Software access ping-pong buffer enable when VBENABE bit low */ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << RAMSW_EN), + (1 << RAMSW_EN)); + return 0; +} + +static inline int vbc_ad0_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD0DMA_EN), + (1 << VBAD0DMA_EN)); + return 0; +} + +static inline int vbc_ad1_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD1DMA_EN), + (1 << VBAD1DMA_EN)); + return 0; +} + +static inline int vbc_ad2_dma_set(int enable) +{ + vbc_reg_update(VBADDMA, ((enable ? 1 : 0) << VBAD2DMA_EN), + (1 << VBAD2DMA_EN)); + return 0; +} + +static inline int vbc_ad3_dma_set(int enable) +{ + vbc_reg_update(VBADDMA, ((enable ? 1 : 0) << VBAD3DMA_EN), + (1 << VBAD3DMA_EN)); + return 0; +} + +static inline int vbc_da0_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA0DMA_EN), + (1 << VBDA0DMA_EN)); + return 0; +} + +static inline int vbc_da1_dma_set(int enable) +{ + vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA1DMA_EN), + (1 << VBDA1DMA_EN)); + return 0; +} + +static void vbc_da_buffer_clear(int id) +{ + int i; + vbc_reg_update(VBDABUFFDTA, ((id ? 1 : 0) << RAMSW_NUMB), + (1 << RAMSW_NUMB)); + for (i = 0; i < VBC_FIFO_FRAME_NUM; i++) { + vbc_reg_raw_write(VBDA0, 0); + vbc_reg_raw_write(VBDA1, 0); + } +} + +static void vbc_da_buffer_clear_all(int vbc_idx) +{ + int i; + + for (i = 0; i < 2; i++) { + vbc[vbc_idx].arch_enable(i); + } + + vbc_sw_write_buffer(true); + vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM); + vbc_da_buffer_clear(1); /* clear data buffer 1 */ + vbc_da_buffer_clear(0); /* clear data buffer 0 */ + vbc_sw_write_buffer(false); + + for (i = 0; i < 2; i++) { + vbc[vbc_idx].arch_disable(i); + } +} + +static int vbc_da_arch_enable(int chan) +{ + return vbc_chan_enable(1, VBC_PLAYBACK, chan); +} + +static int vbc_da_arch_disable(int chan) +{ + return vbc_chan_enable(0, VBC_PLAYBACK, chan); +} + +static int vbc_ad_arch_enable(int chan) +{ + return vbc_chan_enable(1, VBC_CAPTRUE, chan); +} + +static int vbc_ad_arch_disable(int chan) +{ + return vbc_chan_enable(0, VBC_CAPTRUE, chan); +} + +static int vbc_ad23_arch_enable(int chan) +{ + return vbc_chan_enable(1, VBC_CAPTRUE1, chan); +} + +static int vbc_ad23_arch_disable(int chan) +{ + return vbc_chan_enable(0, VBC_CAPTRUE1, chan); +} + +static struct sprd_vbc_priv vbc[VBC_IDX_MAX] = { + /* All Playback */ + { /*PlayBack */ + .dma_set = {vbc_da0_dma_set, vbc_da1_dma_set}, + .arch_enable = vbc_da_arch_enable, + .arch_disable = vbc_da_arch_disable, + .buf_info = {VBBUFFSIZE, VBDABUFFERSIZE_SHIFT, VBDABUFFERSIZE_MASK, + VBC_FIFO_FRAME_NUM}, + }, + + /* All Capture */ + { /*Capture for ad01 */ + .dma_set = {vbc_ad0_dma_set, vbc_ad1_dma_set}, + .arch_enable = vbc_ad_arch_enable, + .arch_disable = vbc_ad_arch_disable, + .buf_info = {VBBUFFSIZE, VBADBUFFERSIZE_SHIFT, VBADBUFFERSIZE_MASK, + VBC_FIFO_FRAME_NUM}, + }, + { /*Capture for ad23 */ + .dma_set = {vbc_ad2_dma_set, vbc_ad3_dma_set}, + .arch_enable = vbc_ad23_arch_enable, + .arch_disable = vbc_ad23_arch_disable, + .buf_info = {VBBUFFAD23, VBAD23BUFFERSIZE_SHIFT, VBAD23BUFFERSIZE_MASK, + VBC_FIFO_FRAME_NUM}, + }, +}; + +/* NOTE: + this index need use for the [struct sprd_vbc_priv] vbc[3] index + default MUST return 0. + */ +static inline int vbc_str_2_index(int stream, int id) +{ + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + return id + SPRD_VBC_PLAYBACK_COUNT; + } + return id; +} + +static int vbc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + int ret; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + sp_asoc_pr_dbg("dfm.hw_rate:%d, dfm.sample_rate:%d", dfm.hw_rate, + dfm.sample_rate); + + if (dfm.sample_rate != 0) { + if ((vbc_idx == VBC_PLAYBACK) || (vbc_idx == VBC_CAPTRUE)) { + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + dfm.sample_rate, + dfm.sample_rate); + if (ret < 0) { + pr_err("constraint error"); + return ret; + } + } + } + + vbc_power(1); + + vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM); + vbc_codec_startup(vbc_idx, dai); + + WARN_ON(!vbc[vbc_idx].arch_enable); + WARN_ON(!vbc[vbc_idx].arch_disable); + WARN_ON(!vbc[vbc_idx].dma_set[0]); + WARN_ON(!vbc[vbc_idx].dma_set[1]); + + return 0; +} + +static void vbc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + /* vbc da close MUST clear da buffer */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + vbc_da_buffer_clear_all(vbc_idx); + } + + vbc_power(0); +} + +static int vbc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int vbc_idx; + struct sprd_pcm_dma_params *dma_data[VBC_IDX_MAX] = { + &vbc_pcm_stereo_out, + &vbc_pcm_stereo_in, + &vbc_pcm23_stereo_in, + }; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + snd_soc_dai_set_dma_data(dai, substream, dma_data[vbc_idx]); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + pr_err("ERR:VBC Only Supports Format S16_LE\n"); + break; + } + + vbc[vbc_idx].used_chan_count = params_channels(params); + if (vbc[vbc_idx].used_chan_count > 2) { + pr_err("ERR:VBC Can NOT Supports Grate 2 Channels\n"); + } + + vbc_iis_high_for_da1(1); +#ifdef CONFIG_SND_SOC_SPRD_VBC_LR_INVERT + if (vbc[vbc_idx].used_chan_count == 2) { + vbc_iis_high_for_da1(0); + } +#endif + +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (params_rate(params) == 44100) { + if ((vbc_idx == 1 && dfm.hw_rate == 0) ||(vbc_idx == 2)) {/*dfm is closed for ad01 */ + vbc_src_set(CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE, + vbc_idx); + } + } else { + vbc_src_set(0, vbc_idx); /*close adc src */ + } + } +#endif + + vbc[vbc_idx].rate = params_rate(params); + return 0; +} + +static int vbc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int vbc_idx; + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx)); + + vbc[vbc_idx].rate = 0; + + return 0; +} +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DMA_ENGINE +struct sprd_runtime_data { + int dma_addr_offset; + struct sprd_pcm_dma_params *params; + struct dma_chan *dma_chn[2]; + struct sprd_dma_cfg *dma_cfg_array; + dma_addr_t dma_cfg_phy[2]; + void *dma_cfg_virt[2]; + struct dma_async_tx_descriptor *dma_tx_des[2]; + dma_cookie_t cookie[2]; + int int_pos_update[2]; + + //dma_addr_t *dma_desc_array; + //dma_addr_t dma_desc_array_phys; + int burst_len; + int hw_chan; + int dma_pos_pre[2]; + int interleaved; + int cb_called; + int new_pos; + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_BUFFER_USE_IRAM + int buffer_in_iram; +#endif +#ifdef CONFIG_SND_VERBOSE_PROCFS + struct snd_info_entry *proc_info_entry; +#endif +}; +#endif + +static int vbc_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int vbc_idx; + int ret = 0; + int i; + +#if 0 + sp_asoc_pr_dbg("%s\n", __func__); +#endif + + vbc_idx = vbc_str_2_index(substream->stream, dai->id); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) { + vbc[vbc_idx].arch_enable(i); + vbc[vbc_idx].dma_set[i] (1); + } + vbc_enable(1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) { + vbc[vbc_idx].arch_disable(i); + vbc[vbc_idx].dma_set[i] (0); + } + vbc_enable(0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct snd_soc_dai_ops vbc_dai_ops = { + .startup = vbc_startup, + .shutdown = vbc_shutdown, + .hw_params = vbc_hw_params, + .trigger = vbc_trigger, + .hw_free = vbc_hw_free, +}; + +static int dfm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + static const unsigned int dfm_all_rates[] = { 32000, 44100, 48000, 8000}; + static const struct snd_pcm_hw_constraint_list dfm_rates_constraint = { + .count = ARRAY_SIZE(dfm_all_rates), + .list = dfm_all_rates, + }; + int ret; + int vbc_idx; + struct snd_soc_card *card = dai->card; + int i; + + sp_asoc_pr_dbg("%s\n, vbc[vbc_idx].rate:da:%d, ad:%d, ad23:%d", + __func__, vbc[0].rate, vbc[1].rate, vbc[2].rate); + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &dfm_rates_constraint); + if (ret < 0) + return ret; + + for (vbc_idx = VBC_PLAYBACK; vbc_idx < VBC_CAPTRUE1; vbc_idx++) { + if (vbc[vbc_idx].rate != 0) { +#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN + if (vbc[vbc_idx].rate != 44100) + return -1; +#else + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + vbc[vbc_idx].rate, + vbc[vbc_idx].rate); + if (ret < 0) { + pr_err("constraint error"); + return ret; + } +#endif + } + } + + kfree(snd_soc_dai_get_dma_data(dai, substream)); + snd_soc_dai_set_dma_data(dai, substream, NULL); + + snd_soc_dapm_enable_pin(&dai->codec->dapm, "DFM"); + snd_soc_dapm_sync(&dai->codec->dapm); + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 1; + } + snd_soc_dapm_ignore_suspend(&dai->codec->dapm, "DFM"); + + return 0; +} + +static void dfm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = dai->card; + int i; + sp_asoc_pr_dbg("%s\n", __func__); + + snd_soc_dapm_disable_pin(&dai->codec->dapm, "DFM"); + snd_soc_dapm_sync(&dai->codec->dapm); + for (i = 0; i < card->num_rtd; i++) { + card->rtd[i].dai_link->ignore_suspend = 0; + } +} + +static int dfm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + sp_asoc_pr_dbg("%s \n", __func__); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + pr_err("ERR:VBC Only Supports Format S16_LE\n"); + break; + } + + return 0; +} + +static int dfm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + sp_asoc_pr_dbg("%s \n", __func__); + dfm.sample_rate = 0; + dfm.hw_rate = 0; + return 0; +} + +static struct snd_soc_dai_ops dfm_dai_ops = { + .startup = dfm_startup, + .shutdown = dfm_shutdown, + .hw_params = dfm_hw_params, + .hw_free = dfm_hw_free, +}; + +static struct snd_soc_dai_driver vbc_dai[] = { + { + .name = "vbc-r2p0", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, /* AD01 */ + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &vbc_dai_ops, + }, + { + .name = "vbc-r2p0-ad23", + .id = 1, + .capture = { + .channels_min = 1, + .channels_max = 2, /* AD23 */ + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &vbc_dai_ops, + }, + { + .name = "vbc-dfm", + .id = DFM_MAGIC_ID, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &dfm_dai_ops, + } +}; + +static const struct snd_soc_component_driver sprd_vbc_component = { + .name = "vbc", +}; + +static int vbc_set_phys_addr(int vbc_switch) +{ + if (vbc_switch == AUDIO_TO_CP2_ARM_CTRL) { /*switch to cp2-arm */ + vbc_pcm_stereo_out.dev_paddr[0] = CP2_PHYS_VBDA0; + vbc_pcm_stereo_out.dev_paddr[1] = CP2_PHYS_VBDA1; + } else { + vbc_pcm_stereo_out.dev_paddr[0] = PHYS_VBDA0 + sprd_vbc_phy_base; + vbc_pcm_stereo_out.dev_paddr[1] = PHYS_VBDA1 + sprd_vbc_phy_base; + } + return 0; +} + +static int sprd_vbc_codec_probe(struct platform_device *pdev); +static int vbc_drv_probe(struct platform_device *pdev) +{ + int i; + int ret; + struct clk *vbc_clk; + struct device_node *node = pdev->dev.of_node; + unsigned int val_arr[2] = {0}; + + sp_asoc_pr_dbg("%s\n", __func__); + + arch_audio_vbc_switch(AUDIO_TO_ARM_CTRL); + ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE); + if (ret != AUDIO_TO_ARM_CTRL) { + pr_err("Failed to Switch VBC to AP\n"); + return -1; + } + + if(node){ + if (!of_property_read_u32_array(node, "sprd,reg_vbc", &val_arr[0], 2)) { + sprd_vbc_base = (unsigned long)ioremap_nocache(val_arr[0], val_arr[1]); + if(!sprd_vbc_base) { + pr_err("ERR: cannot create iomap address for VBC!\n"); + return -EINVAL; + } + sprd_vbc_phy_base = val_arr[0]; + } else { + pr_err("ERR:Must give me the VBC reg address!\n"); + return -EINVAL; + } + } else { + BUG(); + /* + sprd_vbc_base = ioremap_nocache(VBC_BASE_DEFAULT, VBC_BASE_SIZE_DEFAULT); + if(!sprd_vbc_base) { + pr_err("ERR: cannot create iomap address for VBC with default address!\n"); + return -EINVAL; + } + sprd_vbc_phy_base = VBC_BASE_DEFAULT; + */ + } + + vbc_pcm_stereo_out.dev_paddr[0] = PHYS_VBDA0 + sprd_vbc_phy_base; + vbc_pcm_stereo_out.dev_paddr[1] = PHYS_VBDA1 + sprd_vbc_phy_base; + vbc_pcm_stereo_in.dev_paddr[0] = PHYS_VBAD0 + sprd_vbc_phy_base; + vbc_pcm_stereo_in.dev_paddr[1] = PHYS_VBAD1 + sprd_vbc_phy_base; + vbc_pcm23_stereo_in.dev_paddr[0]= PHYS_VBAD2 + sprd_vbc_phy_base; + vbc_pcm23_stereo_in.dev_paddr[1]= PHYS_VBAD3 + sprd_vbc_phy_base; + + for (i = 0; i < 2; i++) { + vbc_pcm_stereo_out.channels[i] = arch_audio_vbc_da_dma_info(i); + vbc_pcm_stereo_in.channels[i] = arch_audio_vbc_ad_dma_info(i); + vbc_pcm23_stereo_in.channels[i] = + arch_audio_vbc_ad23_dma_info(i); + } + + /* 1. probe CODEC */ + ret = sprd_vbc_codec_probe(pdev); + + if (ret < 0) { + goto probe_err; + } + + /* 2. probe DAIS */ + ret = + snd_soc_register_component(&pdev->dev, &sprd_vbc_component, vbc_dai, + ARRAY_SIZE(vbc_dai)); + + if (ret < 0) { + pr_err("ERR:Register VBC to DAIS Failed!\n"); + goto probe_err; + } + + vbc_clk = clk_get(&pdev->dev, "clk_vbc"); + if (IS_ERR(vbc_clk)) { + pr_err("Cannot request clk_vbc\n"); + } else { + vbc_clk_set(vbc_clk); + } + + return ret; +probe_err: + iounmap(sprd_vbc_base); + sp_asoc_pr_dbg("return %i\n", ret); + + return ret; +} + +static int sprd_vbc_codec_remove(struct platform_device *pdev); +static int vbc_drv_remove(struct platform_device *pdev) +{ + struct clk *vbc_clk = vbc_clk_get(); + + if (vbc_clk) { + clk_put(vbc_clk); + vbc_clk_set(0); + } + + snd_soc_unregister_component(&pdev->dev); + + sprd_vbc_codec_remove(pdev); + + iounmap(sprd_vbc_base); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id vbc_of_match[] = { + {.compatible = "sprd,vbc-r2p0",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vbc_of_match); +#endif + +static struct platform_driver vbc_driver = { + .driver = { + .name = "vbc-r2p0", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(vbc_of_match), + }, + + .probe = vbc_drv_probe, + .remove = vbc_drv_remove, +}; + +static int __init sprd_vbc_driver_init(void) +{ + return platform_driver_register(&vbc_driver); +} + +late_initcall(sprd_vbc_driver_init); + +/* include the other module of VBC */ +#include "vbc-comm.c" +#include "vbc-codec.c" + +MODULE_DESCRIPTION("SPRD ASoC VBC CUP-DAI driver"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cpu-dai:vbc-r2p0"); diff --git a/sound/soc/sprd/dai/vbc/r2p0/vbc.h b/sound/soc/sprd/dai/vbc/r2p0/vbc.h new file mode 100644 index 00000000..4949fb7e --- /dev/null +++ b/sound/soc/sprd/dai/vbc/r2p0/vbc.h @@ -0,0 +1,22 @@ +/* + * sound/soc/sprd/dai/vbc/r2p0/vbc.h + * + * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function. + * + * Copyright (C) 2013 SpreadTrum Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY ork FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __VBC_H +#define __VBC_H + +#include "vbc-comm.h" + +#endif /* __VBC_H */ diff --git a/sound/soc/sprd/i2s-r0p0-null-codec.c b/sound/soc/sprd/i2s-r0p0-null-codec.c new file mode 100755 index 00000000..208e4073 --- /dev/null +++ b/sound/soc/sprd/i2s-r0p0-null-codec.c @@ -0,0 +1,228 @@ +/* + * sound/soc/sprd/i2s-null-codec.c + * + * 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("I2SNC") fmt + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <sound/soc.h> +#include "sprd-asoc-common.h" +#include <soc/sprd/i2s.h> + +#define NAME_SIZE 32 + +static struct snd_soc_dai_link all_i2s_dai[] = { + { + .name = "all-i2s", + .stream_name = "i2s", + + .codec_name = "null-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "i2s_bt_sco0", + .codec_dai_name = "null-codec-dai", + .ignore_suspend = 1, + }, +}; + +#ifdef CONFIG_PROC_FS +static int i2s_debug_init(struct snd_soc_card *card) +{ + struct snd_info_entry *entry; + if ((entry = snd_info_create_card_entry(card->snd_card, "i2s-debug", + card->snd_card->proc_root)) != NULL) { + entry->c.text.read = i2s_debug_read; + entry->c.text.write = i2s_debug_write; + entry->mode |= S_IWUSR; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + return 0; +} +static void i2s_register_proc_init(struct snd_soc_card *card) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(card->snd_card, "i2s-reg", &entry)) + snd_info_set_text_ops(entry, NULL, i2s_register_proc_read); +} +#else /* !CONFIG_PROC_FS */ +static void i2s_register_proc_init(struct snd_soc_card *card) +{ +} +static int i2s_debug_init(struct snd_soc_card *card) +{ +} +#endif +static int board_late_probe(struct snd_soc_card *card) +{ + i2s_debug_init(card); + i2s_register_proc_init(card); + return 0; +} + +#define I2S_CONFIG(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, LONG_MAX, 0, i2s_config_get, i2s_config_set) + +static const struct snd_kcontrol_new i2s_config_snd_controls[] = { + I2S_CONFIG("fs", FS), + I2S_CONFIG("hw_port", HW_PROT), + I2S_CONFIG("slave_timeout", SLAVE_TIMEOUT), + I2S_CONFIG("bus_type", BUS_TYPE), + I2S_CONFIG("byte_per_chan", BYTE_PER_CHAN), + I2S_CONFIG("mode", MODE), + I2S_CONFIG("lsb", LSB), + I2S_CONFIG("rtx_mode", TRX_MODE), + I2S_CONFIG("lrck_inv", LRCK_INV), + I2S_CONFIG("sync_mode", SYNC_MODE), + I2S_CONFIG("clk_inv", CLK_INV), + I2S_CONFIG("i2s_bus_mode", I2S_BUS_MODE), + I2S_CONFIG("pcm_bus_mode", PCM_BUS_MODE), + I2S_CONFIG("pcm_slot", PCM_SLOT), + I2S_CONFIG("pcm_cycle", PCM_CYCLE), + I2S_CONFIG("tx_watermark", TX_WATERMARK), + I2S_CONFIG("rx_watermark", RX_WATERMARK), +}; + +static struct snd_soc_card all_i2s_card = { + .name = "all-i2s", + .dai_link = all_i2s_dai, + .num_links = ARRAY_SIZE(all_i2s_dai), + .owner = THIS_MODULE, + .late_probe = board_late_probe, + .controls = i2s_config_snd_controls, + .num_controls = ARRAY_SIZE(i2s_config_snd_controls), +}; + +static int sprd_asoc_i2s_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &all_i2s_card; + struct device_node *node = pdev->dev.of_node; + card->dev = &pdev->dev; + if (node) { + int i; + int dai_count; + struct device_node *pcm_node; + struct device_node *codec_node; + + if (snd_soc_of_parse_card_name(card, "sprd,model")) { + pr_err("ERR:Card name is not provided\n"); + return -ENODEV; + } + + pcm_node = of_parse_phandle(node, "sprd,pcm", 0); + if (!pcm_node) { + pr_err("ERR:PCM node is not provided\n"); + return -EINVAL; + } + + codec_node = of_parse_phandle(node, "sprd,codec", 0); + if (!codec_node) { + pr_err("ERR:CODEC node is not provided\n"); + of_node_put(pcm_node); + return -EINVAL; + } + + if (!of_get_property(node, "sprd,i2s", &dai_count)) + return -ENOENT; + dai_count /= sizeof(((struct property *) 0)->length); + + sp_asoc_pr_dbg("Register I2S from DTS count is %d\n", + dai_count); + + card->dai_link = + devm_kzalloc(&pdev->dev, + dai_count * sizeof(struct snd_soc_dai_link), + GFP_KERNEL); + card->num_links = dai_count; + + for (i = 0; i < card->num_links; i++) { + char uni_name[NAME_SIZE] = { 0 }; + char uni_sname[NAME_SIZE] = { 0 }; + struct device_node *dai_node; + dai_node = of_parse_phandle(node, "sprd,i2s", i); + if (dai_node) + sp_asoc_pr_dbg("Register I2S dai node is %s\n", + dai_node->full_name); + else + pr_err("ERR:I2S dai node is not provided\n"); + card->dai_link[i] = all_i2s_dai[0]; + snprintf(uni_name, NAME_SIZE, "%s.%d", + all_i2s_dai[0].name, i); + snprintf(uni_sname, NAME_SIZE, "%s.%d", + all_i2s_dai[0].stream_name, i); + card->dai_link[i].name = kstrdup(uni_name, GFP_KERNEL); + card->dai_link[i].stream_name = + kstrdup(uni_sname, GFP_KERNEL); + card->dai_link[i].cpu_dai_name = NULL; + card->dai_link[i].cpu_of_node = dai_node; + card->dai_link[i].platform_name = NULL; + card->dai_link[i].platform_of_node = pcm_node; + card->dai_link[i].codec_name = NULL; + card->dai_link[i].codec_of_node = codec_node; + of_node_put(dai_node); + } + of_node_put(pcm_node); + of_node_put(codec_node); + } + return snd_soc_register_card(card); +} + +static int sprd_asoc_i2s_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + int i = 0; + if (all_i2s_dai != card->dai_link) { + for (i = 0; i < card->num_links; i++) { + kfree(card->dai_link[i].name); + kfree(card->dai_link[i].stream_name); + } + } + snd_soc_unregister_card(card); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id i2s_null_codec_of_match[] = { + {.compatible = "sprd,i2s-null-codec",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, i2s_null_codec_of_match); +#endif + +static struct platform_driver sprd_asoc_i2s_driver = { + .driver = { + .name = "i2s-null-codec", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(i2s_null_codec_of_match), + }, + .probe = sprd_asoc_i2s_probe, + .remove = sprd_asoc_i2s_remove, +}; + +static int __init sprd_asoc_i2s_init(void) +{ + return platform_driver_register(&sprd_asoc_i2s_driver); +} + +late_initcall_sync(sprd_asoc_i2s_init); + +MODULE_DESCRIPTION("ALSA SoC SpreadTrum I2S"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("machine:i2s"); diff --git a/sound/soc/sprd/sprd-asoc-card.c b/sound/soc/sprd/sprd-asoc-card.c new file mode 100644 index 00000000..ce60a47f --- /dev/null +++ b/sound/soc/sprd/sprd-asoc-card.c @@ -0,0 +1,566 @@ +/* + * sound/soc/sprd/sprd-asoc-card.c + * + * 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("BOARD") fmt + +#include <linux/module.h> +#include <linux/of.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/sprd-audio-hook.h> + +#include <soc/sprd/sprd-audio.h> +#include "sprd-asoc-common.h" +#include "dai/vbc/dfm.h" + +#define FUN_REG(f) ((unsigned short)(-(f+1))) + +#define SWITCH_FUN_ON 1 +#define SWITCH_FUN_OFF 0 + +enum { + BOARD_FUNC_SPKL = 0, + BOARD_FUNC_SPKR, + BOARD_FUNC_EAR, + BOARD_FUNC_HP, + BOARD_FUNC_MUTE_MAX, + BOARD_FUNC_LINE = BOARD_FUNC_MUTE_MAX, + BOARD_FUNC_MIC, + BOARD_FUNC_AUXMIC, + BOARD_FUNC_HP_MIC, + BOARD_FUNC_DMIC, + BOARD_FUNC_DMIC1, + BOARD_FUNC_DFM, + BOARD_FUNC_MAX +}; + +struct board_mute { + int need_mute; + int is_on; + int (*mute_func) (struct snd_soc_card *, int); +}; + +static struct board_priv { + int func[BOARD_FUNC_MAX]; + struct board_mute m[BOARD_FUNC_MUTE_MAX]; + int pa_type[BOARD_FUNC_MUTE_MAX]; +} board; + +#define BOARD_EXT_SPK "Ext Spk" +#define BOARD_EXT_SPK2 "Ext Spk2" +#define BOARD_EXT_EAR "Ext Ear" +#define BOARD_HEADPHONE_JACK "HeadPhone Jack" +#define BOARD_LINE_JACK "Line Jack" +#define BOARD_MIC_JACK "Mic Jack" +#define BOARD_AUX_MIC_JACK "Aux Mic Jack" +#define BOARD_HP_MIC_JACK "HP Mic Jack" +#define BOARD_DMIC_JACK "DMic Jack" +#define BOARD_DMIC1_JACK "DMic1 Jack" +#define BOARD_DIG_FM_JACK "Dig FM Jack" +#define BOARD_INTER_HP_PA "inter HP PA" +#define BOARD_INTER_SPK_PA "inter Spk PA" +#define BOARD_INTER_SPK2_PA "inter Spk2 PA" + +static const char *func_name[BOARD_FUNC_MAX] = { + BOARD_EXT_SPK, + BOARD_EXT_SPK2, + BOARD_EXT_EAR, + BOARD_HEADPHONE_JACK, + BOARD_LINE_JACK, + BOARD_MIC_JACK, + BOARD_AUX_MIC_JACK, + BOARD_HP_MIC_JACK, + BOARD_DMIC_JACK, + BOARD_DMIC1_JACK, + BOARD_DIG_FM_JACK, +}; + +static void board_ext_control(struct snd_soc_dapm_context *dapm, int s, int e) +{ + int i; + BUG_ON(e > BOARD_FUNC_MAX); + for (i = s; i < e; i++) { + if (board.func[i] == SWITCH_FUN_ON) + snd_soc_dapm_enable_pin(dapm, func_name[i]); + else + snd_soc_dapm_disable_pin(dapm, func_name[i]); + } + + /* signal a DAPM event */ + snd_soc_dapm_sync(dapm); +} + +static inline void local_cpu_pa_control(struct snd_soc_card *card, + const char *id, bool enable, bool sync) +{ + if (enable) { + snd_soc_dapm_enable_pin(&card->dapm, id); + } else { + snd_soc_dapm_disable_pin(&card->dapm, id); + } + + if (sync) { + /* signal a DAPM event */ + snd_soc_dapm_sync(&card->dapm); + } +} + +static void board_try_inter_pa_control(struct snd_soc_card *card, int id, + bool sync) +{ + char *pa_name; + + if (id >= BOARD_FUNC_MUTE_MAX) + return; + + switch (id) { + case BOARD_FUNC_SPKL: + pa_name = BOARD_INTER_SPK_PA; + break; + case BOARD_FUNC_SPKR: + pa_name = BOARD_INTER_SPK2_PA; + break; + case BOARD_FUNC_HP: + pa_name = BOARD_INTER_HP_PA; + break; + default: + return; + } + + if (board.pa_type[id] & HOOK_BPY) { + int enable = 1; + if (board.func[id] == SWITCH_FUN_ON) { + if (board.m[id].need_mute) { + enable = 0; + } + } else { + enable = 0; + } + local_cpu_pa_control(card, pa_name, enable, sync); + } +} + +static int audio_speaker_enable_inter(struct snd_soc_card *card, int enable) +{ + int ret; + if (enable && board.m[BOARD_FUNC_SPKL].need_mute) { + enable = 0; + } + ret = sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER, enable); + if (ret < 0) { + pr_err("ERR:Call external speaker control failed %d!\n", ret); + return ret; + } + return ret; +} + +static void audio_speaker_enable(struct snd_soc_card *card, int enable) +{ + board.m[BOARD_FUNC_SPKL].is_on = enable; + board.m[BOARD_FUNC_SPKL].mute_func(card, enable); +} + +static int audio_speaker2_enable_inter(struct snd_soc_card *card, int enable) +{ + int ret; + if (enable && board.m[BOARD_FUNC_SPKR].need_mute) { + enable = 0; + } + ret = sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER2, enable); + if (ret < 0) { + pr_err("ERR:Call external speaker2 control failed %d!\n", ret); + return ret; + } + return ret; +} + +static void audio_speaker2_enable(struct snd_soc_card *card, int enable) +{ + board.m[BOARD_FUNC_SPKR].is_on = enable; + board.m[BOARD_FUNC_SPKR].mute_func(card, enable); +} + +static int audio_headphone_enable_inter(struct snd_soc_card *card, int enable) +{ + int ret; + if (enable && board.m[BOARD_FUNC_HP].need_mute) { + enable = 0; + } + ret = sprd_ext_headphone_ctrl(SPRD_AUDIO_ID_HEADPHONE, enable); + if (ret < 0) { + pr_err("ERR:Call external headphone control failed %d!\n", ret); + return ret; + } + return ret; +} + +static void audio_headphone_enable(struct snd_soc_card *card, int enable) +{ + board.m[BOARD_FUNC_HP].is_on = enable; + board.m[BOARD_FUNC_HP].mute_func(card, enable); +} + +static int audio_earpiece_enable_inter(struct snd_soc_card *card, int enable) +{ + int ret; + if (enable && board.m[BOARD_FUNC_EAR].need_mute) { + enable = 0; + } + ret = sprd_ext_earpiece_ctrl(SPRD_AUDIO_ID_EARPIECE, enable); + if (ret < 0) { + pr_err("ERR:Call external earpiece control failed %d!\n", ret); + } + return ret; +} + +static void audio_earpiece_enable(struct snd_soc_card *card, int enable) +{ + board.m[BOARD_FUNC_EAR].is_on = enable; + board.m[BOARD_FUNC_EAR].mute_func(card, enable); +} + +static int board_headphone_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Headphone Switch %s\n", STR_ON_OFF(on)); + audio_headphone_enable(w->dapm->card, on); + return 0; +} + +static int board_earpiece_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Earpiece Switch %s\n", STR_ON_OFF(on)); + audio_earpiece_enable(w->dapm->card, on); + return 0; +} + +static int board_speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Speaker Switch %s\n", STR_ON_OFF(on)); + audio_speaker_enable(w->dapm->card, on); + return 0; +} + +static int board_speaker2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Speaker2 Switch %s\n", STR_ON_OFF(on)); + audio_speaker2_enable(w->dapm->card, on); + return 0; +} + +static int board_main_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Main MIC Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_MAIN_MIC, on); + return 0; +} + +static int board_sub_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Sub MIC Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_SUB_MIC, on); + return 0; +} + +static int board_head_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Head MIC Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_HEAD_MIC, on); + return 0; +} + +static int board_dig0_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Digtial0 MIC Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_DIG0_MIC, on); + return 0; +} + +static int board_dig1_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Digtial1 MIC Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_DIG1_MIC, on); + return 0; +} + +static int board_line_in_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("LINE IN Switch %s\n", STR_ON_OFF(on)); + sprd_ext_mic_ctrl(SPRD_AUDIO_ID_LINE_IN, on); + return 0; +} + +static int board_dig_fm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int on = ! !SND_SOC_DAPM_EVENT_ON(event); + sp_asoc_pr_dbg("Digtial FM Switch %s\n", STR_ON_OFF(on)); + sprd_ext_fm_ctrl(SPRD_AUDIO_ID_DIG_FM, on); + return 0; +} + +static const struct snd_soc_dapm_widget sprd_codec_dapm_widgets[] = { + SND_SOC_DAPM_MIC(BOARD_MIC_JACK, board_main_mic_event), + SND_SOC_DAPM_MIC(BOARD_AUX_MIC_JACK, board_sub_mic_event), + SND_SOC_DAPM_MIC(BOARD_HP_MIC_JACK, board_head_mic_event), + SND_SOC_DAPM_MIC(BOARD_DMIC_JACK, board_dig0_mic_event), + SND_SOC_DAPM_MIC(BOARD_DMIC1_JACK, board_dig1_mic_event), + /*digital fm input */ + SND_SOC_DAPM_LINE(BOARD_DIG_FM_JACK, board_dig_fm_event), + + SND_SOC_DAPM_SPK(BOARD_EXT_SPK, board_speaker_event), + SND_SOC_DAPM_SPK(BOARD_EXT_SPK2, board_speaker2_event), + SND_SOC_DAPM_SPK(BOARD_EXT_EAR, board_earpiece_event), + SND_SOC_DAPM_LINE(BOARD_LINE_JACK, board_line_in_event), + SND_SOC_DAPM_HP(BOARD_HEADPHONE_JACK, board_headphone_event), + SND_SOC_DAPM_HP(BOARD_INTER_HP_PA, NULL), + SND_SOC_DAPM_SPK(BOARD_INTER_SPK_PA, NULL), + SND_SOC_DAPM_SPK(BOARD_INTER_SPK2_PA, NULL), +}; + +static int board_func_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + ucontrol->value.integer.value[0] = board.func[id]; + return 0; +} + +static int board_func_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + int id = FUN_REG(mc->reg); + + sp_asoc_pr_info("%s Switch %s\n", func_name[id], + STR_ON_OFF(ucontrol->value.integer.value[0])); + + if (board.func[id] == ucontrol->value.integer.value[0]) + return 0; + + board.func[id] = ucontrol->value.integer.value[0]; + board_try_inter_pa_control(card, id, 1); + board_ext_control(&card->dapm, id, id + 1); + return 1; +} + +static int board_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = FUN_REG(mc->reg); + ucontrol->value.integer.value[0] = board.m[id].need_mute; + return 0; +} + +static int board_mute_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + int id = FUN_REG(mc->reg); + + sp_asoc_pr_info("%s Switch %s\n", func_name[id], + ucontrol->value.integer.value[0] ? "Mute" : "Unmute"); + + if (board.m[id].need_mute == ucontrol->value.integer.value[0]) + return 0; + + board.m[id].need_mute = ucontrol->value.integer.value[0]; + board.m[id].mute_func(card, board.m[id].is_on); + board_try_inter_pa_control(card, id, 1); + return 1; +} + +static void board_inter_pa_check(int func_id) +{ + switch (func_id) { + case BOARD_FUNC_SPKL: + board.pa_type[func_id] = + sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER, 0); + break; + case BOARD_FUNC_SPKR: + board.pa_type[func_id] = + sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER2, 0); + break; + case BOARD_FUNC_EAR: + board.pa_type[func_id] = + sprd_ext_earpiece_ctrl(SPRD_AUDIO_ID_EARPIECE, 0); + break; + case BOARD_FUNC_HP: + board.pa_type[func_id] = + sprd_ext_headphone_ctrl(SPRD_AUDIO_ID_HEADPHONE, 0); + break; + default: + return; + } +} + +static void board_inter_pa_init(void) +{ + int id; + for (id = BOARD_FUNC_SPKL; id < BOARD_FUNC_MUTE_MAX; id++) + board_inter_pa_check(id); +} + +static void board_inter_pa_control(struct snd_soc_card *card) +{ + int id; + for (id = BOARD_FUNC_SPKL; id < BOARD_FUNC_MUTE_MAX; id++) + board_try_inter_pa_control(card, id, 0); +} + +static int board_late_probe(struct snd_soc_card *card) +{ + int i; + sprd_audio_debug_init(card->snd_card); + + board_inter_pa_control(card); + board_ext_control(&card->dapm, 0, BOARD_FUNC_MAX); + + for (i = 0; i < BOARD_FUNC_MAX; i++) { + snd_soc_dapm_ignore_suspend(&card->dapm, func_name[i]); + } + + snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_HP_PA); + snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_SPK_PA); + snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_SPK2_PA); + snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, "Playback-Vaudio"); + snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, + "Main-Capture-Vaudio"); + snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, + "Ext-Capture-Vaudio"); + snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, "DFM-Output"); + return 0; +} + +static void board_mute_init(void) +{ + board.m[BOARD_FUNC_SPKL].mute_func = audio_speaker_enable_inter; + board.m[BOARD_FUNC_SPKR].mute_func = audio_speaker2_enable_inter; + board.m[BOARD_FUNC_HP].mute_func = audio_headphone_enable_inter; + board.m[BOARD_FUNC_EAR].mute_func = audio_earpiece_enable_inter; +} + +static int sprd_asoc_probe(struct platform_device *pdev, + struct snd_soc_card *card) +{ + struct device_node *node = pdev->dev.of_node; + card->dev = &pdev->dev; + if (node) { + int i; + struct device_node *pcm_node; + struct device_node *codec_node; + + if (snd_soc_of_parse_card_name(card, "sprd,model")) { + pr_err("ERR:Card name is not provided\n"); + return -ENODEV; + } +#if 0 + ret = snd_soc_of_parse_audio_routing(card, + "sprd,audio-routing"); + if (ret) { + pr_err("ERR:Error while parsing DAPM routing\n"); + return ret; + } +#endif + + pcm_node = of_parse_phandle(node, "sprd,pcm", 0); + if (!pcm_node) { + pr_err("ERR:PCM node is not provided\n"); + return -EINVAL; + } + + codec_node = of_parse_phandle(node, "sprd,codec", 0); + if (!codec_node) { + pr_err("ERR:CODEC node is not provided\n"); + of_node_put(pcm_node); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + card->dai_link[i].platform_name = NULL; + card->dai_link[i].platform_of_node = pcm_node; + card->dai_link[i].codec_name = NULL; + card->dai_link[i].codec_of_node = codec_node; + } + of_node_put(pcm_node); + of_node_put(codec_node); + } + + board_mute_init(); + board_inter_pa_init(); + return snd_soc_register_card(card); +} + +static int sprd_asoc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + snd_soc_unregister_card(card); + return 0; +} + +static void sprd_asoc_shutdown(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + memset(&board.func, 0, sizeof(board.func)); + board_inter_pa_control(card); + board_ext_control(&card->dapm, 0, BOARD_FUNC_MAX); +} + +#define BOARD_CODEC_FUNC(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, board_func_get, board_func_set) + +#define BOARD_CODEC_MUTE(xname, xreg) \ + SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, board_mute_get, board_mute_set) + +#if defined(CONFIG_SND_SOC_SPRD_VBC_R2P0_SPRD_CODEC_V4) +#include "vbc-r2p0-sprd-codec-v4.h" +#else +#include "vbc-r1p0-sprd-codec-v1.h" +#include "vbc-r2p0-sprd-codec-v3.h" +#endif + +MODULE_DESCRIPTION("ALSA ASoC SpreadTrum VBC SPRD-CODEC"); +MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>"); +MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("machine:vbc-sprd-codec"); diff --git a/sound/soc/sprd/sprd-asoc-common.c b/sound/soc/sprd/sprd-asoc-common.c new file mode 100755 index 00000000..c6c0af1f --- /dev/null +++ b/sound/soc/sprd/sprd-asoc-common.c @@ -0,0 +1,167 @@ +/* + * sound/soc/sprd/sprd-asoc-common.c + * + * SPRD ASoC Common implement -- SpreadTrum ASOC Common. + * + * 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(" COM ") fmt +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/stat.h> +#include <linux/atomic.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/io.h> + +#include <sound/soc.h> +#include <sound/info.h> +#include <sound/sprd-audio-hook.h> + +#include "sprd-asoc-common.h" + +static struct sprd_audio_ext_hook *sprd_audio_hook = 0; + +int sprd_ext_hook_register(struct sprd_audio_ext_hook *hook) +{ + if (sprd_audio_hook) { + pr_err("ERR:Already Registed Hook\n"); + return -1; + } + sprd_audio_hook = hook; + return 0; +} + +EXPORT_SYMBOL(sprd_ext_hook_register); + +int sprd_ext_hook_unregister(struct sprd_audio_ext_hook *hook) +{ + if (sprd_audio_hook != hook) { + pr_err("ERR:Maybe Unregister other's Hook?\n"); + return -1; + } + sprd_audio_hook = 0; + return 0; +} + +EXPORT_SYMBOL(sprd_ext_hook_unregister); + +#define SAFE_CALL(func) do { \ + if (func) { \ + ret = func(id, on); \ + } \ +} while(0) + +int sprd_ext_speaker_ctrl(int id, int on) +{ + int ret = NO_HOOK; + sp_asoc_pr_dbg("external Speaker(%d) Hook; on=%d\n", id, on); + if (sprd_audio_hook) + SAFE_CALL(sprd_audio_hook->ext_speaker_ctrl); + return ret; +} + +EXPORT_SYMBOL(sprd_ext_speaker_ctrl); + +int sprd_ext_headphone_ctrl(int id, int on) +{ + int ret = NO_HOOK; + sp_asoc_pr_dbg("external Headphone(%d) Hook; on=%d\n", id, on); + if (sprd_audio_hook) + SAFE_CALL(sprd_audio_hook->ext_headphone_ctrl); + return ret; +} + +EXPORT_SYMBOL(sprd_ext_headphone_ctrl); + +int sprd_ext_earpiece_ctrl(int id, int on) +{ + int ret = NO_HOOK; + sp_asoc_pr_dbg("external Earpiece(%d) Hook; on=%d\n", id, on); + if (sprd_audio_hook) + SAFE_CALL(sprd_audio_hook->ext_earpiece_ctrl); + return ret; +} + +EXPORT_SYMBOL(sprd_ext_earpiece_ctrl); + +int sprd_ext_mic_ctrl(int id, int on) +{ + int ret = NO_HOOK; + sp_asoc_pr_dbg("external MIC(%d) Hook; on=%d\n", id, on); + if (sprd_audio_hook) + SAFE_CALL(sprd_audio_hook->ext_mic_ctrl); + return ret; +} + +EXPORT_SYMBOL(sprd_ext_mic_ctrl); + +int sprd_ext_fm_ctrl(int id, int on) +{ + int ret = NO_HOOK; + sp_asoc_pr_dbg("external FM(%d) Hook; on=%d\n", id, on); + if (sprd_audio_hook) + SAFE_CALL(sprd_audio_hook->ext_fm_ctrl); + return ret; +} + +EXPORT_SYMBOL(sprd_ext_fm_ctrl); + +/* spreadtrum audio debug */ + +static int sp_audio_debug_flag = SP_AUDIO_DEBUG_DEFAULT; + +inline int get_sp_audio_debug_flag(void) +{ + return sp_audio_debug_flag; +} + +EXPORT_SYMBOL(get_sp_audio_debug_flag); + +static void snd_pcm_sprd_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int *p_sp_audio_debug_flag = entry->private_data; + snd_iprintf(buffer, "0x%08x\n", *p_sp_audio_debug_flag); +} + +static void snd_pcm_sprd_debug_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int *p_sp_audio_debug_flag = entry->private_data; + char line[64]; + if (!snd_info_get_line(buffer, line, sizeof(line))) + *p_sp_audio_debug_flag = simple_strtoul(line, NULL, 16); +} + +int sprd_audio_debug_init(struct snd_card *card) +{ + struct snd_info_entry *entry; + if ((entry = snd_info_create_card_entry(card, "asoc-sprd-debug", + card->proc_root)) != NULL) { + entry->c.text.read = snd_pcm_sprd_debug_read; + entry->c.text.write = snd_pcm_sprd_debug_write; + entry->mode |= S_IWUSR; + entry->private_data = &sp_audio_debug_flag; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + return 0; +} + +EXPORT_SYMBOL(sprd_audio_debug_init); diff --git a/sound/soc/sprd/sprd-asoc-common.h b/sound/soc/sprd/sprd-asoc-common.h new file mode 100644 index 00000000..b2ae0c20 --- /dev/null +++ b/sound/soc/sprd/sprd-asoc-common.h @@ -0,0 +1,84 @@ +/* + * sound/soc/sprd/sprd-asoc-common.h + * + * SPRD ASoC Common include -- SpreadTrum ASOC Common. + * + * 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. + */ +#ifndef __SPRD_ASOC_COMMON_H +#define __SPRD_ASOC_COMMON_H + +#define STR_ON_OFF(on) (on ? "On" : "Off") + +#ifndef pr_fmt +#define pr_fmt(fmt) fmt +#endif + +#define SP_AUDIO_DEBUG_BASIC (1<<0) +#define SP_AUDIO_DEBUG_MORE (1<<1) +#define SP_AUDIO_DEBUG_REG (1<<2) +#define SP_AUDIO_DEBUG_DEFAULT (SP_AUDIO_DEBUG_BASIC) + +#define SP_AUDIO_CODEC_NUM (4) +#define AUDIO_CODEC_2713 (0) +#define AUDIO_CODEC_2723 (1) +#define AUDIO_CODEC_2723E (2) +#define AUDIO_CODEC_2723T (2) + +#define AUDIO_2723_VER_AA (0) +#define AUDIO_2723_VER_S (0x000) +#define AUDIO_2723_VER_E (0x090) +#define AUDIO_2723_VER_T (0x0C0) + + +static const char *codec_hw_info[] = { + "2713", "2723", "2723E", "2723T" +}; + +struct snd_card; +int sprd_audio_debug_init(struct snd_card *card); +int get_sp_audio_debug_flag(void); +#define sp_audio_debug(mask) (get_sp_audio_debug_flag() & (mask)) + +#define sp_asoc_pr_info(fmt, ...) do { \ + if (sp_audio_debug(SP_AUDIO_DEBUG_BASIC)) { \ + pr_info(fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#define sp_asoc_pr_reg(fmt, ...) do { \ + if (sp_audio_debug(SP_AUDIO_DEBUG_REG)) { \ + pr_info(fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#ifdef CONFIG_SND_SOC_SPRD_AUDIO_DEBUG +#define sp_asoc_dev_dbg(dev, format, ...) do { \ + if (sp_audio_debug(SP_AUDIO_DEBUG_MORE)) { \ + dev_dbg(dev, pr_fmt(format), ##__VA_ARGS__); \ + } \ +} while(0) + +#define sp_asoc_pr_dbg(fmt, ...) do { \ + if (sp_audio_debug(SP_AUDIO_DEBUG_MORE)) { \ + pr_debug(fmt, ##__VA_ARGS__); \ + } \ +} while(0) +#if !defined(CONFIG_DYNAMIC_DEBUG) +#define DEBUG +#endif +#else /* !CONFIG_SND_SOC_SPRD_AUDIO_DEBUG */ +#define sp_asoc_dev_dbg(...) +#define sp_asoc_pr_dbg(...) +#endif + +#endif /* __SPRD_ASOC_COMMON_H */ diff --git a/sound/soc/sprd/sprd-asoc-debug.h b/sound/soc/sprd/sprd-asoc-debug.h new file mode 100644 index 00000000..727cfc3f --- /dev/null +++ b/sound/soc/sprd/sprd-asoc-debug.h @@ -0,0 +1,35 @@ +/* + * sound/soc/sprd/sprd-asoc-debug.h + * + * SPRD ASoC Debug include -- SpreadTrum ASOC Debug. + * + * 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. + */ +#ifndef __SPRD_ASOC_DEBUG_H +#define __SPRD_ASOC_DEBUG_H + +#define pr_color_terminal 0 +#if pr_color_terminal +#define pr_col_s "\e[35m" +#define pr_col_e "\e[0m" +#else +#define pr_col_s +#define pr_col_e +#endif + +#define pr_id "ASoC:" +#define pr_s pr_col_s "[" pr_id +#define pr_e "] " pr_col_e + +#define pr_sprd_fmt(id) pr_s id pr_e + +#endif /* __SPRD_ASOC_DEBUG_H */ diff --git a/sound/soc/sprd/vbc-r1p0-sprd-codec-v1.h b/sound/soc/sprd/vbc-r1p0-sprd-codec-v1.h new file mode 100644 index 00000000..2b47e7d0 --- /dev/null +++ b/sound/soc/sprd/vbc-r1p0-sprd-codec-v1.h @@ -0,0 +1,129 @@ +/* + * sound/soc/sprd/vbc-r1p0-sprd-codec-v1.h + * + * 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. + */ + +static const struct snd_kcontrol_new vbc_r1p0_codec_v1_controls[] = { + BOARD_CODEC_FUNC("Speaker Function", BOARD_FUNC_SPKL), + BOARD_CODEC_FUNC("Speaker2 Function", BOARD_FUNC_SPKR), + BOARD_CODEC_FUNC("Earpiece Function", BOARD_FUNC_EAR), + BOARD_CODEC_FUNC("HeadPhone Function", BOARD_FUNC_HP), + BOARD_CODEC_FUNC("Line Function", BOARD_FUNC_LINE), + BOARD_CODEC_FUNC("Mic Function", BOARD_FUNC_MIC), + BOARD_CODEC_FUNC("Aux Mic Function", BOARD_FUNC_AUXMIC), + BOARD_CODEC_FUNC("HP Mic Function", BOARD_FUNC_HP_MIC), + BOARD_CODEC_FUNC("Digital FM Function", BOARD_FUNC_DFM), + + BOARD_CODEC_MUTE("Speaker Mute", BOARD_FUNC_SPKL), + BOARD_CODEC_MUTE("Speaker2 Mute", BOARD_FUNC_SPKR), + BOARD_CODEC_MUTE("Earpiece Mute", BOARD_FUNC_EAR), + BOARD_CODEC_MUTE("HeadPhone Mute", BOARD_FUNC_HP), +}; + +/* board supported audio map */ +static const struct snd_soc_dapm_route vbc_r1p0_codec_v1_map[] = { + {"HeadPhone Jack", NULL, "HEAD_P_L"}, + {"HeadPhone Jack", NULL, "HEAD_P_R"}, + {"Ext Spk", NULL, "AOL"}, + {"Ext Spk2", NULL, "AOR"}, + {"Ext Ear", NULL, "EAR"}, + {"MIC", NULL, "Mic Jack"}, + {"AUXMIC", NULL, "Aux Mic Jack"}, + {"HPMIC", NULL, "HP Mic Jack"}, + {"AIL", NULL, "Line Jack"}, + {"AIR", NULL, "Line Jack"}, + + {"inter Spk PA", NULL, "Spk PA"}, + + /* VBC -- SPRD-CODEC */ + {"Aud input", NULL, "AD Clk"}, + + {"Aud input", NULL, "Digital ADCL Switch"}, + {"Aud input", NULL, "Digital ADCR Switch"}, + +}; + +static struct snd_soc_dai_link vbc_r1p0_codec_v1_dai[] = { + { + .name = "vbc(r1)-codec(v1)-ap", + .stream_name = "HiFi", + + .codec_name = "sprd-codec-v1", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-r1p0", + .codec_dai_name = "sprd-codec-v1-i2s", + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "vbc(r1)-codec(v1)-dsp", + .stream_name = "Voice", + + .codec_name = "sprd-codec-v1", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vaudio", + .codec_dai_name = "sprd-codec-v1-vaudio", + }, +#endif + { + .name = "vbc(r1)-dfm", + .stream_name = "Dfm", + + .codec_name = "sprd-codec-v1", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-dfm", + .codec_dai_name = "sprd-codec-v1-fm", + }, +}; + +static struct snd_soc_card vbc_r1p0_codec_v1_card = { + .name = "sprdphone", + .dai_link = vbc_r1p0_codec_v1_dai, + .num_links = ARRAY_SIZE(vbc_r1p0_codec_v1_dai), + .owner = THIS_MODULE, + + .controls = vbc_r1p0_codec_v1_controls, + .num_controls = ARRAY_SIZE(vbc_r1p0_codec_v1_controls), + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = vbc_r1p0_codec_v1_map, + .num_dapm_routes = ARRAY_SIZE(vbc_r1p0_codec_v1_map), + .late_probe = board_late_probe, +}; + +static int vbc_r1p0_codec_v1_probe(struct platform_device *pdev) +{ + return sprd_asoc_probe(pdev, &vbc_r1p0_codec_v1_card); +} + +#ifdef CONFIG_OF +static const struct of_device_id vbc_r1p0_codec_v1_of_match[] = { + {.compatible = "sprd,vbc-r1p0-sprd-codec-v1",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vbc_r1p0_codec_v1_of_match); +#endif + +static struct platform_driver vbc_r1p0_codec_v1_driver = { + .driver = { + .name = "vbc-r1p0-sprd-codec-v1", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(vbc_r1p0_codec_v1_of_match), + }, + .probe = vbc_r1p0_codec_v1_probe, + .remove = sprd_asoc_remove, + .shutdown = sprd_asoc_shutdown, +}; + +module_platform_driver(vbc_r1p0_codec_v1_driver); diff --git a/sound/soc/sprd/vbc-r2p0-sprd-codec-v3.h b/sound/soc/sprd/vbc-r2p0-sprd-codec-v3.h new file mode 100755 index 00000000..427194c0 --- /dev/null +++ b/sound/soc/sprd/vbc-r2p0-sprd-codec-v3.h @@ -0,0 +1,207 @@ +/* + * sound/soc/sprd/vbc-r2p0-sprd-codec-v3.h + * + * 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. + */ + +static const struct snd_kcontrol_new vbc_r2p0_codec_v3_controls[] = { + BOARD_CODEC_FUNC("Speaker Function", BOARD_FUNC_SPKL), + BOARD_CODEC_FUNC("Speaker2 Function", BOARD_FUNC_SPKR), + BOARD_CODEC_FUNC("Earpiece Function", BOARD_FUNC_EAR), + BOARD_CODEC_FUNC("HeadPhone Function", BOARD_FUNC_HP), + BOARD_CODEC_FUNC("Line Function", BOARD_FUNC_LINE), + BOARD_CODEC_FUNC("Mic Function", BOARD_FUNC_MIC), + BOARD_CODEC_FUNC("Aux Mic Function", BOARD_FUNC_AUXMIC), + BOARD_CODEC_FUNC("HP Mic Function", BOARD_FUNC_HP_MIC), + BOARD_CODEC_FUNC("DMic Function", BOARD_FUNC_DMIC), + BOARD_CODEC_FUNC("DMic1 Function", BOARD_FUNC_DMIC1), + BOARD_CODEC_FUNC("Digital FM Function", BOARD_FUNC_DFM), + + BOARD_CODEC_MUTE("Speaker Mute", BOARD_FUNC_SPKL), + BOARD_CODEC_MUTE("Speaker2 Mute", BOARD_FUNC_SPKR), + BOARD_CODEC_MUTE("Earpiece Mute", BOARD_FUNC_EAR), + BOARD_CODEC_MUTE("HeadPhone Mute", BOARD_FUNC_HP), +}; + +/* board supported audio map */ +static const struct snd_soc_dapm_route vbc_r2p0_codec_v3_map[] = { + {"HeadPhone Jack", NULL, "HEAD_P_L"}, + {"HeadPhone Jack", NULL, "HEAD_P_R"}, + {"Ext Spk", NULL, "AOL"}, + {"Ext Spk2", NULL, "AOR"}, + {"Ext Ear", NULL, "EAR"}, + {"MIC", NULL, "Mic Jack"}, + {"AUXMIC", NULL, "Aux Mic Jack"}, + {"HPMIC", NULL, "HP Mic Jack"}, + {"AIL", NULL, "Line Jack"}, + {"AIR", NULL, "Line Jack"}, + {"DMIC", NULL, "DMic Jack"}, + {"DMIC1", NULL, "DMic1 Jack"}, + + {"inter HP PA", NULL, "HP PA"}, + {"inter Spk PA", NULL, "Spk PA"}, + + /* VBC -- SPRD-CODEC */ + {"Aud input", NULL, "AD Clk"}, + {"Aud1 input", NULL, "AD Clk"}, + + {"Aud input", NULL, "Digital ADCL Switch"}, + {"Aud input", NULL, "Digital ADCR Switch"}, + + {"Aud1 input", NULL, "Digital ADC1L Switch"}, + {"Aud1 input", NULL, "Digital ADC1R Switch"}, + +}; + +extern struct sprd_dfm_priv dfm; +static int dfm_rate(struct snd_pcm_hw_params *params) +{ +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE + dfm.hw_rate = CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE; +#else + dfm.hw_rate = params_rate(params); +#endif + + if (dfm.hw_rate == 8000) + dfm.sample_rate = 8000; + else { +#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN + dfm.sample_rate = 44100; +#else + dfm.sample_rate = params_rate(params); +#endif + } + sp_asoc_pr_dbg("%s: hw_rate:%d, sample_rate:%d \n", + __func__, dfm.hw_rate, dfm.sample_rate); + + if (dfm.hw_rate != dfm.sample_rate) { + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval dfm_rates; + dfm_rates.min = dfm.sample_rate; + dfm_rates.max = dfm.sample_rate; + dfm_rates.openmin = dfm_rates.openmax = 0; + dfm_rates.integer = 0; + + return snd_interval_refine(rate, &dfm_rates); + } + return 0; +} + +static int dfm_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + sp_asoc_pr_dbg("%s \n", __func__); + dfm_rate(params); + return 0; +} + +static const struct snd_soc_ops dfm_ops = { + .hw_params = dfm_params, +}; + +static struct snd_soc_dai_link vbc_r2p0_codec_v3_dai[] = { + { + .name = "vbc(r2)-codec(v3)-ap", + .stream_name = "HiFi", + + .codec_name = "sprd-codec-v3", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-r2p0", + .codec_dai_name = "sprd-codec-v3-i2s", + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "vbc(r2)-codec(v3)-dsp", + .stream_name = "Voice", + + .codec_name = "sprd-codec-v3", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vaudio", + .codec_dai_name = "sprd-codec-v3-vaudio", + }, +#endif + { + .name = "aux-captrue", + .stream_name = "AuxRecord", + + .codec_name = "sprd-codec-v3", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-r2p0-ad23", + .codec_dai_name = "codec-i2s-ext", + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "aux-dsp-captrue", + .stream_name = "AuxDSPCaptrue", + + .codec_name = "sprd-codec-v3", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vaudio-ad23", + .codec_dai_name = "codec-vaudio-ext", + }, +#endif + { + .name = "vbc(r2)-dfm", + .stream_name = "Dfm", + + .codec_name = "sprd-codec-v3", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-dfm", + .codec_dai_name = "sprd-codec-v3-fm", + .ops = &dfm_ops, + }, +}; + +static struct snd_soc_card vbc_r2p0_codec_v3_card = { + .name = "sprdphone", + .dai_link = vbc_r2p0_codec_v3_dai, + .num_links = ARRAY_SIZE(vbc_r2p0_codec_v3_dai), + .owner = THIS_MODULE, + + .controls = vbc_r2p0_codec_v3_controls, + .num_controls = ARRAY_SIZE(vbc_r2p0_codec_v3_controls), + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = vbc_r2p0_codec_v3_map, + .num_dapm_routes = ARRAY_SIZE(vbc_r2p0_codec_v3_map), + .late_probe = board_late_probe, +}; + +static int vbc_r2p0_codec_v3_probe(struct platform_device *pdev) +{ + return sprd_asoc_probe(pdev, &vbc_r2p0_codec_v3_card); +} + +#ifdef CONFIG_OF +static const struct of_device_id vbc_r2p0_codec_v3_of_match[] = { + {.compatible = "sprd,vbc-r2p0-sprd-codec-v3",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vbc_r2p0_codec_v3_of_match); +#endif + +static struct platform_driver vbc_r2p0_codec_v3_driver = { + .driver = { + .name = "vbc-r2p0-sprd-codec-v3", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(vbc_r2p0_codec_v3_of_match), + }, + .probe = vbc_r2p0_codec_v3_probe, + .remove = sprd_asoc_remove, + .shutdown = sprd_asoc_shutdown, +}; + +module_platform_driver(vbc_r2p0_codec_v3_driver); diff --git a/sound/soc/sprd/vbc-r2p0-sprd-codec-v4.h b/sound/soc/sprd/vbc-r2p0-sprd-codec-v4.h new file mode 100755 index 00000000..a2f26a0a --- /dev/null +++ b/sound/soc/sprd/vbc-r2p0-sprd-codec-v4.h @@ -0,0 +1,218 @@ +/* + * sound/soc/sprd/vbc-r2p0-sprd-codec-v4.h + * + * 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. + */ + +static const struct snd_kcontrol_new vbc_r2p0_codec_controls[] = { + BOARD_CODEC_FUNC("Speaker Function", BOARD_FUNC_SPKL), + BOARD_CODEC_FUNC("Speaker2 Function", BOARD_FUNC_SPKR), + BOARD_CODEC_FUNC("Earpiece Function", BOARD_FUNC_EAR), + BOARD_CODEC_FUNC("HeadPhone Function", BOARD_FUNC_HP), + BOARD_CODEC_FUNC("Line Function", BOARD_FUNC_LINE), + BOARD_CODEC_FUNC("Mic Function", BOARD_FUNC_MIC), + BOARD_CODEC_FUNC("Aux Mic Function", BOARD_FUNC_AUXMIC), + BOARD_CODEC_FUNC("HP Mic Function", BOARD_FUNC_HP_MIC), + BOARD_CODEC_FUNC("DMic Function", BOARD_FUNC_DMIC), + BOARD_CODEC_FUNC("DMic1 Function", BOARD_FUNC_DMIC1), + BOARD_CODEC_FUNC("Digital FM Function", BOARD_FUNC_DFM), + + BOARD_CODEC_MUTE("Speaker Mute", BOARD_FUNC_SPKL), + BOARD_CODEC_MUTE("Speaker2 Mute", BOARD_FUNC_SPKR), + BOARD_CODEC_MUTE("Earpiece Mute", BOARD_FUNC_EAR), + BOARD_CODEC_MUTE("HeadPhone Mute", BOARD_FUNC_HP), +}; + +/* board supported audio map */ +static const struct snd_soc_dapm_route vbc_r2p0_codec_map[] = { + {"HeadPhone Jack", NULL, "HEAD_P_L"}, + {"HeadPhone Jack", NULL, "HEAD_P_R"}, + {"Ext Spk", NULL, "AOL"}, + {"Ext Spk2", NULL, "AOR"}, + {"Ext Ear", NULL, "EAR"}, + {"MIC", NULL, "Mic Jack"}, + {"AUXMIC", NULL, "Aux Mic Jack"}, + {"HPMIC", NULL, "HP Mic Jack"}, + {"AIL", NULL, "Line Jack"}, + {"AIR", NULL, "Line Jack"}, + {"DMIC", NULL, "DMic Jack"}, + {"DMIC1", NULL, "DMic1 Jack"}, + + {"inter HP PA", NULL, "HP PA"}, + {"inter Spk PA", NULL, "Spk PA"}, + + /* VBC -- SPRD-CODEC */ + {"Aud input", NULL, "AD Clk"}, + {"Aud1 input", NULL, "AD Clk"}, + + {"Aud input", NULL, "Digital ADCL Switch"}, + {"Aud input", NULL, "Digital ADCR Switch"}, + + {"Aud1 input", NULL, "Digital ADC1L Switch"}, + {"Aud1 input", NULL, "Digital ADC1R Switch"}, + +}; + +extern struct sprd_dfm_priv dfm; +static int dfm_rate(struct snd_pcm_hw_params *params) +{ + +#ifdef CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE + dfm.hw_rate = CONFIG_SND_SOC_VBC_SRC_SAMPLE_RATE; +#else + dfm.hw_rate = params_rate(params); +#endif + + if (dfm.hw_rate == 8000) + dfm.sample_rate = 8000; + else { +#ifdef CONFIG_SND_SOC_SPRD_VBC_SRC_OPEN + dfm.sample_rate = 44100; +#else + dfm.sample_rate = params_rate(params); +#endif + } + sp_asoc_pr_dbg("%s: hw_rate:%d, sample_rate:%d \n", + __func__, dfm.hw_rate, dfm.sample_rate); + + if (dfm.hw_rate != dfm.sample_rate) { + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval dfm_rates; + dfm_rates.min = dfm.sample_rate; + dfm_rates.max = dfm.sample_rate; + dfm_rates.openmin = dfm_rates.openmax = 0; + dfm_rates.integer = 0; + + return snd_interval_refine(rate, &dfm_rates); + } + return 0; +} + +static int dfm_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + sp_asoc_pr_dbg("%s \n", __func__); + dfm_rate(params); + return 0; +} + +static const struct snd_soc_ops dfm_ops = { + .hw_params = dfm_params, +}; + +static struct snd_soc_dai_link vbc_r2p0_codec_dai[] = { + { + .name = "vbc(r2)-codec(v4)-ap", + .stream_name = "HiFi", + + .codec_name = "sprd-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-r2p0", + .codec_dai_name = "sprd-codec-i2s", + .ignore_suspend = 1, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "vbc(r2)-codec(v4)-dsp", + .stream_name = "Voice", + + .codec_name = "sprd-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vaudio", + .codec_dai_name = "sprd-codec-vaudio", + .ignore_suspend = 1, + }, +#endif + { + .name = "aux-captrue", + .stream_name = "AuxRecord", + + .codec_name = "sprd-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-r2p0-ad23", + .codec_dai_name = "codec-i2s-ext", + .ignore_suspend = 1, + }, +#ifdef CONFIG_SND_SOC_SPRD_VAUDIO + { + .name = "aux-dsp-captrue", + .stream_name = "AuxDSPCaptrue", + + .codec_name = "sprd-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vaudio-ad23", + .codec_dai_name = "codec-vaudio-ext", + .ignore_suspend = 1, + }, +#endif + { + .name = "vbc(r2)-dfm", + .stream_name = "Dfm", + + .codec_name = "sprd-codec", + .platform_name = "sprd-pcm-audio", + .cpu_dai_name = "vbc-dfm", + .codec_dai_name = "sprd-codec-fm", + .ops = &dfm_ops, + .ignore_suspend = 1, + }, +}; + +static struct snd_soc_card vbc_r2p0_codec_card = { + .name = "sprdphone", + .dai_link = vbc_r2p0_codec_dai, + .num_links = ARRAY_SIZE(vbc_r2p0_codec_dai), + .owner = THIS_MODULE, + + .controls = vbc_r2p0_codec_controls, + .num_controls = ARRAY_SIZE(vbc_r2p0_codec_controls), + .dapm_widgets = sprd_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sprd_codec_dapm_widgets), + .dapm_routes = vbc_r2p0_codec_map, + .num_dapm_routes = ARRAY_SIZE(vbc_r2p0_codec_map), + .late_probe = board_late_probe, +}; + +static int vbc_r2p0_codec_probe(struct platform_device *pdev) +{ + return sprd_asoc_probe(pdev, &vbc_r2p0_codec_card); +} + +#ifdef CONFIG_OF +static const struct of_device_id vbc_r2p0_codec_of_match[] = { + {.compatible = "sprd,vbc-r2p0-sprd-codec",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, vbc_r2p0_codec_of_match); +#endif + +static struct platform_driver vbc_r2p0_codec_driver = { + .driver = { + .name = "vbc-r2p0-sprd-codec", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(vbc_r2p0_codec_of_match), + }, + .probe = vbc_r2p0_codec_probe, + .remove = sprd_asoc_remove, + .shutdown = sprd_asoc_shutdown, +}; + +static int __init vbc_r2p0_codec_init(void) +{ + return platform_driver_register(&vbc_r2p0_codec_driver); +} + +late_initcall_sync(vbc_r2p0_codec_init); |
