summaryrefslogtreecommitdiff
path: root/sound/soc/sprd
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /sound/soc/sprd
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'sound/soc/sprd')
-rw-r--r--sound/soc/sprd/Kconfig59
-rw-r--r--sound/soc/sprd/Makefile14
-rw-r--r--sound/soc/sprd/codec/null-codec/Kconfig2
-rw-r--r--sound/soc/sprd/codec/null-codec/Makefile6
-rw-r--r--sound/soc/sprd/codec/null-codec/null-codec.c110
-rw-r--r--sound/soc/sprd/codec/null-codec/null-codec.h20
-rw-r--r--sound/soc/sprd/codec/sprd/Kconfig30
-rw-r--r--sound/soc/sprd/codec/sprd/v1/Kconfig2
-rw-r--r--sound/soc/sprd/codec/sprd/v1/Makefile7
-rwxr-xr-xsound/soc/sprd/codec/sprd/v1/sprd-audio-power.c614
-rw-r--r--sound/soc/sprd/codec/sprd/v1/sprd-audio-power.h33
-rw-r--r--sound/soc/sprd/codec/sprd/v1/sprd-codec.c3006
-rw-r--r--sound/soc/sprd/codec/sprd/v1/sprd-codec.h375
-rwxr-xr-xsound/soc/sprd/codec/sprd/v3/Kconfig15
-rw-r--r--sound/soc/sprd/codec/sprd/v3/Makefile7
-rw-r--r--sound/soc/sprd/codec/sprd/v3/sprd-audio-power.c654
-rwxr-xr-xsound/soc/sprd/codec/sprd/v3/sprd-audio-power.h35
-rwxr-xr-xsound/soc/sprd/codec/sprd/v3/sprd-codec.c3858
-rwxr-xr-xsound/soc/sprd/codec/sprd/v3/sprd-codec.h404
-rw-r--r--sound/soc/sprd/codec/sprd/v4/Kconfig15
-rw-r--r--sound/soc/sprd/codec/sprd/v4/Makefile7
-rw-r--r--sound/soc/sprd/codec/sprd/v4/sprd-audio-power.c672
-rwxr-xr-xsound/soc/sprd/codec/sprd/v4/sprd-audio-power.h36
-rw-r--r--sound/soc/sprd/codec/sprd/v4/sprd-codec.c4943
-rw-r--r--sound/soc/sprd/codec/sprd/v4/sprd-codec.h593
-rw-r--r--sound/soc/sprd/dai/Kconfig35
-rw-r--r--sound/soc/sprd/dai/Makefile11
-rw-r--r--sound/soc/sprd/dai/i2s/Kconfig2
-rw-r--r--sound/soc/sprd/dai/i2s/Makefile6
-rw-r--r--sound/soc/sprd/dai/i2s/i2s.c1441
-rw-r--r--sound/soc/sprd/dai/sprd-dmaengine-pcm.c1069
-rw-r--r--sound/soc/sprd/dai/sprd-dmaengine-pcm.h56
-rw-r--r--sound/soc/sprd/dai/sprd-pcm.c1025
-rw-r--r--sound/soc/sprd/dai/sprd-pcm.h49
-rw-r--r--sound/soc/sprd/dai/vaudio/Kconfig2
-rw-r--r--sound/soc/sprd/dai/vaudio/Makefile6
-rw-r--r--sound/soc/sprd/dai/vaudio/vaudio.c157
-rw-r--r--sound/soc/sprd/dai/vaudio/vaudio.h22
-rw-r--r--sound/soc/sprd/dai/vbc/Kconfig19
-rw-r--r--sound/soc/sprd/dai/vbc/dfm.h27
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/Kconfig3
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/Makefile6
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-codec.c1571
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-codec.h29
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-comm.c229
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc-comm.h219
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc.c637
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc.h22
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/Kconfig20
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/Makefile6
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-codec.c2596
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-codec.h30
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-comm.c374
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc-comm.h299
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc.c807
-rw-r--r--sound/soc/sprd/dai/vbc/r2p0/vbc.h22
-rwxr-xr-xsound/soc/sprd/i2s-r0p0-null-codec.c228
-rw-r--r--sound/soc/sprd/sprd-asoc-card.c566
-rwxr-xr-xsound/soc/sprd/sprd-asoc-common.c167
-rw-r--r--sound/soc/sprd/sprd-asoc-common.h84
-rw-r--r--sound/soc/sprd/sprd-asoc-debug.h35
-rw-r--r--sound/soc/sprd/vbc-r1p0-sprd-codec-v1.h129
-rwxr-xr-xsound/soc/sprd/vbc-r2p0-sprd-codec-v3.h207
-rwxr-xr-xsound/soc/sprd/vbc-r2p0-sprd-codec-v4.h218
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);