summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig42
-rw-r--r--sound/soc/codecs/Makefile14
-rw-r--r--sound/soc/codecs/adau-utils.c61
-rw-r--r--sound/soc/codecs/adau-utils.h7
-rw-r--r--sound/soc/codecs/adau1373.c38
-rw-r--r--sound/soc/codecs/adau1761-i2c.c2
-rw-r--r--sound/soc/codecs/adau1761-spi.c2
-rw-r--r--sound/soc/codecs/adau1781-i2c.c2
-rw-r--r--sound/soc/codecs/adau1781-spi.c2
-rw-r--r--sound/soc/codecs/adau17x1.c251
-rw-r--r--sound/soc/codecs/adau17x1.h6
-rw-r--r--sound/soc/codecs/adau7002.c80
-rw-r--r--sound/soc/codecs/ak4613.c14
-rw-r--r--sound/soc/codecs/ak4642.c13
-rw-r--r--sound/soc/codecs/arizona.c91
-rw-r--r--sound/soc/codecs/arizona.h22
-rw-r--r--sound/soc/codecs/bt-sco.c52
-rw-r--r--sound/soc/codecs/cs35l33.c1303
-rw-r--r--sound/soc/codecs/cs35l33.h221
-rw-r--r--sound/soc/codecs/cs47l24.c19
-rw-r--r--sound/soc/codecs/cs53l30.c1143
-rw-r--r--sound/soc/codecs/cs53l30.h459
-rw-r--r--sound/soc/codecs/cx20442.c1
-rw-r--r--sound/soc/codecs/da7219-aad.c103
-rw-r--r--sound/soc/codecs/da7219.c34
-rw-r--r--sound/soc/codecs/hdac_hdmi.c27
-rw-r--r--sound/soc/codecs/hdmi-codec.c15
-rw-r--r--sound/soc/codecs/max98504.c383
-rw-r--r--sound/soc/codecs/max98504.h59
-rw-r--r--sound/soc/codecs/max9860.c753
-rw-r--r--sound/soc/codecs/max9860.h162
-rw-r--r--[-rwxr-xr-x]sound/soc/codecs/max9867.c0
-rw-r--r--[-rwxr-xr-x]sound/soc/codecs/max9867.h0
-rw-r--r--sound/soc/codecs/max9877.h2
-rw-r--r--sound/soc/codecs/nau8825.c1256
-rw-r--r--sound/soc/codecs/nau8825.h127
-rw-r--r--sound/soc/codecs/pcm1681.c2
-rw-r--r--sound/soc/codecs/pcm179x.c2
-rw-r--r--sound/soc/codecs/pcm5102a.c1
-rw-r--r--sound/soc/codecs/rt286.c7
-rw-r--r--sound/soc/codecs/rt5514-spi.c447
-rw-r--r--sound/soc/codecs/rt5514-spi.h38
-rw-r--r--sound/soc/codecs/rt5514.c168
-rw-r--r--sound/soc/codecs/rt5514.h7
-rw-r--r--sound/soc/codecs/rt5645.c26
-rw-r--r--sound/soc/codecs/rt5645.h3
-rw-r--r--sound/soc/codecs/rt5670.c5
-rw-r--r--sound/soc/codecs/sgtl5000.c431
-rw-r--r--sound/soc/codecs/sgtl5000.h2
-rw-r--r--sound/soc/codecs/wm5102.c3
-rw-r--r--sound/soc/codecs/wm5110.c20
-rw-r--r--sound/soc/codecs/wm8940.c1
-rw-r--r--sound/soc/codecs/wm8998.c1
-rw-r--r--sound/soc/codecs/wm_adsp.c34
-rw-r--r--sound/soc/codecs/wm_adsp.h4
55 files changed, 7233 insertions, 735 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4d82a58ff6b0..d54604fecd7f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_ADAU1977_SPI if SPI_MASTER
select SND_SOC_ADAU1977_I2C if I2C
select SND_SOC_ADAU1701 if I2C
+ select SND_SOC_ADAU7002
select SND_SOC_ADS117X
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
@@ -46,6 +47,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_BT_SCO
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS35L32 if I2C
+ select SND_SOC_CS35L33 if I2C
select SND_SOC_CS42L51_I2C if I2C
select SND_SOC_CS42L52 if I2C && INPUT
select SND_SOC_CS42L56 if I2C && INPUT
@@ -57,6 +59,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CS42XX8_I2C if I2C
select SND_SOC_CS4349 if I2C
select SND_SOC_CS47L24 if MFD_CS47L24
+ select SND_SOC_CS53L30 if I2C
select SND_SOC_CX20442 if TTY
select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI
select SND_SOC_DA7213 if I2C
@@ -84,6 +87,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MAX98925 if I2C
select SND_SOC_MAX98926 if I2C
select SND_SOC_MAX9850 if I2C
+ select SND_SOC_MAX9860 if I2C
select SND_SOC_MAX9768 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_MC13783 if MFD_MC13XXX
@@ -269,8 +273,12 @@ config SND_SOC_AD1980
config SND_SOC_AD73311
tristate
+config SND_SOC_ADAU_UTILS
+ tristate
+
config SND_SOC_ADAU1373
tristate
+ select SND_SOC_ADAU_UTILS
config SND_SOC_ADAU1701
tristate "Analog Devices ADAU1701 CODEC"
@@ -280,6 +288,7 @@ config SND_SOC_ADAU1701
config SND_SOC_ADAU17X1
tristate
select SND_SOC_SIGMADSP_REGMAP
+ select SND_SOC_ADAU_UTILS
config SND_SOC_ADAU1761
tristate
@@ -322,6 +331,9 @@ config SND_SOC_ADAU1977_I2C
select SND_SOC_ADAU1977
select REGMAP_I2C
+config SND_SOC_ADAU7002
+ tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter"
+
config SND_SOC_ADAV80X
tristate
@@ -371,7 +383,7 @@ config SND_SOC_ALC5632
tristate
config SND_SOC_BT_SCO
- tristate
+ tristate "Dummy BT SCO codec driver"
config SND_SOC_CQ0093VC
tristate
@@ -380,6 +392,10 @@ config SND_SOC_CS35L32
tristate "Cirrus Logic CS35L32 CODEC"
depends on I2C
+config SND_SOC_CS35L33
+ tristate "Cirrus Logic CS35L33 CODEC"
+ depends on I2C
+
config SND_SOC_CS42L51
tristate
@@ -450,6 +466,11 @@ config SND_SOC_CS4349
config SND_SOC_CS47L24
tristate
+# Cirrus Logic Quad-Channel ADC
+config SND_SOC_CS53L30
+ tristate "Cirrus Logic CS53L30 CODEC"
+ depends on I2C
+
config SND_SOC_CX20442
tristate
depends on TTY
@@ -483,9 +504,10 @@ config SND_SOC_DMIC
tristate
config SND_SOC_HDMI_CODEC
- tristate
- select SND_PCM_ELD
- select SND_PCM_IEC958
+ tristate
+ select SND_PCM_ELD
+ select SND_PCM_IEC958
+ select HDMI
config SND_SOC_ES8328
tristate "Everest Semi ES8328 CODEC"
@@ -535,6 +557,10 @@ config SND_SOC_MAX98357A
config SND_SOC_MAX98371
tristate
+config SND_SOC_MAX98504
+ tristate "Maxim MAX98504 speaker amplifier"
+ depends on I2C
+
config SND_SOC_MAX9867
tristate
@@ -547,6 +573,11 @@ config SND_SOC_MAX98926
config SND_SOC_MAX9850
tristate
+config SND_SOC_MAX9860
+ tristate "Maxim MAX9860 Mono Audio Voice Codec"
+ depends on I2C
+ select REGMAP_I2C
+
config SND_SOC_PCM1681
tristate "Texas Instruments PCM1681 CODEC"
depends on I2C
@@ -643,6 +674,9 @@ config SND_SOC_RT298
config SND_SOC_RT5514
tristate
+config SND_SOC_RT5514_SPI
+ tristate
+
config SND_SOC_RT5616
tristate "Realtek RT5616 CODEC"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 0f548fd34ca3..58036af2c7d9 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -7,6 +7,7 @@ snd-soc-ad193x-spi-objs := ad193x-spi.o
snd-soc-ad193x-i2c-objs := ad193x-i2c.o
snd-soc-ad1980-objs := ad1980.o
snd-soc-ad73311-objs := ad73311.o
+snd-soc-adau-utils-objs := adau-utils.o
snd-soc-adau1373-objs := adau1373.o
snd-soc-adau1701-objs := adau1701.o
snd-soc-adau17x1-objs := adau17x1.o
@@ -19,6 +20,7 @@ snd-soc-adau1781-spi-objs := adau1781-spi.o
snd-soc-adau1977-objs := adau1977.o
snd-soc-adau1977-spi-objs := adau1977-spi.o
snd-soc-adau1977-i2c-objs := adau1977-i2c.o
+snd-soc-adau7002-objs := adau7002.o
snd-soc-adav80x-objs := adav80x.o
snd-soc-adav801-objs := adav801.o
snd-soc-adav803-objs := adav803.o
@@ -35,6 +37,7 @@ snd-soc-arizona-objs := arizona.o
snd-soc-bt-sco-objs := bt-sco.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs35l32-objs := cs35l32.o
+snd-soc-cs35l33-objs := cs35l33.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o
snd-soc-cs42l52-objs := cs42l52.o
@@ -49,6 +52,7 @@ snd-soc-cs42xx8-objs := cs42xx8.o
snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o
snd-soc-cs4349-objs := cs4349.o
snd-soc-cs47l24-objs := cs47l24.o
+snd-soc-cs53l30-objs := cs53l30.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
snd-soc-da7213-objs := da7213.o
@@ -79,6 +83,7 @@ snd-soc-max9867-objs := max9867.o
snd-soc-max98925-objs := max98925.o
snd-soc-max98926-objs := max98926.o
snd-soc-max9850-objs := max9850.o
+snd-soc-max9860-objs := max9860.o
snd-soc-mc13783-objs := mc13783.o
snd-soc-ml26124-objs := ml26124.o
snd-soc-nau8825-objs := nau8825.o
@@ -100,6 +105,7 @@ snd-soc-rl6347a-objs := rl6347a.o
snd-soc-rt286-objs := rt286.o
snd-soc-rt298-objs := rt298.o
snd-soc-rt5514-objs := rt5514.o
+snd-soc-rt5514-spi-objs := rt5514-spi.o
snd-soc-rt5616-objs := rt5616.o
snd-soc-rt5631-objs := rt5631.o
snd-soc-rt5640-objs := rt5640.o
@@ -208,6 +214,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
# Amp
snd-soc-max9877-objs := max9877.o
+snd-soc-max98504-objs := max98504.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
snd-soc-tas2552-objs := tas2552.o
@@ -220,6 +227,7 @@ obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o
obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o
obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o
obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o
obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o
@@ -232,6 +240,7 @@ obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o
obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o
obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o
obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o
+obj-$(CONFIG_SND_SOC_ADAU7002) += snd-soc-adau7002.o
obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o
obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o
obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o
@@ -250,6 +259,7 @@ obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o
+obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o
obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o
@@ -264,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o
obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o
obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o
obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o
+obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o
@@ -293,6 +304,7 @@ obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o
obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o
obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o
obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
+obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o
obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o
obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o
obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o
@@ -314,6 +326,7 @@ obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o
obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o
obj-$(CONFIG_SND_SOC_RT298) += snd-soc-rt298.o
obj-$(CONFIG_SND_SOC_RT5514) += snd-soc-rt5514.o
+obj-$(CONFIG_SND_SOC_RT5514_SPI) += snd-soc-rt5514-spi.o
obj-$(CONFIG_SND_SOC_RT5616) += snd-soc-rt5616.o
obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o
obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o
@@ -419,4 +432,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
+obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
diff --git a/sound/soc/codecs/adau-utils.c b/sound/soc/codecs/adau-utils.c
new file mode 100644
index 000000000000..19d6a6f41b12
--- /dev/null
+++ b/sound/soc/codecs/adau-utils.c
@@ -0,0 +1,61 @@
+/*
+ * Shared helper functions for devices from the ADAU family
+ *
+ * Copyright 2011-2016 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/gcd.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "adau-utils.h"
+
+int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
+ uint8_t regs[5])
+{
+ unsigned int r, n, m, i, j;
+ unsigned int div;
+
+ if (!freq_out) {
+ r = 0;
+ n = 0;
+ m = 0;
+ div = 0;
+ } else {
+ if (freq_out % freq_in != 0) {
+ div = DIV_ROUND_UP(freq_in, 13500000);
+ freq_in /= div;
+ r = freq_out / freq_in;
+ i = freq_out % freq_in;
+ j = gcd(i, freq_in);
+ n = i / j;
+ m = freq_in / j;
+ div--;
+ } else {
+ r = freq_out / freq_in;
+ n = 0;
+ m = 0;
+ div = 0;
+ }
+ if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
+ return -EINVAL;
+ }
+
+ regs[0] = m >> 8;
+ regs[1] = m & 0xff;
+ regs[2] = n >> 8;
+ regs[3] = n & 0xff;
+ regs[4] = (r << 3) | (div << 1);
+ if (m != 0)
+ regs[4] |= 1; /* Fractional mode */
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(adau_calc_pll_cfg);
+
+MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/adau-utils.h b/sound/soc/codecs/adau-utils.h
new file mode 100644
index 000000000000..939b5f37762f
--- /dev/null
+++ b/sound/soc/codecs/adau-utils.h
@@ -0,0 +1,7 @@
+#ifndef SOUND_SOC_CODECS_ADAU_PLL_H
+#define SOUND_SOC_CODECS_ADAU_PLL_H
+
+int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
+ uint8_t regs[5]);
+
+#endif
diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c
index fe1353a797b9..1556b360fa15 100644
--- a/sound/soc/codecs/adau1373.c
+++ b/sound/soc/codecs/adau1373.c
@@ -23,6 +23,7 @@
#include <sound/adau1373.h>
#include "adau1373.h"
+#include "adau-utils.h"
struct adau1373_dai {
unsigned int clk_src;
@@ -1254,7 +1255,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id,
{
struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec);
unsigned int dpll_div = 0;
- unsigned int x, r, n, m, i, j, mode;
+ uint8_t pll_regs[5];
+ int ret;
switch (pll_id) {
case ADAU1373_PLL1:
@@ -1295,27 +1297,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id,
dpll_div++;
}
- if (freq_out % freq_in != 0) {
- /* fout = fin * (r + (n/m)) / x */
- x = DIV_ROUND_UP(freq_in, 13500000);
- freq_in /= x;
- r = freq_out / freq_in;
- i = freq_out % freq_in;
- j = gcd(i, freq_in);
- n = i / j;
- m = freq_in / j;
- x--;
- mode = 1;
- } else {
- /* fout = fin / r */
- r = freq_out / freq_in;
- n = 0;
- m = 0;
- x = 0;
- mode = 0;
- }
-
- if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff)
+ ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs);
+ if (ret)
return -EINVAL;
if (dpll_div) {
@@ -1330,12 +1313,11 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id,
regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id),
(source << 4) | dpll_div);
- regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff);
- regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), m & 0xff);
- regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff);
- regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), n & 0xff);
- regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id),
- (r << 3) | (x << 1) | mode);
+ regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]);
+ regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]);
+ regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]);
+ regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]);
+ regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]);
/* Set sysclk to pll_rate / 4 */
regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09);
diff --git a/sound/soc/codecs/adau1761-i2c.c b/sound/soc/codecs/adau1761-i2c.c
index 8de010f758cd..9e7f257f17f8 100644
--- a/sound/soc/codecs/adau1761-i2c.c
+++ b/sound/soc/codecs/adau1761-i2c.c
@@ -31,7 +31,7 @@ static int adau1761_i2c_probe(struct i2c_client *client,
static int adau1761_i2c_remove(struct i2c_client *client)
{
- snd_soc_unregister_codec(&client->dev);
+ adau17x1_remove(&client->dev);
return 0;
}
diff --git a/sound/soc/codecs/adau1761-spi.c b/sound/soc/codecs/adau1761-spi.c
index d9171245bd9f..a0b214be759a 100644
--- a/sound/soc/codecs/adau1761-spi.c
+++ b/sound/soc/codecs/adau1761-spi.c
@@ -48,7 +48,7 @@ static int adau1761_spi_probe(struct spi_device *spi)
static int adau1761_spi_remove(struct spi_device *spi)
{
- snd_soc_unregister_codec(&spi->dev);
+ adau17x1_remove(&spi->dev);
return 0;
}
diff --git a/sound/soc/codecs/adau1781-i2c.c b/sound/soc/codecs/adau1781-i2c.c
index 06cbca84cf02..7b9d1802d159 100644
--- a/sound/soc/codecs/adau1781-i2c.c
+++ b/sound/soc/codecs/adau1781-i2c.c
@@ -31,7 +31,7 @@ static int adau1781_i2c_probe(struct i2c_client *client,
static int adau1781_i2c_remove(struct i2c_client *client)
{
- snd_soc_unregister_codec(&client->dev);
+ adau17x1_remove(&client->dev);
return 0;
}
diff --git a/sound/soc/codecs/adau1781-spi.c b/sound/soc/codecs/adau1781-spi.c
index 3d965a01b99c..9b233544d2e8 100644
--- a/sound/soc/codecs/adau1781-spi.c
+++ b/sound/soc/codecs/adau1781-spi.c
@@ -48,7 +48,7 @@ static int adau1781_spi_probe(struct spi_device *spi)
static int adau1781_spi_remove(struct spi_device *spi)
{
- snd_soc_unregister_codec(&spi->dev);
+ adau17x1_remove(&spi->dev);
return 0;
}
diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c
index fcf05b254ecd..439aa3ff1f99 100644
--- a/sound/soc/codecs/adau17x1.c
+++ b/sound/soc/codecs/adau17x1.c
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/core.h>
@@ -23,6 +24,7 @@
#include "sigmadsp.h"
#include "adau17x1.h"
+#include "adau-utils.h"
static const char * const adau17x1_capture_mixer_boost_text[] = {
"Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3",
@@ -302,6 +304,116 @@ bool adau17x1_has_dsp(struct adau *adau)
}
EXPORT_SYMBOL_GPL(adau17x1_has_dsp);
+static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct adau *adau = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ if (freq_in < 8000000 || freq_in > 27000000)
+ return -EINVAL;
+
+ ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs);
+ if (ret < 0)
+ return ret;
+
+ /* The PLL register is 6 bytes long and can only be written at once. */
+ ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL,
+ adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
+ if (ret)
+ return ret;
+
+ adau->pll_freq = freq_out;
+
+ return 0;
+}
+
+static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec);
+ struct adau *adau = snd_soc_codec_get_drvdata(dai->codec);
+ bool is_pll;
+ bool was_pll;
+
+ switch (clk_id) {
+ case ADAU17X1_CLK_SRC_MCLK:
+ is_pll = false;
+ break;
+ case ADAU17X1_CLK_SRC_PLL_AUTO:
+ if (!adau->mclk)
+ return -EINVAL;
+ /* Fall-through */
+ case ADAU17X1_CLK_SRC_PLL:
+ is_pll = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (adau->clk_src) {
+ case ADAU17X1_CLK_SRC_MCLK:
+ was_pll = false;
+ break;
+ case ADAU17X1_CLK_SRC_PLL:
+ case ADAU17X1_CLK_SRC_PLL_AUTO:
+ was_pll = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ adau->sysclk = freq;
+
+ if (is_pll != was_pll) {
+ if (is_pll) {
+ snd_soc_dapm_add_routes(dapm,
+ &adau17x1_dapm_pll_route, 1);
+ } else {
+ snd_soc_dapm_del_routes(dapm,
+ &adau17x1_dapm_pll_route, 1);
+ }
+ }
+
+ adau->clk_src = clk_id;
+
+ return 0;
+}
+
+static int adau17x1_auto_pll(struct snd_soc_dai *dai,
+ struct snd_pcm_hw_params *params)
+{
+ struct adau *adau = snd_soc_dai_get_drvdata(dai);
+ unsigned int pll_rate;
+
+ switch (params_rate(params)) {
+ case 48000:
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 96000:
+ pll_rate = 48000 * 1024;
+ break;
+ case 44100:
+ case 7350:
+ case 11025:
+ case 14700:
+ case 22050:
+ case 29400:
+ case 88200:
+ pll_rate = 44100 * 1024;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return adau17x1_set_dai_pll(dai, ADAU17X1_PLL, ADAU17X1_PLL_SRC_MCLK,
+ clk_get_rate(adau->mclk), pll_rate);
+}
+
static int adau17x1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
@@ -311,10 +423,19 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream,
unsigned int freq;
int ret;
- if (adau->clk_src == ADAU17X1_CLK_SRC_PLL)
+ switch (adau->clk_src) {
+ case ADAU17X1_CLK_SRC_PLL_AUTO:
+ ret = adau17x1_auto_pll(dai, params);
+ if (ret)
+ return ret;
+ /* Fall-through */
+ case ADAU17X1_CLK_SRC_PLL:
freq = adau->pll_freq;
- else
+ break;
+ default:
freq = adau->sysclk;
+ break;
+ }
if (freq % params_rate(params) != 0)
return -EINVAL;
@@ -386,93 +507,6 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream,
ADAU17X1_SERIAL_PORT1_DELAY_MASK, val);
}
-static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
- int source, unsigned int freq_in, unsigned int freq_out)
-{
- struct snd_soc_codec *codec = dai->codec;
- struct adau *adau = snd_soc_codec_get_drvdata(codec);
- unsigned int r, n, m, i, j;
- unsigned int div;
- int ret;
-
- if (freq_in < 8000000 || freq_in > 27000000)
- return -EINVAL;
-
- if (!freq_out) {
- r = 0;
- n = 0;
- m = 0;
- div = 0;
- } else {
- if (freq_out % freq_in != 0) {
- div = DIV_ROUND_UP(freq_in, 13500000);
- freq_in /= div;
- r = freq_out / freq_in;
- i = freq_out % freq_in;
- j = gcd(i, freq_in);
- n = i / j;
- m = freq_in / j;
- div--;
- } else {
- r = freq_out / freq_in;
- n = 0;
- m = 0;
- div = 0;
- }
- if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
- return -EINVAL;
- }
-
- adau->pll_regs[0] = m >> 8;
- adau->pll_regs[1] = m & 0xff;
- adau->pll_regs[2] = n >> 8;
- adau->pll_regs[3] = n & 0xff;
- adau->pll_regs[4] = (r << 3) | (div << 1);
- if (m != 0)
- adau->pll_regs[4] |= 1; /* Fractional mode */
-
- /* The PLL register is 6 bytes long and can only be written at once. */
- ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL,
- adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
- if (ret)
- return ret;
-
- adau->pll_freq = freq_out;
-
- return 0;
-}
-
-static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai,
- int clk_id, unsigned int freq, int dir)
-{
- struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec);
- struct adau *adau = snd_soc_codec_get_drvdata(dai->codec);
-
- switch (clk_id) {
- case ADAU17X1_CLK_SRC_MCLK:
- case ADAU17X1_CLK_SRC_PLL:
- break;
- default:
- return -EINVAL;
- }
-
- adau->sysclk = freq;
-
- if (adau->clk_src != clk_id) {
- if (clk_id == ADAU17X1_CLK_SRC_PLL) {
- snd_soc_dapm_add_routes(dapm,
- &adau17x1_dapm_pll_route, 1);
- } else {
- snd_soc_dapm_del_routes(dapm,
- &adau17x1_dapm_pll_route, 1);
- }
- }
-
- adau->clk_src = clk_id;
-
- return 0;
-}
-
static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
@@ -857,6 +891,10 @@ int adau17x1_add_routes(struct snd_soc_codec *codec)
ret = snd_soc_dapm_add_routes(dapm, adau17x1_no_dsp_dapm_routes,
ARRAY_SIZE(adau17x1_no_dsp_dapm_routes));
}
+
+ if (adau->clk_src != ADAU17X1_CLK_SRC_MCLK)
+ snd_soc_dapm_add_routes(dapm, &adau17x1_dapm_pll_route, 1);
+
return ret;
}
EXPORT_SYMBOL_GPL(adau17x1_add_routes);
@@ -879,6 +917,7 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap,
const char *firmware_name)
{
struct adau *adau;
+ int ret;
if (IS_ERR(regmap))
return PTR_ERR(regmap);
@@ -887,6 +926,30 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap,
if (!adau)
return -ENOMEM;
+ adau->mclk = devm_clk_get(dev, "mclk");
+ if (IS_ERR(adau->mclk)) {
+ if (PTR_ERR(adau->mclk) != -ENOENT)
+ return PTR_ERR(adau->mclk);
+ /* Clock is optional (for the driver) */
+ adau->mclk = NULL;
+ } else if (adau->mclk) {
+ adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO;
+
+ /*
+ * Any valid PLL output rate will work at this point, use one
+ * that is likely to be chosen later as well. The register will
+ * be written when the PLL is powered up for the first time.
+ */
+ ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024,
+ adau->pll_regs);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_prepare_enable(adau->mclk);
+ if (ret)
+ return ret;
+ }
+
adau->regmap = regmap;
adau->switch_mode = switch_mode;
adau->type = type;
@@ -910,6 +973,16 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap,
}
EXPORT_SYMBOL_GPL(adau17x1_probe);
+void adau17x1_remove(struct device *dev)
+{
+ struct adau *adau = dev_get_drvdata(dev);
+
+ snd_soc_unregister_codec(dev);
+ if (adau->mclk)
+ clk_disable_unprepare(adau->mclk);
+}
+EXPORT_SYMBOL_GPL(adau17x1_remove);
+
MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h
index 5ae87a084d97..bf04b7efee40 100644
--- a/sound/soc/codecs/adau17x1.h
+++ b/sound/soc/codecs/adau17x1.h
@@ -22,13 +22,18 @@ enum adau17x1_pll_src {
};
enum adau17x1_clk_src {
+ /* Automatically configure PLL based on the sample rate */
+ ADAU17X1_CLK_SRC_PLL_AUTO,
ADAU17X1_CLK_SRC_MCLK,
ADAU17X1_CLK_SRC_PLL,
};
+struct clk;
+
struct adau {
unsigned int sysclk;
unsigned int pll_freq;
+ struct clk *mclk;
enum adau17x1_clk_src clk_src;
enum adau17x1_type type;
@@ -52,6 +57,7 @@ int adau17x1_add_routes(struct snd_soc_codec *codec);
int adau17x1_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev),
const char *firmware_name);
+void adau17x1_remove(struct device *dev);
int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec,
enum adau17x1_micbias_voltage micbias);
bool adau17x1_readable_register(struct device *dev, unsigned int reg);
diff --git a/sound/soc/codecs/adau7002.c b/sound/soc/codecs/adau7002.c
new file mode 100644
index 000000000000..9df72c6adcca
--- /dev/null
+++ b/sound/soc/codecs/adau7002.c
@@ -0,0 +1,80 @@
+/*
+ * ADAU7002 Stereo PDM-to-I2S/TDM converter driver
+ *
+ * Copyright 2014-2016 Analog Devices
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static const struct snd_soc_dapm_widget adau7002_widgets[] = {
+ SND_SOC_DAPM_INPUT("PDM_DAT"),
+ SND_SOC_DAPM_REGULATOR_SUPPLY("IOVDD", 0, 0),
+};
+
+static const struct snd_soc_dapm_route adau7002_routes[] = {
+ { "Capture", NULL, "PDM_DAT" },
+ { "Capture", NULL, "IOVDD" },
+};
+
+static struct snd_soc_dai_driver adau7002_dai = {
+ .name = "adau7002-hifi",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE,
+ .sig_bits = 20,
+ },
+};
+
+static const struct snd_soc_codec_driver adau7002_codec_driver = {
+ .dapm_widgets = adau7002_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(adau7002_widgets),
+ .dapm_routes = adau7002_routes,
+ .num_dapm_routes = ARRAY_SIZE(adau7002_routes),
+};
+
+static int adau7002_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &adau7002_codec_driver,
+ &adau7002_dai, 1);
+}
+
+static int adau7002_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id adau7002_dt_ids[] = {
+ { .compatible = "adi,adau7002", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adau7002_dt_ids);
+#endif
+
+static struct platform_driver adau7002_driver = {
+ .driver = {
+ .name = "adau7002",
+ .of_match_table = of_match_ptr(adau7002_dt_ids),
+ },
+ .probe = adau7002_probe,
+ .remove = adau7002_remove,
+};
+module_platform_driver(adau7002_driver);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ADAU7002 Stereo PDM-to-I2S/TDM Converter driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c
index 647f69de6baa..97798d250f08 100644
--- a/sound/soc/codecs/ak4613.c
+++ b/sound/soc/codecs/ak4613.c
@@ -146,6 +146,7 @@ static const struct regmap_config ak4613_regmap_cfg = {
.max_register = 0x16,
.reg_defaults = ak4613_reg,
.num_reg_defaults = ARRAY_SIZE(ak4613_reg),
+ .cache_type = REGCACHE_RBTREE,
};
static const struct of_device_id ak4613_of_match[] = {
@@ -436,15 +437,25 @@ static struct snd_soc_dai_driver ak4613_dai = {
.symmetric_rates = 1,
};
-static int ak4613_resume(struct snd_soc_codec *codec)
+static int ak4613_suspend(struct snd_soc_codec *codec)
{
struct regmap *regmap = dev_get_regmap(codec->dev, NULL);
+ regcache_cache_only(regmap, true);
regcache_mark_dirty(regmap);
+ return 0;
+}
+
+static int ak4613_resume(struct snd_soc_codec *codec)
+{
+ struct regmap *regmap = dev_get_regmap(codec->dev, NULL);
+
+ regcache_cache_only(regmap, false);
return regcache_sync(regmap);
}
static struct snd_soc_codec_driver soc_codec_dev_ak4613 = {
+ .suspend = ak4613_suspend,
.resume = ak4613_resume,
.set_bias_level = ak4613_set_bias_level,
.controls = ak4613_snd_controls,
@@ -530,7 +541,6 @@ static int ak4613_i2c_remove(struct i2c_client *client)
static struct i2c_driver ak4613_i2c_driver = {
.driver = {
.name = "ak4613-codec",
- .owner = THIS_MODULE,
.of_match_table = ak4613_of_match,
},
.probe = ak4613_i2c_probe,
diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c
index 4d8b9e49e8d6..cc941d66ec3d 100644
--- a/sound/soc/codecs/ak4642.c
+++ b/sound/soc/codecs/ak4642.c
@@ -523,15 +523,23 @@ static struct snd_soc_dai_driver ak4642_dai = {
.symmetric_rates = 1,
};
-static int ak4642_resume(struct snd_soc_codec *codec)
+static int ak4642_suspend(struct snd_soc_codec *codec)
{
struct regmap *regmap = dev_get_regmap(codec->dev, NULL);
+ regcache_cache_only(regmap, true);
regcache_mark_dirty(regmap);
- regcache_sync(regmap);
return 0;
}
+static int ak4642_resume(struct snd_soc_codec *codec)
+{
+ struct regmap *regmap = dev_get_regmap(codec->dev, NULL);
+
+ regcache_cache_only(regmap, false);
+ regcache_sync(regmap);
+ return 0;
+}
static int ak4642_probe(struct snd_soc_codec *codec)
{
struct ak4642_priv *priv = snd_soc_codec_get_drvdata(codec);
@@ -544,6 +552,7 @@ static int ak4642_probe(struct snd_soc_codec *codec)
static struct snd_soc_codec_driver soc_codec_dev_ak4642 = {
.probe = ak4642_probe,
+ .suspend = ak4642_suspend,
.resume = ak4642_resume,
.set_bias_level = ak4642_set_bias_level,
.controls = ak4642_snd_controls,
diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c
index 664a8c044ffb..ecfdbfcae366 100644
--- a/sound/soc/codecs/arizona.c
+++ b/sound/soc/codecs/arizona.c
@@ -85,30 +85,9 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w,
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
struct arizona *arizona = dev_get_drvdata(codec->dev->parent);
- struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
- bool manual_ena = false;
int val;
- switch (arizona->type) {
- case WM5102:
- switch (arizona->rev) {
- case 0:
- break;
- default:
- manual_ena = true;
- break;
- }
- default:
- break;
- }
-
switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
- if (!priv->spk_ena && manual_ena) {
- regmap_write_async(arizona->regmap, 0x4f5, 0x25a);
- priv->spk_ena_pending = true;
- }
- break;
case SND_SOC_DAPM_POST_PMU:
val = snd_soc_read(codec, ARIZONA_INTERRUPT_RAW_STATUS_3);
if (val & ARIZONA_SPK_OVERHEAT_STS) {
@@ -120,33 +99,12 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w,
regmap_update_bits_async(arizona->regmap,
ARIZONA_OUTPUT_ENABLES_1,
1 << w->shift, 1 << w->shift);
-
- if (priv->spk_ena_pending) {
- msleep(75);
- regmap_write_async(arizona->regmap, 0x4f5, 0xda);
- priv->spk_ena_pending = false;
- priv->spk_ena++;
- }
break;
case SND_SOC_DAPM_PRE_PMD:
- if (manual_ena) {
- priv->spk_ena--;
- if (!priv->spk_ena)
- regmap_write_async(arizona->regmap,
- 0x4f5, 0x25a);
- }
-
regmap_update_bits_async(arizona->regmap,
ARIZONA_OUTPUT_ENABLES_1,
1 << w->shift, 0);
break;
- case SND_SOC_DAPM_POST_PMD:
- if (manual_ena) {
- if (!priv->spk_ena)
- regmap_write_async(arizona->regmap,
- 0x4f5, 0x0da);
- }
- break;
default:
break;
}
@@ -324,6 +282,17 @@ int arizona_init_gpio(struct snd_soc_codec *codec)
}
EXPORT_SYMBOL_GPL(arizona_init_gpio);
+int arizona_init_notifiers(struct snd_soc_codec *codec)
+{
+ struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
+ struct arizona *arizona = priv->arizona;
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&arizona->notifier);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(arizona_init_notifiers);
+
const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = {
"None",
"Tone Generator 1",
@@ -619,7 +588,7 @@ const struct soc_enum arizona_asrc_rate1 =
arizona_rate_text, arizona_rate_val);
EXPORT_SYMBOL_GPL(arizona_asrc_rate1);
-static const char *arizona_vol_ramp_text[] = {
+static const char * const arizona_vol_ramp_text[] = {
"0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB",
"15ms/6dB", "30ms/6dB",
};
@@ -648,7 +617,7 @@ SOC_ENUM_SINGLE_DECL(arizona_out_vi_ramp,
arizona_vol_ramp_text);
EXPORT_SYMBOL_GPL(arizona_out_vi_ramp);
-static const char *arizona_lhpf_mode_text[] = {
+static const char * const arizona_lhpf_mode_text[] = {
"Low-pass", "High-pass"
};
@@ -676,7 +645,7 @@ SOC_ENUM_SINGLE_DECL(arizona_lhpf4_mode,
arizona_lhpf_mode_text);
EXPORT_SYMBOL_GPL(arizona_lhpf4_mode);
-static const char *arizona_ng_hold_text[] = {
+static const char * const arizona_ng_hold_text[] = {
"30ms", "120ms", "250ms", "500ms",
};
@@ -810,6 +779,14 @@ const struct soc_enum arizona_output_anc_src[] = {
};
EXPORT_SYMBOL_GPL(arizona_output_anc_src);
+const struct snd_kcontrol_new arizona_voice_trigger_switch[] = {
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0),
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 1, 1, 0),
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 2, 1, 0),
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 3, 1, 0),
+};
+EXPORT_SYMBOL_GPL(arizona_voice_trigger_switch);
+
static void arizona_in_set_vu(struct snd_soc_codec *codec, int ena)
{
struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
@@ -2573,6 +2550,30 @@ int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol,
}
EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put);
+int arizona_register_notifier(struct snd_soc_codec *codec,
+ struct notifier_block *nb,
+ int (*notify)(struct notifier_block *nb,
+ unsigned long action, void *data))
+{
+ struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
+ struct arizona *arizona = priv->arizona;
+
+ nb->notifier_call = notify;
+
+ return blocking_notifier_chain_register(&arizona->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(arizona_register_notifier);
+
+int arizona_unregister_notifier(struct snd_soc_codec *codec,
+ struct notifier_block *nb)
+{
+ struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
+ struct arizona *arizona = priv->arizona;
+
+ return blocking_notifier_chain_unregister(&arizona->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(arizona_unregister_notifier);
+
MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h
index ce0531b8c632..69da1ef3a17c 100644
--- a/sound/soc/codecs/arizona.h
+++ b/sound/soc/codecs/arizona.h
@@ -63,6 +63,9 @@
#define ARIZONA_DVFS_SR1_RQ 0x001
#define ARIZONA_DVFS_ADSP1_RQ 0x100
+/* Notifier events */
+#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1
+
struct arizona;
struct wm_adsp;
@@ -87,14 +90,15 @@ struct arizona_priv {
unsigned int out_down_pending;
unsigned int out_down_delay;
- unsigned int spk_ena:2;
- unsigned int spk_ena_pending:1;
-
unsigned int dvfs_reqs;
struct mutex dvfs_lock;
bool dvfs_cached;
};
+struct arizona_voice_trigger_info {
+ int core;
+};
+
#define ARIZONA_NUM_MIXER_INPUTS 104
extern const unsigned int arizona_mixer_tlv[];
@@ -248,6 +252,8 @@ extern const struct soc_enum arizona_anc_input_src[];
extern const struct soc_enum arizona_anc_ng_enum;
extern const struct soc_enum arizona_output_anc_src[];
+extern const struct snd_kcontrol_new arizona_voice_trigger_switch[];
+
extern int arizona_in_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int event);
@@ -306,6 +312,7 @@ extern int arizona_set_fll(struct arizona_fll *fll, int source,
extern int arizona_init_spk(struct snd_soc_codec *codec);
extern int arizona_init_gpio(struct snd_soc_codec *codec);
extern int arizona_init_mono(struct snd_soc_codec *codec);
+extern int arizona_init_notifiers(struct snd_soc_codec *codec);
extern int arizona_free_spk(struct snd_soc_codec *codec);
@@ -317,4 +324,13 @@ int arizona_set_output_mode(struct snd_soc_codec *codec, int output,
extern bool arizona_input_analog(struct snd_soc_codec *codec, int shift);
extern const char *arizona_sample_rate_val_to_name(unsigned int rate_val);
+
+extern int arizona_register_notifier(struct snd_soc_codec *codec,
+ struct notifier_block *nb,
+ int (*notify)(struct notifier_block *nb,
+ unsigned long action,
+ void *data));
+extern int arizona_unregister_notifier(struct snd_soc_codec *codec,
+ struct notifier_block *nb);
+
#endif
diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c
index b084ad113e96..2a8d0ee141d4 100644
--- a/sound/soc/codecs/bt-sco.c
+++ b/sound/soc/codecs/bt-sco.c
@@ -25,22 +25,41 @@ static const struct snd_soc_dapm_route bt_sco_routes[] = {
{ "TX", NULL, "Playback" },
};
-static struct snd_soc_dai_driver bt_sco_dai = {
- .name = "bt-sco-pcm",
- .playback = {
- .stream_name = "Playback",
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
+static struct snd_soc_dai_driver bt_sco_dai[] = {
+ {
+ .name = "bt-sco-pcm",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
},
+ {
+ .name = "bt-sco-pcm-wb",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ }
};
static struct snd_soc_codec_driver soc_codec_dev_bt_sco = {
@@ -53,7 +72,7 @@ static struct snd_soc_codec_driver soc_codec_dev_bt_sco = {
static int bt_sco_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_bt_sco,
- &bt_sco_dai, 1);
+ bt_sco_dai, ARRAY_SIZE(bt_sco_dai));
}
static int bt_sco_remove(struct platform_device *pdev)
@@ -77,6 +96,7 @@ MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids);
#if defined(CONFIG_OF)
static const struct of_device_id bt_sco_codec_of_match[] = {
{ .compatible = "delta,dfbmcs320", },
+ { .compatible = "linux,bt-sco", },
{},
};
MODULE_DEVICE_TABLE(of, bt_sco_codec_of_match);
diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c
new file mode 100644
index 000000000000..6f9c1addcd7f
--- /dev/null
+++ b/sound/soc/codecs/cs35l33.c
@@ -0,0 +1,1303 @@
+/*
+ * cs35l33.c -- CS35L33 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Paul Handrigan <paul.handrigan@cirrus.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <sound/cs35l33.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/machine.h>
+#include <linux/of_gpio.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+
+#include "cs35l33.h"
+
+#define CS35L33_BOOT_DELAY 50
+
+struct cs35l33_private {
+ struct snd_soc_codec *codec;
+ struct cs35l33_pdata pdata;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ bool amp_cal;
+ int mclk_int;
+ struct regulator_bulk_data core_supplies[2];
+ int num_core_supplies;
+ bool is_tdm_mode;
+ bool enable_soft_ramp;
+};
+
+static const struct reg_default cs35l33_reg[] = {
+ {CS35L33_PWRCTL1, 0x85},
+ {CS35L33_PWRCTL2, 0xFE},
+ {CS35L33_CLK_CTL, 0x0C},
+ {CS35L33_BST_PEAK_CTL, 0x90},
+ {CS35L33_PROTECT_CTL, 0x55},
+ {CS35L33_BST_CTL1, 0x00},
+ {CS35L33_BST_CTL2, 0x01},
+ {CS35L33_ADSP_CTL, 0x00},
+ {CS35L33_ADC_CTL, 0xC8},
+ {CS35L33_DAC_CTL, 0x14},
+ {CS35L33_DIG_VOL_CTL, 0x00},
+ {CS35L33_CLASSD_CTL, 0x04},
+ {CS35L33_AMP_CTL, 0x90},
+ {CS35L33_INT_MASK_1, 0xFF},
+ {CS35L33_INT_MASK_2, 0xFF},
+ {CS35L33_DIAG_LOCK, 0x00},
+ {CS35L33_DIAG_CTRL_1, 0x40},
+ {CS35L33_DIAG_CTRL_2, 0x00},
+ {CS35L33_HG_MEMLDO_CTL, 0x62},
+ {CS35L33_HG_REL_RATE, 0x03},
+ {CS35L33_LDO_DEL, 0x12},
+ {CS35L33_HG_HEAD, 0x0A},
+ {CS35L33_HG_EN, 0x05},
+ {CS35L33_TX_VMON, 0x00},
+ {CS35L33_TX_IMON, 0x03},
+ {CS35L33_TX_VPMON, 0x02},
+ {CS35L33_TX_VBSTMON, 0x05},
+ {CS35L33_TX_FLAG, 0x06},
+ {CS35L33_TX_EN1, 0x00},
+ {CS35L33_TX_EN2, 0x00},
+ {CS35L33_TX_EN3, 0x00},
+ {CS35L33_TX_EN4, 0x00},
+ {CS35L33_RX_AUD, 0x40},
+ {CS35L33_RX_SPLY, 0x03},
+ {CS35L33_RX_ALIVE, 0x04},
+ {CS35L33_BST_CTL4, 0x63},
+};
+
+static const struct reg_sequence cs35l33_patch[] = {
+ { 0x00, 0x99, 0 },
+ { 0x59, 0x02, 0 },
+ { 0x52, 0x30, 0 },
+ { 0x39, 0x45, 0 },
+ { 0x57, 0x30, 0 },
+ { 0x2C, 0x68, 0 },
+ { 0x00, 0x00, 0 },
+};
+
+static bool cs35l33_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS35L33_DEVID_AB:
+ case CS35L33_DEVID_CD:
+ case CS35L33_DEVID_E:
+ case CS35L33_REV_ID:
+ case CS35L33_INT_STATUS_1:
+ case CS35L33_INT_STATUS_2:
+ case CS35L33_HG_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs35l33_writeable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ /* these are read only registers */
+ case CS35L33_DEVID_AB:
+ case CS35L33_DEVID_CD:
+ case CS35L33_DEVID_E:
+ case CS35L33_REV_ID:
+ case CS35L33_INT_STATUS_1:
+ case CS35L33_INT_STATUS_2:
+ case CS35L33_HG_STATUS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool cs35l33_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS35L33_DEVID_AB:
+ case CS35L33_DEVID_CD:
+ case CS35L33_DEVID_E:
+ case CS35L33_REV_ID:
+ case CS35L33_PWRCTL1:
+ case CS35L33_PWRCTL2:
+ case CS35L33_CLK_CTL:
+ case CS35L33_BST_PEAK_CTL:
+ case CS35L33_PROTECT_CTL:
+ case CS35L33_BST_CTL1:
+ case CS35L33_BST_CTL2:
+ case CS35L33_ADSP_CTL:
+ case CS35L33_ADC_CTL:
+ case CS35L33_DAC_CTL:
+ case CS35L33_DIG_VOL_CTL:
+ case CS35L33_CLASSD_CTL:
+ case CS35L33_AMP_CTL:
+ case CS35L33_INT_MASK_1:
+ case CS35L33_INT_MASK_2:
+ case CS35L33_INT_STATUS_1:
+ case CS35L33_INT_STATUS_2:
+ case CS35L33_DIAG_LOCK:
+ case CS35L33_DIAG_CTRL_1:
+ case CS35L33_DIAG_CTRL_2:
+ case CS35L33_HG_MEMLDO_CTL:
+ case CS35L33_HG_REL_RATE:
+ case CS35L33_LDO_DEL:
+ case CS35L33_HG_HEAD:
+ case CS35L33_HG_EN:
+ case CS35L33_TX_VMON:
+ case CS35L33_TX_IMON:
+ case CS35L33_TX_VPMON:
+ case CS35L33_TX_VBSTMON:
+ case CS35L33_TX_FLAG:
+ case CS35L33_TX_EN1:
+ case CS35L33_TX_EN2:
+ case CS35L33_TX_EN3:
+ case CS35L33_TX_EN4:
+ case CS35L33_RX_AUD:
+ case CS35L33_RX_SPLY:
+ case CS35L33_RX_ALIVE:
+ case CS35L33_BST_CTL4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 100, 0);
+static DECLARE_TLV_DB_SCALE(dac_tlv, -10200, 50, 0);
+
+static const struct snd_kcontrol_new cs35l33_snd_controls[] = {
+
+ SOC_SINGLE_TLV("SPK Amp Volume", CS35L33_AMP_CTL,
+ 4, 0x09, 0, classd_ctl_tlv),
+ SOC_SINGLE_SX_TLV("DAC Volume", CS35L33_DIG_VOL_CTL,
+ 0, 0x34, 0xE4, dac_tlv),
+};
+
+static int cs35l33_spkrdrv_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (!priv->amp_cal) {
+ usleep_range(8000, 9000);
+ priv->amp_cal = true;
+ regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL,
+ CS35L33_AMP_CAL, 0);
+ dev_dbg(codec->dev, "Amp calibration done\n");
+ }
+ dev_dbg(codec->dev, "Amp turned on\n");
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ dev_dbg(codec->dev, "Amp turned off\n");
+ break;
+ default:
+ dev_err(codec->dev, "Invalid event = 0x%x\n", event);
+ break;
+ }
+
+ return 0;
+}
+
+static int cs35l33_sdin_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL1,
+ CS35L33_PDN_BST, 0);
+ val = priv->is_tdm_mode ? 0 : CS35L33_PDN_TDM;
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL2,
+ CS35L33_PDN_TDM, val);
+ dev_dbg(codec->dev, "BST turned on\n");
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ dev_dbg(codec->dev, "SDIN turned on\n");
+ if (!priv->amp_cal) {
+ regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL,
+ CS35L33_AMP_CAL, CS35L33_AMP_CAL);
+ dev_dbg(codec->dev, "Amp calibration started\n");
+ usleep_range(10000, 11000);
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL2,
+ CS35L33_PDN_TDM, CS35L33_PDN_TDM);
+ usleep_range(4000, 4100);
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL1,
+ CS35L33_PDN_BST, CS35L33_PDN_BST);
+ dev_dbg(codec->dev, "BST and SDIN turned off\n");
+ break;
+ default:
+ dev_err(codec->dev, "Invalid event = 0x%x\n", event);
+
+ }
+
+ return 0;
+}
+
+static int cs35l33_sdout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+ unsigned int mask = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM;
+ unsigned int mask2 = CS35L33_SDOUT_3ST_TDM;
+ unsigned int val, val2;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (priv->is_tdm_mode) {
+ /* set sdout_3st_i2s and reset pdn_tdm */
+ val = CS35L33_SDOUT_3ST_I2S;
+ /* reset sdout_3st_tdm */
+ val2 = 0;
+ } else {
+ /* reset sdout_3st_i2s and set pdn_tdm */
+ val = CS35L33_PDN_TDM;
+ /* set sdout_3st_tdm */
+ val2 = CS35L33_SDOUT_3ST_TDM;
+ }
+ dev_dbg(codec->dev, "SDOUT turned on\n");
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ val = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM;
+ val2 = CS35L33_SDOUT_3ST_TDM;
+ dev_dbg(codec->dev, "SDOUT turned off\n");
+ break;
+ default:
+ dev_err(codec->dev, "Invalid event = 0x%x\n", event);
+ return 0;
+ }
+
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL2,
+ mask, val);
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ mask2, val2);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cs35l33_dapm_widgets[] = {
+
+ SND_SOC_DAPM_OUTPUT("SPK"),
+ SND_SOC_DAPM_OUT_DRV_E("SPKDRV", CS35L33_PWRCTL1, 7, 1, NULL, 0,
+ cs35l33_spkrdrv_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L33_PWRCTL2,
+ 2, 1, cs35l33_sdin_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_INPUT("MON"),
+
+ SND_SOC_DAPM_ADC("VMON", NULL,
+ CS35L33_PWRCTL2, CS35L33_PDN_VMON_SHIFT, 1),
+ SND_SOC_DAPM_ADC("IMON", NULL,
+ CS35L33_PWRCTL2, CS35L33_PDN_IMON_SHIFT, 1),
+ SND_SOC_DAPM_ADC("VPMON", NULL,
+ CS35L33_PWRCTL2, CS35L33_PDN_VPMON_SHIFT, 1),
+ SND_SOC_DAPM_ADC("VBSTMON", NULL,
+ CS35L33_PWRCTL2, CS35L33_PDN_VBSTMON_SHIFT, 1),
+
+ SND_SOC_DAPM_AIF_OUT_E("SDOUT", NULL, 0, SND_SOC_NOPM, 0, 0,
+ cs35l33_sdout_event, SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_PRE_PMD),
+};
+
+static const struct snd_soc_dapm_route cs35l33_audio_map[] = {
+ {"SDIN", NULL, "CS35L33 Playback"},
+ {"SPKDRV", NULL, "SDIN"},
+ {"SPK", NULL, "SPKDRV"},
+
+ {"VMON", NULL, "MON"},
+ {"IMON", NULL, "MON"},
+
+ {"SDOUT", NULL, "VMON"},
+ {"SDOUT", NULL, "IMON"},
+ {"CS35L33 Capture", NULL, "SDOUT"},
+};
+
+static const struct snd_soc_dapm_route cs35l33_vphg_auto_route[] = {
+ {"SPKDRV", NULL, "VPMON"},
+ {"VPMON", NULL, "CS35L33 Playback"},
+};
+
+static const struct snd_soc_dapm_route cs35l33_vp_vbst_mon_route[] = {
+ {"SDOUT", NULL, "VPMON"},
+ {"VPMON", NULL, "MON"},
+ {"SDOUT", NULL, "VBSTMON"},
+ {"VBSTMON", NULL, "MON"},
+};
+
+static int cs35l33_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int val;
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL1,
+ CS35L33_PDN_ALL, 0);
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ CS35L33_MCLKDIS, 0);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL1,
+ CS35L33_PDN_ALL, CS35L33_PDN_ALL);
+ regmap_read(priv->regmap, CS35L33_INT_STATUS_2, &val);
+ usleep_range(1000, 1100);
+ if (val & CS35L33_PDN_DONE)
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ CS35L33_MCLKDIS, CS35L33_MCLKDIS);
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct cs35l33_mclk_div {
+ int mclk;
+ int srate;
+ u8 adsp_rate;
+ u8 int_fs_ratio;
+};
+
+static const struct cs35l33_mclk_div cs35l33_mclk_coeffs[] = {
+ /* MCLK, Sample Rate, adsp_rate, int_fs_ratio */
+ {5644800, 11025, 0x4, CS35L33_INT_FS_RATE},
+ {5644800, 22050, 0x8, CS35L33_INT_FS_RATE},
+ {5644800, 44100, 0xC, CS35L33_INT_FS_RATE},
+
+ {6000000, 8000, 0x1, 0},
+ {6000000, 11025, 0x2, 0},
+ {6000000, 11029, 0x3, 0},
+ {6000000, 12000, 0x4, 0},
+ {6000000, 16000, 0x5, 0},
+ {6000000, 22050, 0x6, 0},
+ {6000000, 22059, 0x7, 0},
+ {6000000, 24000, 0x8, 0},
+ {6000000, 32000, 0x9, 0},
+ {6000000, 44100, 0xA, 0},
+ {6000000, 44118, 0xB, 0},
+ {6000000, 48000, 0xC, 0},
+
+ {6144000, 8000, 0x1, CS35L33_INT_FS_RATE},
+ {6144000, 12000, 0x4, CS35L33_INT_FS_RATE},
+ {6144000, 16000, 0x5, CS35L33_INT_FS_RATE},
+ {6144000, 24000, 0x8, CS35L33_INT_FS_RATE},
+ {6144000, 32000, 0x9, CS35L33_INT_FS_RATE},
+ {6144000, 48000, 0xC, CS35L33_INT_FS_RATE},
+};
+
+static int cs35l33_get_mclk_coeff(int mclk, int srate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs35l33_mclk_coeffs); i++) {
+ if (cs35l33_mclk_coeffs[i].mclk == mclk &&
+ cs35l33_mclk_coeffs[i].srate == srate)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static int cs35l33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL,
+ CS35L33_MS_MASK, CS35L33_MS_MASK);
+ dev_dbg(codec->dev, "Audio port in master mode\n");
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL,
+ CS35L33_MS_MASK, 0);
+ dev_dbg(codec->dev, "Audio port in slave mode\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ /*
+ * tdm mode in cs35l33 resembles dsp-a mode very
+ * closely, it is dsp-a with fsync shifted left by half bclk
+ */
+ priv->is_tdm_mode = true;
+ dev_dbg(codec->dev, "Audio port in TDM mode\n");
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ priv->is_tdm_mode = false;
+ dev_dbg(codec->dev, "Audio port in I2S mode\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs35l33_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 cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+ int sample_size = params_width(params);
+ int coeff = cs35l33_get_mclk_coeff(priv->mclk_int, params_rate(params));
+
+ if (coeff < 0)
+ return coeff;
+
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ CS35L33_ADSP_FS | CS35L33_INT_FS_RATE,
+ cs35l33_mclk_coeffs[coeff].int_fs_ratio
+ | cs35l33_mclk_coeffs[coeff].adsp_rate);
+
+ if (priv->is_tdm_mode) {
+ sample_size = (sample_size / 8) - 1;
+ if (sample_size > 2)
+ sample_size = 2;
+ regmap_update_bits(priv->regmap, CS35L33_RX_AUD,
+ CS35L33_AUDIN_RX_DEPTH,
+ sample_size << CS35L33_AUDIN_RX_DEPTH_SHIFT);
+ }
+
+ dev_dbg(codec->dev, "sample rate=%d, bits per sample=%d\n",
+ params_rate(params), params_width(params));
+
+ return 0;
+}
+
+static const unsigned int cs35l33_src_rates[] = {
+ 8000, 11025, 11029, 12000, 16000, 22050,
+ 22059, 24000, 32000, 44100, 44118, 48000
+};
+
+static const struct snd_pcm_hw_constraint_list cs35l33_constraints = {
+ .count = ARRAY_SIZE(cs35l33_src_rates),
+ .list = cs35l33_src_rates,
+};
+
+static int cs35l33_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &cs35l33_constraints);
+ return 0;
+}
+
+static int cs35l33_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ if (tristate) {
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL2,
+ CS35L33_SDOUT_3ST_I2S, CS35L33_SDOUT_3ST_I2S);
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ CS35L33_SDOUT_3ST_TDM, CS35L33_SDOUT_3ST_TDM);
+ } else {
+ regmap_update_bits(priv->regmap, CS35L33_PWRCTL2,
+ CS35L33_SDOUT_3ST_I2S, 0);
+ regmap_update_bits(priv->regmap, CS35L33_CLK_CTL,
+ CS35L33_SDOUT_3ST_TDM, 0);
+ }
+
+ return 0;
+}
+
+static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+ unsigned int reg, bit_pos, i;
+ int slot, slot_num;
+
+ if (slot_width != 8)
+ return -EINVAL;
+
+ /* scan rx_mask for aud slot */
+ slot = ffs(rx_mask) - 1;
+ if (slot >= 0) {
+ regmap_update_bits(priv->regmap, CS35L33_RX_AUD,
+ CS35L33_X_LOC, slot);
+ dev_dbg(codec->dev, "Audio starts from slots %d", slot);
+ }
+
+ /*
+ * scan tx_mask: vmon(2 slots); imon (2 slots);
+ * vpmon (1 slot) vbstmon (1 slot)
+ */
+ slot = ffs(tx_mask) - 1;
+ slot_num = 0;
+
+ for (i = 0; i < 2 ; i++) {
+ /* disable vpmon/vbstmon: enable later if set in tx_mask */
+ regmap_update_bits(priv->regmap, CS35L33_TX_VPMON + i,
+ CS35L33_X_STATE | CS35L33_X_LOC, CS35L33_X_STATE
+ | CS35L33_X_LOC);
+ }
+
+ /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/
+ snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route,
+ ARRAY_SIZE(cs35l33_vp_vbst_mon_route));
+
+ while (slot >= 0) {
+ /* configure VMON_TX_LOC */
+ if (slot_num == 0) {
+ regmap_update_bits(priv->regmap, CS35L33_TX_VMON,
+ CS35L33_X_STATE | CS35L33_X_LOC, slot);
+ dev_dbg(codec->dev, "VMON enabled in slots %d-%d",
+ slot, slot + 1);
+ }
+
+ /* configure IMON_TX_LOC */
+ if (slot_num == 3) {
+ regmap_update_bits(priv->regmap, CS35L33_TX_IMON,
+ CS35L33_X_STATE | CS35L33_X_LOC, slot);
+ dev_dbg(codec->dev, "IMON enabled in slots %d-%d",
+ slot, slot + 1);
+ }
+
+ /* configure VPMON_TX_LOC */
+ if (slot_num == 4) {
+ regmap_update_bits(priv->regmap, CS35L33_TX_VPMON,
+ CS35L33_X_STATE | CS35L33_X_LOC, slot);
+ snd_soc_dapm_add_routes(dapm,
+ &cs35l33_vp_vbst_mon_route[0], 2);
+ dev_dbg(codec->dev, "VPMON enabled in slots %d", slot);
+ }
+
+ /* configure VBSTMON_TX_LOC */
+ if (slot_num == 5) {
+ regmap_update_bits(priv->regmap, CS35L33_TX_VBSTMON,
+ CS35L33_X_STATE | CS35L33_X_LOC, slot);
+ snd_soc_dapm_add_routes(dapm,
+ &cs35l33_vp_vbst_mon_route[2], 2);
+ dev_dbg(codec->dev,
+ "VBSTMON enabled in slots %d", slot);
+ }
+
+ /* Enable the relevant tx slot */
+ reg = CS35L33_TX_EN4 - (slot/8);
+ bit_pos = slot - ((slot / 8) * (8));
+ regmap_update_bits(priv->regmap, reg,
+ 1 << bit_pos, 1 << bit_pos);
+
+ tx_mask &= ~(1 << slot);
+ slot = ffs(tx_mask) - 1;
+ slot_num++;
+ }
+
+ return 0;
+}
+
+static int cs35l33_codec_set_sysclk(struct snd_soc_codec *codec,
+ int clk_id, int source, unsigned int freq, int dir)
+{
+ struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case CS35L33_MCLK_5644:
+ case CS35L33_MCLK_6:
+ case CS35L33_MCLK_6144:
+ regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL,
+ CS35L33_MCLKDIV2, 0);
+ cs35l33->mclk_int = freq;
+ break;
+ case CS35L33_MCLK_11289:
+ case CS35L33_MCLK_12:
+ case CS35L33_MCLK_12288:
+ regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL,
+ CS35L33_MCLKDIV2, CS35L33_MCLKDIV2);
+ cs35l33->mclk_int = freq/2;
+ break;
+ default:
+ cs35l33->mclk_int = 0;
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "external mclk freq=%d, internal mclk freq=%d\n",
+ freq, cs35l33->mclk_int);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l33_ops = {
+ .startup = cs35l33_pcm_startup,
+ .set_tristate = cs35l33_set_tristate,
+ .set_fmt = cs35l33_set_dai_fmt,
+ .hw_params = cs35l33_pcm_hw_params,
+ .set_tdm_slot = cs35l33_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver cs35l33_dai = {
+ .name = "cs35l33-dai",
+ .id = 0,
+ .playback = {
+ .stream_name = "CS35L33 Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = CS35L33_RATES,
+ .formats = CS35L33_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CS35L33 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CS35L33_RATES,
+ .formats = CS35L33_FORMATS,
+ },
+ .ops = &cs35l33_ops,
+ .symmetric_rates = 1,
+};
+
+static int cs35l33_set_hg_data(struct snd_soc_codec *codec,
+ struct cs35l33_pdata *pdata)
+{
+ struct cs35l33_hg *hg_config = &pdata->hg_config;
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+ struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec);
+
+ if (hg_config->enable_hg_algo) {
+ regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL,
+ CS35L33_MEM_DEPTH_MASK,
+ hg_config->mem_depth << CS35L33_MEM_DEPTH_SHIFT);
+ regmap_write(priv->regmap, CS35L33_HG_REL_RATE,
+ hg_config->release_rate);
+ regmap_update_bits(priv->regmap, CS35L33_HG_HEAD,
+ CS35L33_HD_RM_MASK,
+ hg_config->hd_rm << CS35L33_HD_RM_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL,
+ CS35L33_LDO_THLD_MASK,
+ hg_config->ldo_thld << CS35L33_LDO_THLD_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL,
+ CS35L33_LDO_DISABLE_MASK,
+ hg_config->ldo_path_disable <<
+ CS35L33_LDO_DISABLE_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_LDO_DEL,
+ CS35L33_LDO_ENTRY_DELAY_MASK,
+ hg_config->ldo_entry_delay <<
+ CS35L33_LDO_ENTRY_DELAY_SHIFT);
+ if (hg_config->vp_hg_auto) {
+ regmap_update_bits(priv->regmap, CS35L33_HG_EN,
+ CS35L33_VP_HG_AUTO_MASK,
+ CS35L33_VP_HG_AUTO_MASK);
+ snd_soc_dapm_add_routes(dapm, cs35l33_vphg_auto_route,
+ ARRAY_SIZE(cs35l33_vphg_auto_route));
+ }
+ regmap_update_bits(priv->regmap, CS35L33_HG_EN,
+ CS35L33_VP_HG_MASK,
+ hg_config->vp_hg << CS35L33_VP_HG_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_LDO_DEL,
+ CS35L33_VP_HG_RATE_MASK,
+ hg_config->vp_hg_rate << CS35L33_VP_HG_RATE_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_LDO_DEL,
+ CS35L33_VP_HG_VA_MASK,
+ hg_config->vp_hg_va << CS35L33_VP_HG_VA_SHIFT);
+ regmap_update_bits(priv->regmap, CS35L33_HG_EN,
+ CS35L33_CLASS_HG_EN_MASK, CS35L33_CLASS_HG_EN_MASK);
+ }
+ return 0;
+}
+
+static int cs35l33_set_bst_ipk(struct snd_soc_codec *codec, unsigned int bst)
+{
+ struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0, steps = 0;
+
+ /* Boost current in uA */
+ if (bst > 3600000 || bst < 1850000) {
+ dev_err(codec->dev, "Invalid boost current %d\n", bst);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (bst % 15625) {
+ dev_err(codec->dev, "Current not a multiple of 15625uA (%d)\n",
+ bst);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ while (bst > 1850000) {
+ bst -= 15625;
+ steps++;
+ }
+
+ regmap_write(cs35l33->regmap, CS35L33_BST_PEAK_CTL,
+ steps+0x70);
+
+err:
+ return ret;
+}
+
+static int cs35l33_probe(struct snd_soc_codec *codec)
+{
+ struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec);
+
+ cs35l33->codec = codec;
+ pm_runtime_get_sync(codec->dev);
+
+ regmap_update_bits(cs35l33->regmap, CS35L33_PROTECT_CTL,
+ CS35L33_ALIVE_WD_DIS, 0x8);
+ regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL2,
+ CS35L33_ALIVE_WD_DIS2,
+ CS35L33_ALIVE_WD_DIS2);
+
+ /* Set Platform Data */
+ regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL1,
+ CS35L33_BST_CTL_MASK, cs35l33->pdata.boost_ctl);
+ regmap_update_bits(cs35l33->regmap, CS35L33_CLASSD_CTL,
+ CS35L33_AMP_DRV_SEL_MASK,
+ cs35l33->pdata.amp_drv_sel << CS35L33_AMP_DRV_SEL_SHIFT);
+
+ if (cs35l33->pdata.boost_ipk)
+ cs35l33_set_bst_ipk(codec, cs35l33->pdata.boost_ipk);
+
+ if (cs35l33->enable_soft_ramp) {
+ snd_soc_update_bits(codec, CS35L33_DAC_CTL,
+ CS35L33_DIGSFT, CS35L33_DIGSFT);
+ snd_soc_update_bits(codec, CS35L33_DAC_CTL,
+ CS35L33_DSR_RATE, cs35l33->pdata.ramp_rate);
+ } else {
+ snd_soc_update_bits(codec, CS35L33_DAC_CTL,
+ CS35L33_DIGSFT, 0);
+ }
+
+ /* update IMON scaling rate if different from default of 0x8 */
+ if (cs35l33->pdata.imon_adc_scale != 0x8)
+ snd_soc_update_bits(codec, CS35L33_ADC_CTL,
+ CS35L33_IMON_SCALE, cs35l33->pdata.imon_adc_scale);
+
+ cs35l33_set_hg_data(codec, &(cs35l33->pdata));
+
+ /*
+ * unmask important interrupts that causes the chip to enter
+ * speaker safe mode and hence deserves user attention
+ */
+ regmap_update_bits(cs35l33->regmap, CS35L33_INT_MASK_1,
+ CS35L33_M_OTE | CS35L33_M_OTW | CS35L33_M_AMP_SHORT |
+ CS35L33_M_CAL_ERR, 0);
+
+ pm_runtime_put_sync(codec->dev);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_cs35l33 = {
+ .probe = cs35l33_probe,
+
+ .set_bias_level = cs35l33_set_bias_level,
+ .set_sysclk = cs35l33_codec_set_sysclk,
+
+ .dapm_widgets = cs35l33_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs35l33_dapm_widgets),
+ .dapm_routes = cs35l33_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cs35l33_audio_map),
+ .controls = cs35l33_snd_controls,
+ .num_controls = ARRAY_SIZE(cs35l33_snd_controls),
+
+ .idle_bias_off = true,
+};
+
+static const struct regmap_config cs35l33_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = CS35L33_MAX_REGISTER,
+ .reg_defaults = cs35l33_reg,
+ .num_reg_defaults = ARRAY_SIZE(cs35l33_reg),
+ .volatile_reg = cs35l33_volatile_register,
+ .readable_reg = cs35l33_readable_register,
+ .writeable_reg = cs35l33_writeable_register,
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_rw = true,
+};
+
+static int __maybe_unused cs35l33_runtime_resume(struct device *dev)
+{
+ struct cs35l33_private *cs35l33 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (cs35l33->reset_gpio)
+ gpiod_set_value_cansleep(cs35l33->reset_gpio, 0);
+
+ ret = regulator_bulk_enable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable core supplies: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(cs35l33->regmap, false);
+
+ if (cs35l33->reset_gpio)
+ gpiod_set_value_cansleep(cs35l33->reset_gpio, 1);
+
+ msleep(CS35L33_BOOT_DELAY);
+
+ ret = regcache_sync(cs35l33->regmap);
+ if (ret != 0) {
+ dev_err(dev, "Failed to restore register cache\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ regcache_cache_only(cs35l33->regmap, true);
+ regulator_bulk_disable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+
+ return ret;
+}
+
+static int __maybe_unused cs35l33_runtime_suspend(struct device *dev)
+{
+ struct cs35l33_private *cs35l33 = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ /* redo the calibration in next power up */
+ cs35l33->amp_cal = false;
+
+ regcache_cache_only(cs35l33->regmap, true);
+ regcache_mark_dirty(cs35l33->regmap);
+ regulator_bulk_disable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+
+ return 0;
+}
+
+static const struct dev_pm_ops cs35l33_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs35l33_runtime_suspend,
+ cs35l33_runtime_resume,
+ NULL)
+};
+
+static int cs35l33_get_hg_data(const struct device_node *np,
+ struct cs35l33_pdata *pdata)
+{
+ struct device_node *hg;
+ struct cs35l33_hg *hg_config = &pdata->hg_config;
+ u32 val32;
+
+ hg = of_get_child_by_name(np, "cirrus,hg-algo");
+ hg_config->enable_hg_algo = hg ? true : false;
+
+ if (hg_config->enable_hg_algo) {
+ if (of_property_read_u32(hg, "cirrus,mem-depth", &val32) >= 0)
+ hg_config->mem_depth = val32;
+ if (of_property_read_u32(hg, "cirrus,release-rate",
+ &val32) >= 0)
+ hg_config->release_rate = val32;
+ if (of_property_read_u32(hg, "cirrus,ldo-thld", &val32) >= 0)
+ hg_config->ldo_thld = val32;
+ if (of_property_read_u32(hg, "cirrus,ldo-path-disable",
+ &val32) >= 0)
+ hg_config->ldo_path_disable = val32;
+ if (of_property_read_u32(hg, "cirrus,ldo-entry-delay",
+ &val32) >= 0)
+ hg_config->ldo_entry_delay = val32;
+
+ hg_config->vp_hg_auto = of_property_read_bool(hg,
+ "cirrus,vp-hg-auto");
+
+ if (of_property_read_u32(hg, "cirrus,vp-hg", &val32) >= 0)
+ hg_config->vp_hg = val32;
+ if (of_property_read_u32(hg, "cirrus,vp-hg-rate", &val32) >= 0)
+ hg_config->vp_hg_rate = val32;
+ if (of_property_read_u32(hg, "cirrus,vp-hg-va", &val32) >= 0)
+ hg_config->vp_hg_va = val32;
+ }
+
+ of_node_put(hg);
+
+ return 0;
+}
+
+static irqreturn_t cs35l33_irq_thread(int irq, void *data)
+{
+ struct cs35l33_private *cs35l33 = data;
+ struct snd_soc_codec *codec = cs35l33->codec;
+ unsigned int sticky_val1, sticky_val2, current_val, mask1, mask2;
+
+ regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_2,
+ &sticky_val2);
+ regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1,
+ &sticky_val1);
+ regmap_read(cs35l33->regmap, CS35L33_INT_MASK_2, &mask2);
+ regmap_read(cs35l33->regmap, CS35L33_INT_MASK_1, &mask1);
+
+ /* Check to see if the unmasked bits are active,
+ * if not then exit.
+ */
+ if (!(sticky_val1 & ~mask1) && !(sticky_val2 & ~mask2))
+ return IRQ_NONE;
+
+ regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1,
+ &current_val);
+
+ /* handle the interrupts */
+
+ if (sticky_val1 & CS35L33_AMP_SHORT) {
+ dev_crit(codec->dev, "Amp short error\n");
+ if (!(current_val & CS35L33_AMP_SHORT)) {
+ dev_dbg(codec->dev,
+ "Amp short error release\n");
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL,
+ CS35L33_AMP_SHORT_RLS, 0);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL,
+ CS35L33_AMP_SHORT_RLS,
+ CS35L33_AMP_SHORT_RLS);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_AMP_SHORT_RLS,
+ 0);
+ }
+ }
+
+ if (sticky_val1 & CS35L33_CAL_ERR) {
+ dev_err(codec->dev, "Cal error\n");
+
+ /* redo the calibration in next power up */
+ cs35l33->amp_cal = false;
+
+ if (!(current_val & CS35L33_CAL_ERR)) {
+ dev_dbg(codec->dev, "Cal error release\n");
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS,
+ 0);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS,
+ CS35L33_CAL_ERR_RLS);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS,
+ 0);
+ }
+ }
+
+ if (sticky_val1 & CS35L33_OTE) {
+ dev_crit(codec->dev, "Over temperature error\n");
+ if (!(current_val & CS35L33_OTE)) {
+ dev_dbg(codec->dev,
+ "Over temperature error release\n");
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTE_RLS,
+ CS35L33_OTE_RLS);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0);
+ }
+ }
+
+ if (sticky_val1 & CS35L33_OTW) {
+ dev_err(codec->dev, "Over temperature warning\n");
+ if (!(current_val & CS35L33_OTW)) {
+ dev_dbg(codec->dev,
+ "Over temperature warning release\n");
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTW_RLS,
+ CS35L33_OTW_RLS);
+ regmap_update_bits(cs35l33->regmap,
+ CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0);
+ }
+ }
+ if (CS35L33_ALIVE_ERR & sticky_val1)
+ dev_err(codec->dev, "ERROR: ADSPCLK Interrupt\n");
+
+ if (CS35L33_MCLK_ERR & sticky_val1)
+ dev_err(codec->dev, "ERROR: MCLK Interrupt\n");
+
+ if (CS35L33_VMON_OVFL & sticky_val2)
+ dev_err(codec->dev,
+ "ERROR: VMON Overflow Interrupt\n");
+
+ if (CS35L33_IMON_OVFL & sticky_val2)
+ dev_err(codec->dev,
+ "ERROR: IMON Overflow Interrupt\n");
+
+ if (CS35L33_VPMON_OVFL & sticky_val2)
+ dev_err(codec->dev,
+ "ERROR: VPMON Overflow Interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static const char * const cs35l33_core_supplies[] = {
+ "VA",
+ "VP",
+};
+
+static int cs35l33_of_get_pdata(struct device *dev,
+ struct cs35l33_private *cs35l33)
+{
+ struct device_node *np = dev->of_node;
+ struct cs35l33_pdata *pdata = &cs35l33->pdata;
+ u32 val32;
+
+ if (!np)
+ return 0;
+
+ if (of_property_read_u32(np, "cirrus,boost-ctl", &val32) >= 0) {
+ pdata->boost_ctl = val32;
+ pdata->amp_drv_sel = 1;
+ }
+
+ if (of_property_read_u32(np, "cirrus,ramp-rate", &val32) >= 0) {
+ pdata->ramp_rate = val32;
+ cs35l33->enable_soft_ramp = true;
+ }
+
+ if (of_property_read_u32(np, "cirrus,boost-ipk", &val32) >= 0)
+ pdata->boost_ipk = val32;
+
+ if (of_property_read_u32(np, "cirrus,imon-adc-scale", &val32) >= 0) {
+ if ((val32 == 0x0) || (val32 == 0x7) || (val32 == 0x6))
+ pdata->imon_adc_scale = val32;
+ else
+ /* use default value */
+ pdata->imon_adc_scale = 0x8;
+ } else {
+ /* use default value */
+ pdata->imon_adc_scale = 0x8;
+ }
+
+ cs35l33_get_hg_data(np, pdata);
+
+ return 0;
+}
+
+static int cs35l33_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct cs35l33_private *cs35l33;
+ struct cs35l33_pdata *pdata = dev_get_platdata(&i2c_client->dev);
+ int ret, devid, i;
+ unsigned int reg;
+
+ cs35l33 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs35l33_private),
+ GFP_KERNEL);
+ if (!cs35l33)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c_client, cs35l33);
+ cs35l33->regmap = devm_regmap_init_i2c(i2c_client, &cs35l33_regmap);
+ if (IS_ERR(cs35l33->regmap)) {
+ ret = PTR_ERR(cs35l33->regmap);
+ dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(cs35l33->regmap, true);
+
+ for (i = 0; i < ARRAY_SIZE(cs35l33_core_supplies); i++)
+ cs35l33->core_supplies[i].supply
+ = cs35l33_core_supplies[i];
+ cs35l33->num_core_supplies = ARRAY_SIZE(cs35l33_core_supplies);
+
+ ret = devm_regulator_bulk_get(&i2c_client->dev,
+ cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+ if (ret != 0) {
+ dev_err(&i2c_client->dev,
+ "Failed to request core supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (pdata) {
+ cs35l33->pdata = *pdata;
+ } else {
+ cs35l33_of_get_pdata(&i2c_client->dev, cs35l33);
+ pdata = &cs35l33->pdata;
+ }
+
+ ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL,
+ cs35l33_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ "cs35l33", cs35l33);
+ if (ret != 0)
+ dev_warn(&i2c_client->dev, "Failed to request IRQ: %d\n", ret);
+
+ /* We could issue !RST or skip it based on AMP topology */
+ cs35l33->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
+ "reset-gpios", GPIOD_OUT_HIGH);
+ if (IS_ERR(cs35l33->reset_gpio)) {
+ dev_err(&i2c_client->dev, "%s ERROR: Can't get reset GPIO\n",
+ __func__);
+ return PTR_ERR(cs35l33->reset_gpio);
+ }
+
+ ret = regulator_bulk_enable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+ if (ret != 0) {
+ dev_err(&i2c_client->dev,
+ "Failed to enable core supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (cs35l33->reset_gpio)
+ gpiod_set_value_cansleep(cs35l33->reset_gpio, 1);
+
+ msleep(CS35L33_BOOT_DELAY);
+ regcache_cache_only(cs35l33->regmap, false);
+
+ /* initialize codec */
+ ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_AB, &reg);
+ devid = (reg & 0xFF) << 12;
+ ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_CD, &reg);
+ devid |= (reg & 0xFF) << 4;
+ ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_E, &reg);
+ devid |= (reg & 0xF0) >> 4;
+
+ if (devid != CS35L33_CHIP_ID) {
+ dev_err(&i2c_client->dev,
+ "CS35L33 Device ID (%X). Expected ID %X\n",
+ devid, CS35L33_CHIP_ID);
+ goto err_enable;
+ }
+
+ ret = regmap_read(cs35l33->regmap, CS35L33_REV_ID, &reg);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Get Revision ID failed\n");
+ goto err_enable;
+ }
+
+ dev_info(&i2c_client->dev,
+ "Cirrus Logic CS35L33, Revision: %02X\n", reg & 0xFF);
+
+ ret = regmap_register_patch(cs35l33->regmap,
+ cs35l33_patch, ARRAY_SIZE(cs35l33_patch));
+ if (ret < 0) {
+ dev_err(&i2c_client->dev,
+ "Error in applying regmap patch: %d\n", ret);
+ goto err_enable;
+ }
+
+ /* disable mclk and tdm */
+ regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL,
+ CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM,
+ CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM);
+
+ pm_runtime_set_autosuspend_delay(&i2c_client->dev, 100);
+ pm_runtime_use_autosuspend(&i2c_client->dev);
+ pm_runtime_set_active(&i2c_client->dev);
+ pm_runtime_enable(&i2c_client->dev);
+
+ ret = snd_soc_register_codec(&i2c_client->dev,
+ &soc_codec_dev_cs35l33, &cs35l33_dai, 1);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "%s: Register codec failed\n",
+ __func__);
+ goto err_enable;
+ }
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+
+ return ret;
+}
+
+static int cs35l33_i2c_remove(struct i2c_client *client)
+{
+ struct cs35l33_private *cs35l33 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+
+ if (cs35l33->reset_gpio)
+ gpiod_set_value_cansleep(cs35l33->reset_gpio, 0);
+
+ pm_runtime_disable(&client->dev);
+ regulator_bulk_disable(cs35l33->num_core_supplies,
+ cs35l33->core_supplies);
+
+ return 0;
+}
+
+static const struct of_device_id cs35l33_of_match[] = {
+ { .compatible = "cirrus,cs35l33", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cs35l33_of_match);
+
+static const struct i2c_device_id cs35l33_id[] = {
+ {"cs35l33", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, cs35l33_id);
+
+static struct i2c_driver cs35l33_i2c_driver = {
+ .driver = {
+ .name = "cs35l33",
+ .pm = &cs35l33_pm_ops,
+ .of_match_table = cs35l33_of_match,
+
+ },
+ .id_table = cs35l33_id,
+ .probe = cs35l33_i2c_probe,
+ .remove = cs35l33_i2c_remove,
+
+};
+module_i2c_driver(cs35l33_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L33 driver");
+MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <paul.handrigan@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs35l33.h b/sound/soc/codecs/cs35l33.h
new file mode 100644
index 000000000000..c045737d1a5f
--- /dev/null
+++ b/sound/soc/codecs/cs35l33.h
@@ -0,0 +1,221 @@
+/*
+ * cs35l33.h -- CS35L33 ALSA SoC audio driver
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ *
+ * Author: Paul Handrigan <paul.handrigan@cirrus.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __CS35L33_H__
+#define __CS35L33_H__
+
+#define CS35L33_CHIP_ID 0x00035A33
+#define CS35L33_DEVID_AB 0x01 /* Device ID A & B [RO] */
+#define CS35L33_DEVID_CD 0x02 /* Device ID C & D [RO] */
+#define CS35L33_DEVID_E 0x03 /* Device ID E [RO] */
+#define CS35L33_FAB_ID 0x04 /* Fab ID [RO] */
+#define CS35L33_REV_ID 0x05 /* Revision ID [RO] */
+#define CS35L33_PWRCTL1 0x06 /* Power Ctl 1 */
+#define CS35L33_PWRCTL2 0x07 /* Power Ctl 2 */
+#define CS35L33_CLK_CTL 0x08 /* Clock Ctl */
+#define CS35L33_BST_PEAK_CTL 0x09 /* Max Current for Boost */
+#define CS35L33_PROTECT_CTL 0x0A /* Amp Protection Parameters */
+#define CS35L33_BST_CTL1 0x0B /* Boost Converter CTL1 */
+#define CS35L33_BST_CTL2 0x0C /* Boost Converter CTL2 */
+#define CS35L33_ADSP_CTL 0x0D /* Serial Port Control */
+#define CS35L33_ADC_CTL 0x0E /* ADC Control */
+#define CS35L33_DAC_CTL 0x0F /* DAC Control */
+#define CS35L33_DIG_VOL_CTL 0x10 /* Digital Volume CTL */
+#define CS35L33_CLASSD_CTL 0x11 /* Class D Amp CTL */
+#define CS35L33_AMP_CTL 0x12 /* Amp Gain/Protecton Release CTL */
+#define CS35L33_INT_MASK_1 0x13 /* Interrupt Mask 1 */
+#define CS35L33_INT_MASK_2 0x14 /* Interrupt Mask 2 */
+#define CS35L33_INT_STATUS_1 0x15 /* Interrupt Status 1 [RO] */
+#define CS35L33_INT_STATUS_2 0x16 /* Interrupt Status 2 [RO] */
+#define CS35L33_DIAG_LOCK 0x17 /* Diagnostic Mode Register Lock */
+#define CS35L33_DIAG_CTRL_1 0x18 /* Diagnostic Mode Register Control */
+#define CS35L33_DIAG_CTRL_2 0x19 /* Diagnostic Mode Register Control 2 */
+#define CS35L33_HG_MEMLDO_CTL 0x23 /* H/G Memory/LDO CTL */
+#define CS35L33_HG_REL_RATE 0x24 /* H/G Release Rate */
+#define CS35L33_LDO_DEL 0x25 /* LDO Entry Delay/VPhg Control 1 */
+#define CS35L33_HG_HEAD 0x29 /* H/G Headroom */
+#define CS35L33_HG_EN 0x2A /* H/G Enable/VPhg CNT2 */
+#define CS35L33_TX_VMON 0x2D /* TDM TX Control 1 (VMON) */
+#define CS35L33_TX_IMON 0x2E /* TDM TX Control 2 (IMON) */
+#define CS35L33_TX_VPMON 0x2F /* TDM TX Control 3 (VPMON) */
+#define CS35L33_TX_VBSTMON 0x30 /* TDM TX Control 4 (VBSTMON) */
+#define CS35L33_TX_FLAG 0x31 /* TDM TX Control 5 (FLAG) */
+#define CS35L33_TX_EN1 0x32 /* TDM TX Enable 1 */
+#define CS35L33_TX_EN2 0x33 /* TDM TX Enable 2 */
+#define CS35L33_TX_EN3 0x34 /* TDM TX Enable 3 */
+#define CS35L33_TX_EN4 0x35 /* TDM TX Enable 4 */
+#define CS35L33_RX_AUD 0x36 /* TDM RX Control 1 */
+#define CS35L33_RX_SPLY 0x37 /* TDM RX Control 2 */
+#define CS35L33_RX_ALIVE 0x38 /* TDM RX Control 3 */
+#define CS35L33_BST_CTL4 0x39 /* Boost Converter Control 4 */
+#define CS35L33_HG_STATUS 0x3F /* H/G Status */
+#define CS35L33_MAX_REGISTER 0x59
+
+#define CS35L33_MCLK_5644 5644800
+#define CS35L33_MCLK_6144 6144000
+#define CS35L33_MCLK_6 6000000
+#define CS35L33_MCLK_11289 11289600
+#define CS35L33_MCLK_12 12000000
+#define CS35L33_MCLK_12288 12288000
+
+/* CS35L33_PWRCTL1 */
+#define CS35L33_PDN_AMP (1 << 7)
+#define CS35L33_PDN_BST (1 << 2)
+#define CS35L33_PDN_ALL 1
+
+/* CS35L33_PWRCTL2 */
+#define CS35L33_PDN_VMON_SHIFT 7
+#define CS35L33_PDN_VMON (1 << CS35L33_PDN_VMON_SHIFT)
+#define CS35L33_PDN_IMON_SHIFT 6
+#define CS35L33_PDN_IMON (1 << CS35L33_PDN_IMON_SHIFT)
+#define CS35L33_PDN_VPMON_SHIFT 5
+#define CS35L33_PDN_VPMON (1 << CS35L33_PDN_VPMON_SHIFT)
+#define CS35L33_PDN_VBSTMON_SHIFT 4
+#define CS35L33_PDN_VBSTMON (1 << CS35L33_PDN_VBSTMON_SHIFT)
+#define CS35L33_SDOUT_3ST_I2S_SHIFT 3
+#define CS35L33_SDOUT_3ST_I2S (1 << CS35L33_SDOUT_3ST_I2S_SHIFT)
+#define CS35L33_PDN_SDIN_SHIFT 2
+#define CS35L33_PDN_SDIN (1 << CS35L33_PDN_SDIN_SHIFT)
+#define CS35L33_PDN_TDM_SHIFT 1
+#define CS35L33_PDN_TDM (1 << CS35L33_PDN_TDM_SHIFT)
+
+/* CS35L33_CLK_CTL */
+#define CS35L33_MCLKDIS (1 << 7)
+#define CS35L33_MCLKDIV2 (1 << 6)
+#define CS35L33_SDOUT_3ST_TDM (1 << 5)
+#define CS35L33_INT_FS_RATE (1 << 4)
+#define CS35L33_ADSP_FS 0xF
+
+/* CS35L33_PROTECT_CTL */
+#define CS35L33_ALIVE_WD_DIS (3 << 2)
+
+/* CS35L33_BST_CTL1 */
+#define CS35L33_BST_CTL_SRC (1 << 6)
+#define CS35L33_BST_CTL_SHIFT (1 << 5)
+#define CS35L33_BST_CTL_MASK 0x3F
+
+/* CS35L33_BST_CTL2 */
+#define CS35L33_TDM_WD_SEL (1 << 4)
+#define CS35L33_ALIVE_WD_DIS2 (1 << 3)
+#define CS35L33_VBST_SR_STEP 0x3
+
+/* CS35L33_ADSP_CTL */
+#define CS35L33_ADSP_DRIVE (1 << 7)
+#define CS35L33_MS_MASK (1 << 6)
+#define CS35L33_SDIN_LOC (3 << 4)
+#define CS35L33_ALIVE_RATE 0x3
+
+/* CS35L33_ADC_CTL */
+#define CS35L33_INV_VMON (1 << 7)
+#define CS35L33_INV_IMON (1 << 6)
+#define CS35L33_ADC_NOTCH_DIS (1 << 5)
+#define CS35L33_IMON_SCALE 0xF
+
+/* CS35L33_DAC_CTL */
+#define CS35L33_INV_DAC (1 << 7)
+#define CS35L33_DAC_NOTCH_DIS (1 << 5)
+#define CS35L33_DIGSFT (1 << 4)
+#define CS35L33_DSR_RATE 0xF
+
+/* CS35L33_CLASSD_CTL */
+#define CS35L33_AMP_SD (1 << 6)
+#define CS35L33_AMP_DRV_SEL_SRC (1 << 5)
+#define CS35L33_AMP_DRV_SEL_MASK 0x10
+#define CS35L33_AMP_DRV_SEL_SHIFT 4
+#define CS35L33_AMP_CAL (1 << 3)
+#define CS35L33_GAIN_CHG_ZC_MASK 0x04
+#define CS35L33_GAIN_CHG_ZC_SHIFT 2
+#define CS35L33_CLASS_D_CTL_MASK 0x3F
+
+/* CS35L33_AMP_CTL */
+#define CS35L33_AMP_GAIN 0xF0
+#define CS35L33_CAL_ERR_RLS (1 << 3)
+#define CS35L33_AMP_SHORT_RLS (1 << 2)
+#define CS35L33_OTW_RLS (1 << 1)
+#define CS35L33_OTE_RLS 1
+
+/* CS35L33_INT_MASK_1 */
+#define CS35L33_M_CAL_ERR_SHIFT 6
+#define CS35L33_M_CAL_ERR (1 << CS35L33_M_CAL_ERR_SHIFT)
+#define CS35L33_M_ALIVE_ERR_SHIFT 5
+#define CS35L33_M_ALIVE_ERR (1 << CS35L33_M_ALIVE_ERR_SHIFT)
+#define CS35L33_M_AMP_SHORT_SHIFT 2
+#define CS35L33_M_AMP_SHORT (1 << CS35L33_M_AMP_SHORT_SHIFT)
+#define CS35L33_M_OTW_SHIFT 1
+#define CS35L33_M_OTW (1 << CS35L33_M_OTW_SHIFT)
+#define CS35L33_M_OTE_SHIFT 0
+#define CS35L33_M_OTE (1 << CS35L33_M_OTE_SHIFT)
+
+/* CS35L33_INT_STATUS_1 */
+#define CS35L33_CAL_ERR (1 << 6)
+#define CS35L33_ALIVE_ERR (1 << 5)
+#define CS35L33_ADSPCLK_ERR (1 << 4)
+#define CS35L33_MCLK_ERR (1 << 3)
+#define CS35L33_AMP_SHORT (1 << 2)
+#define CS35L33_OTW (1 << 1)
+#define CS35L33_OTE (1 << 0)
+
+/* CS35L33_INT_STATUS_2 */
+#define CS35L33_VMON_OVFL (1 << 7)
+#define CS35L33_IMON_OVFL (1 << 6)
+#define CS35L33_VPMON_OVFL (1 << 5)
+#define CS35L33_VBSTMON_OVFL (1 << 4)
+#define CS35L33_PDN_DONE 1
+
+/* CS35L33_BST_CTL4 */
+#define CS35L33_BST_RGS 0x70
+#define CS35L33_BST_COEFF3 0xF
+
+/* CS35L33_HG_MEMLDO_CTL */
+#define CS35L33_MEM_DEPTH_SHIFT 5
+#define CS35L33_MEM_DEPTH_MASK (0x3 << CS35L33_MEM_DEPTH_SHIFT)
+#define CS35L33_LDO_THLD_SHIFT 1
+#define CS35L33_LDO_THLD_MASK (0xF << CS35L33_LDO_THLD_SHIFT)
+#define CS35L33_LDO_DISABLE_SHIFT 0
+#define CS35L33_LDO_DISABLE_MASK (0x1 << CS35L33_LDO_DISABLE_SHIFT)
+
+/* CS35L33_LDO_DEL */
+#define CS35L33_VP_HG_VA_SHIFT 5
+#define CS35L33_VP_HG_VA_MASK (0x7 << CS35L33_VP_HG_VA_SHIFT)
+#define CS35L33_LDO_ENTRY_DELAY_SHIFT 2
+#define CS35L33_LDO_ENTRY_DELAY_MASK (0x7 << CS35L33_LDO_ENTRY_DELAY_SHIFT)
+#define CS35L33_VP_HG_RATE_SHIFT 0
+#define CS35L33_VP_HG_RATE_MASK (0x3 << CS35L33_VP_HG_RATE_SHIFT)
+
+/* CS35L33_HG_HEAD */
+#define CS35L33_HD_RM_SHIFT 0
+#define CS35L33_HD_RM_MASK (0x7F << CS35L33_HD_RM_SHIFT)
+
+/* CS35L33_HG_EN */
+#define CS35L33_CLASS_HG_ENA_SHIFT 7
+#define CS35L33_CLASS_HG_EN_MASK (0x1 << CS35L33_CLASS_HG_ENA_SHIFT)
+#define CS35L33_VP_HG_AUTO_SHIFT 6
+#define CS35L33_VP_HG_AUTO_MASK (0x1 << 6)
+#define CS35L33_VP_HG_SHIFT 0
+#define CS35L33_VP_HG_MASK (0x1F << CS35L33_VP_HG_SHIFT)
+
+#define CS35L33_RATES (SNDRV_PCM_RATE_8000_48000)
+#define CS35L33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/* CS35L33_{RX,TX}_X */
+#define CS35L33_X_STATE_SHIFT 7
+#define CS35L33_X_STATE (1 << CS35L33_X_STATE_SHIFT)
+#define CS35L33_X_LOC_SHIFT 0
+#define CS35L33_X_LOC (0x1F << CS35L33_X_LOC_SHIFT)
+
+/* CS35L33_RX_AUD */
+#define CS35L33_AUDIN_RX_DEPTH_SHIFT 5
+#define CS35L33_AUDIN_RX_DEPTH (0x7 << CS35L33_AUDIN_RX_DEPTH_SHIFT)
+
+#endif
diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c
index 5ec5a682d186..954a4f5d3338 100644
--- a/sound/soc/codecs/cs47l24.c
+++ b/sound/soc/codecs/cs47l24.c
@@ -359,6 +359,11 @@ SND_SOC_DAPM_INPUT("IN2R"),
SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"),
SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"),
+SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"),
+
+SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0,
+ &arizona_voice_trigger_switch[2]),
+
SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT,
0, NULL, 0, arizona_in_ev,
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD |
@@ -899,10 +904,16 @@ static const struct snd_soc_dapm_route cs47l24_dapm_routes[] = {
{ "MICSUPP", NULL, "SYSCLK" },
+ { "DRC1 Signal Activity", NULL, "SYSCLK" },
+ { "DRC2 Signal Activity", NULL, "SYSCLK" },
{ "DRC1 Signal Activity", NULL, "DRC1L" },
{ "DRC1 Signal Activity", NULL, "DRC1R" },
{ "DRC2 Signal Activity", NULL, "DRC2L" },
{ "DRC2 Signal Activity", NULL, "DRC2R" },
+
+ { "DSP Voice Trigger", NULL, "SYSCLK" },
+ { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" },
+ { "DSP3 Voice Trigger", "Switch", "DSP3" },
};
static int cs47l24_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
@@ -1067,6 +1078,7 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data)
{
struct cs47l24_priv *priv = data;
struct arizona *arizona = priv->core.arizona;
+ struct arizona_voice_trigger_info info;
int serviced = 0;
int i, ret;
@@ -1074,6 +1086,12 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data)
ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]);
if (ret != -ENODEV)
serviced++;
+ if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) {
+ info.core = i;
+ arizona_call_notifiers(arizona,
+ ARIZONA_NOTIFY_VOICE_TRIGGER,
+ &info);
+ }
}
if (!serviced) {
@@ -1096,6 +1114,7 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec)
arizona_init_spk(codec);
arizona_init_gpio(codec);
arizona_init_mono(codec);
+ arizona_init_notifiers(codec);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", cs47l24_adsp2_irq,
diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c
new file mode 100644
index 000000000000..2c0d9c430a8c
--- /dev/null
+++ b/sound/soc/codecs/cs53l30.c
@@ -0,0 +1,1143 @@
+/*
+ * cs53l30.c -- CS53l30 ALSA Soc Audio driver
+ *
+ * Copyright 2015 Cirrus Logic, Inc.
+ *
+ * Authors: Paul Handrigan <Paul.Handrigan@cirrus.com>,
+ * Tim Howe <Tim.Howe@cirrus.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "cs53l30.h"
+
+#define CS53L30_NUM_SUPPLIES 2
+static const char *const cs53l30_supply_names[CS53L30_NUM_SUPPLIES] = {
+ "VA",
+ "VP",
+};
+
+struct cs53l30_private {
+ struct regulator_bulk_data supplies[CS53L30_NUM_SUPPLIES];
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *mute_gpio;
+ struct clk *mclk;
+ bool use_sdout2;
+ u32 mclk_rate;
+};
+
+static const struct reg_default cs53l30_reg_defaults[] = {
+ { CS53L30_PWRCTL, CS53L30_PWRCTL_DEFAULT },
+ { CS53L30_MCLKCTL, CS53L30_MCLKCTL_DEFAULT },
+ { CS53L30_INT_SR_CTL, CS53L30_INT_SR_CTL_DEFAULT },
+ { CS53L30_MICBIAS_CTL, CS53L30_MICBIAS_CTL_DEFAULT },
+ { CS53L30_ASPCFG_CTL, CS53L30_ASPCFG_CTL_DEFAULT },
+ { CS53L30_ASP_CTL1, CS53L30_ASP_CTL1_DEFAULT },
+ { CS53L30_ASP_TDMTX_CTL1, CS53L30_ASP_TDMTX_CTLx_DEFAULT },
+ { CS53L30_ASP_TDMTX_CTL2, CS53L30_ASP_TDMTX_CTLx_DEFAULT },
+ { CS53L30_ASP_TDMTX_CTL3, CS53L30_ASP_TDMTX_CTLx_DEFAULT },
+ { CS53L30_ASP_TDMTX_CTL4, CS53L30_ASP_TDMTX_CTLx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN1, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN2, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN3, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN4, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN5, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_TDMTX_EN6, CS53L30_ASP_TDMTX_ENx_DEFAULT },
+ { CS53L30_ASP_CTL2, CS53L30_ASP_CTL2_DEFAULT },
+ { CS53L30_SFT_RAMP, CS53L30_SFT_RMP_DEFAULT },
+ { CS53L30_LRCK_CTL1, CS53L30_LRCK_CTLx_DEFAULT },
+ { CS53L30_LRCK_CTL2, CS53L30_LRCK_CTLx_DEFAULT },
+ { CS53L30_MUTEP_CTL1, CS53L30_MUTEP_CTL1_DEFAULT },
+ { CS53L30_MUTEP_CTL2, CS53L30_MUTEP_CTL2_DEFAULT },
+ { CS53L30_INBIAS_CTL1, CS53L30_INBIAS_CTL1_DEFAULT },
+ { CS53L30_INBIAS_CTL2, CS53L30_INBIAS_CTL2_DEFAULT },
+ { CS53L30_DMIC1_STR_CTL, CS53L30_DMIC1_STR_CTL_DEFAULT },
+ { CS53L30_DMIC2_STR_CTL, CS53L30_DMIC2_STR_CTL_DEFAULT },
+ { CS53L30_ADCDMIC1_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT },
+ { CS53L30_ADCDMIC1_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT },
+ { CS53L30_ADC1_CTL3, CS53L30_ADCx_CTL3_DEFAULT },
+ { CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT },
+ { CS53L30_ADC1A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT },
+ { CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT },
+ { CS53L30_ADC1A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT },
+ { CS53L30_ADC1B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT },
+ { CS53L30_ADCDMIC2_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT },
+ { CS53L30_ADCDMIC2_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT },
+ { CS53L30_ADC2_CTL3, CS53L30_ADCx_CTL3_DEFAULT },
+ { CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT },
+ { CS53L30_ADC2A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT },
+ { CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT },
+ { CS53L30_ADC2A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT },
+ { CS53L30_ADC2B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT },
+ { CS53L30_INT_MASK, CS53L30_DEVICE_INT_MASK },
+};
+
+static bool cs53l30_volatile_register(struct device *dev, unsigned int reg)
+{
+ if (reg == CS53L30_IS)
+ return true;
+ else
+ return false;
+}
+
+static bool cs53l30_writeable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS53L30_DEVID_AB:
+ case CS53L30_DEVID_CD:
+ case CS53L30_DEVID_E:
+ case CS53L30_REVID:
+ case CS53L30_IS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool cs53l30_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS53L30_DEVID_AB:
+ case CS53L30_DEVID_CD:
+ case CS53L30_DEVID_E:
+ case CS53L30_REVID:
+ case CS53L30_PWRCTL:
+ case CS53L30_MCLKCTL:
+ case CS53L30_INT_SR_CTL:
+ case CS53L30_MICBIAS_CTL:
+ case CS53L30_ASPCFG_CTL:
+ case CS53L30_ASP_CTL1:
+ case CS53L30_ASP_TDMTX_CTL1:
+ case CS53L30_ASP_TDMTX_CTL2:
+ case CS53L30_ASP_TDMTX_CTL3:
+ case CS53L30_ASP_TDMTX_CTL4:
+ case CS53L30_ASP_TDMTX_EN1:
+ case CS53L30_ASP_TDMTX_EN2:
+ case CS53L30_ASP_TDMTX_EN3:
+ case CS53L30_ASP_TDMTX_EN4:
+ case CS53L30_ASP_TDMTX_EN5:
+ case CS53L30_ASP_TDMTX_EN6:
+ case CS53L30_ASP_CTL2:
+ case CS53L30_SFT_RAMP:
+ case CS53L30_LRCK_CTL1:
+ case CS53L30_LRCK_CTL2:
+ case CS53L30_MUTEP_CTL1:
+ case CS53L30_MUTEP_CTL2:
+ case CS53L30_INBIAS_CTL1:
+ case CS53L30_INBIAS_CTL2:
+ case CS53L30_DMIC1_STR_CTL:
+ case CS53L30_DMIC2_STR_CTL:
+ case CS53L30_ADCDMIC1_CTL1:
+ case CS53L30_ADCDMIC1_CTL2:
+ case CS53L30_ADC1_CTL3:
+ case CS53L30_ADC1_NG_CTL:
+ case CS53L30_ADC1A_AFE_CTL:
+ case CS53L30_ADC1B_AFE_CTL:
+ case CS53L30_ADC1A_DIG_VOL:
+ case CS53L30_ADC1B_DIG_VOL:
+ case CS53L30_ADCDMIC2_CTL1:
+ case CS53L30_ADCDMIC2_CTL2:
+ case CS53L30_ADC2_CTL3:
+ case CS53L30_ADC2_NG_CTL:
+ case CS53L30_ADC2A_AFE_CTL:
+ case CS53L30_ADC2B_AFE_CTL:
+ case CS53L30_ADC2A_DIG_VOL:
+ case CS53L30_ADC2B_DIG_VOL:
+ case CS53L30_INT_MASK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2000, 0);
+static DECLARE_TLV_DB_SCALE(adc_ng_boost_tlv, 0, 3000, 0);
+static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0);
+static DECLARE_TLV_DB_SCALE(dig_tlv, -9600, 100, 1);
+static DECLARE_TLV_DB_SCALE(pga_preamp_tlv, 0, 10000, 0);
+
+static const char * const input1_sel_text[] = {
+ "DMIC1 On AB In",
+ "DMIC1 On A In",
+ "DMIC1 On B In",
+ "ADC1 On AB In",
+ "ADC1 On A In",
+ "ADC1 On B In",
+ "DMIC1 Off ADC1 Off",
+};
+
+static unsigned int const input1_sel_values[] = {
+ CS53L30_CH_TYPE,
+ CS53L30_ADCxB_PDN | CS53L30_CH_TYPE,
+ CS53L30_ADCxA_PDN | CS53L30_CH_TYPE,
+ CS53L30_DMICx_PDN,
+ CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN,
+ CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN,
+ CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN,
+};
+
+static const char * const input2_sel_text[] = {
+ "DMIC2 On AB In",
+ "DMIC2 On A In",
+ "DMIC2 On B In",
+ "ADC2 On AB In",
+ "ADC2 On A In",
+ "ADC2 On B In",
+ "DMIC2 Off ADC2 Off",
+};
+
+static unsigned int const input2_sel_values[] = {
+ 0x0,
+ CS53L30_ADCxB_PDN,
+ CS53L30_ADCxA_PDN,
+ CS53L30_DMICx_PDN,
+ CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN,
+ CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN,
+ CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN,
+};
+
+static const char * const input1_route_sel_text[] = {
+ "ADC1_SEL", "DMIC1_SEL",
+};
+
+static const struct soc_enum input1_route_sel_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, CS53L30_CH_TYPE_SHIFT,
+ ARRAY_SIZE(input1_route_sel_text),
+ input1_route_sel_text);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(input1_sel_enum, CS53L30_ADCDMIC1_CTL1, 0,
+ CS53L30_ADCDMICx_PDN_MASK, input1_sel_text,
+ input1_sel_values);
+
+static const struct snd_kcontrol_new input1_route_sel_mux =
+ SOC_DAPM_ENUM("Input 1 Route", input1_route_sel_enum);
+
+static const char * const input2_route_sel_text[] = {
+ "ADC2_SEL", "DMIC2_SEL",
+};
+
+/* Note: CS53L30_ADCDMIC1_CTL1 CH_TYPE controls inputs 1 and 2 */
+static const struct soc_enum input2_route_sel_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0,
+ ARRAY_SIZE(input2_route_sel_text),
+ input2_route_sel_text);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(input2_sel_enum, CS53L30_ADCDMIC2_CTL1, 0,
+ CS53L30_ADCDMICx_PDN_MASK, input2_sel_text,
+ input2_sel_values);
+
+static const struct snd_kcontrol_new input2_route_sel_mux =
+ SOC_DAPM_ENUM("Input 2 Route", input2_route_sel_enum);
+
+/*
+ * TB = 6144*(MCLK(int) scaling factor)/MCLK(internal)
+ * TB - Time base
+ * NOTE: If MCLK_INT_SCALE = 0, then TB=1
+ */
+static const char * const cs53l30_ng_delay_text[] = {
+ "TB*50ms", "TB*100ms", "TB*150ms", "TB*200ms",
+};
+
+static const struct soc_enum adc1_ng_delay_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT,
+ ARRAY_SIZE(cs53l30_ng_delay_text),
+ cs53l30_ng_delay_text);
+
+static const struct soc_enum adc2_ng_delay_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT,
+ ARRAY_SIZE(cs53l30_ng_delay_text),
+ cs53l30_ng_delay_text);
+
+/* The noise gate threshold selected will depend on NG Boost */
+static const char * const cs53l30_ng_thres_text[] = {
+ "-64dB/-34dB", "-66dB/-36dB", "-70dB/-40dB", "-73dB/-43dB",
+ "-76dB/-46dB", "-82dB/-52dB", "-58dB", "-64dB",
+};
+
+static const struct soc_enum adc1_ng_thres_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT,
+ ARRAY_SIZE(cs53l30_ng_thres_text),
+ cs53l30_ng_thres_text);
+
+static const struct soc_enum adc2_ng_thres_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT,
+ ARRAY_SIZE(cs53l30_ng_thres_text),
+ cs53l30_ng_thres_text);
+
+/* Corner frequencies are with an Fs of 48kHz. */
+static const char * const hpf_corner_freq_text[] = {
+ "1.86Hz", "120Hz", "235Hz", "466Hz",
+};
+
+static const struct soc_enum adc1_hpf_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC1_CTL3, CS53L30_ADCx_HPF_CF_SHIFT,
+ ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text);
+
+static const struct soc_enum adc2_hpf_enum =
+ SOC_ENUM_SINGLE(CS53L30_ADC2_CTL3, CS53L30_ADCx_HPF_CF_SHIFT,
+ ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text);
+
+static const struct snd_kcontrol_new cs53l30_snd_controls[] = {
+ SOC_SINGLE("Digital Soft-Ramp Switch", CS53L30_SFT_RAMP,
+ CS53L30_DIGSFT_SHIFT, 1, 0),
+ SOC_SINGLE("ADC1 Noise Gate Ganging Switch", CS53L30_ADC1_CTL3,
+ CS53L30_ADCx_NG_ALL_SHIFT, 1, 0),
+ SOC_SINGLE("ADC2 Noise Gate Ganging Switch", CS53L30_ADC2_CTL3,
+ CS53L30_ADCx_NG_ALL_SHIFT, 1, 0),
+ SOC_SINGLE("ADC1A Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL,
+ CS53L30_ADCxA_NG_SHIFT, 1, 0),
+ SOC_SINGLE("ADC1B Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL,
+ CS53L30_ADCxB_NG_SHIFT, 1, 0),
+ SOC_SINGLE("ADC2A Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL,
+ CS53L30_ADCxA_NG_SHIFT, 1, 0),
+ SOC_SINGLE("ADC2B Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL,
+ CS53L30_ADCxB_NG_SHIFT, 1, 0),
+ SOC_SINGLE("ADC1 Notch Filter Switch", CS53L30_ADCDMIC1_CTL2,
+ CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1),
+ SOC_SINGLE("ADC2 Notch Filter Switch", CS53L30_ADCDMIC2_CTL2,
+ CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1),
+ SOC_SINGLE("ADC1A Invert Switch", CS53L30_ADCDMIC1_CTL2,
+ CS53L30_ADCxA_INV_SHIFT, 1, 0),
+ SOC_SINGLE("ADC1B Invert Switch", CS53L30_ADCDMIC1_CTL2,
+ CS53L30_ADCxB_INV_SHIFT, 1, 0),
+ SOC_SINGLE("ADC2A Invert Switch", CS53L30_ADCDMIC2_CTL2,
+ CS53L30_ADCxA_INV_SHIFT, 1, 0),
+ SOC_SINGLE("ADC2B Invert Switch", CS53L30_ADCDMIC2_CTL2,
+ CS53L30_ADCxB_INV_SHIFT, 1, 0),
+
+ SOC_SINGLE_TLV("ADC1A Digital Boost Volume", CS53L30_ADCDMIC1_CTL2,
+ CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv),
+ SOC_SINGLE_TLV("ADC1B Digital Boost Volume", CS53L30_ADCDMIC1_CTL2,
+ CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv),
+ SOC_SINGLE_TLV("ADC2A Digital Boost Volume", CS53L30_ADCDMIC2_CTL2,
+ CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv),
+ SOC_SINGLE_TLV("ADC2B Digital Boost Volume", CS53L30_ADCDMIC2_CTL2,
+ CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv),
+ SOC_SINGLE_TLV("ADC1 NG Boost Volume", CS53L30_ADC1_NG_CTL,
+ CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv),
+ SOC_SINGLE_TLV("ADC2 NG Boost Volume", CS53L30_ADC2_NG_CTL,
+ CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv),
+
+ SOC_DOUBLE_R_TLV("ADC1 Preamplifier Volume", CS53L30_ADC1A_AFE_CTL,
+ CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT,
+ 2, 0, pga_preamp_tlv),
+ SOC_DOUBLE_R_TLV("ADC2 Preamplifier Volume", CS53L30_ADC2A_AFE_CTL,
+ CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT,
+ 2, 0, pga_preamp_tlv),
+
+ SOC_ENUM("Input 1 Channel Select", input1_sel_enum),
+ SOC_ENUM("Input 2 Channel Select", input2_sel_enum),
+
+ SOC_ENUM("ADC1 HPF Select", adc1_hpf_enum),
+ SOC_ENUM("ADC2 HPF Select", adc2_hpf_enum),
+ SOC_ENUM("ADC1 NG Threshold", adc1_ng_thres_enum),
+ SOC_ENUM("ADC2 NG Threshold", adc2_ng_thres_enum),
+ SOC_ENUM("ADC1 NG Delay", adc1_ng_delay_enum),
+ SOC_ENUM("ADC2 NG Delay", adc2_ng_delay_enum),
+
+ SOC_SINGLE_SX_TLV("ADC1A PGA Volume",
+ CS53L30_ADC1A_AFE_CTL, 0, 0x34, 0x18, pga_tlv),
+ SOC_SINGLE_SX_TLV("ADC1B PGA Volume",
+ CS53L30_ADC1B_AFE_CTL, 0, 0x34, 0x18, pga_tlv),
+ SOC_SINGLE_SX_TLV("ADC2A PGA Volume",
+ CS53L30_ADC2A_AFE_CTL, 0, 0x34, 0x18, pga_tlv),
+ SOC_SINGLE_SX_TLV("ADC2B PGA Volume",
+ CS53L30_ADC2B_AFE_CTL, 0, 0x34, 0x18, pga_tlv),
+
+ SOC_SINGLE_SX_TLV("ADC1A Digital Volume",
+ CS53L30_ADC1A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv),
+ SOC_SINGLE_SX_TLV("ADC1B Digital Volume",
+ CS53L30_ADC1B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv),
+ SOC_SINGLE_SX_TLV("ADC2A Digital Volume",
+ CS53L30_ADC2A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv),
+ SOC_SINGLE_SX_TLV("ADC2B Digital Volume",
+ CS53L30_ADC2B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv),
+};
+
+static const struct snd_soc_dapm_widget cs53l30_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("IN1_DMIC1"),
+ SND_SOC_DAPM_INPUT("IN2"),
+ SND_SOC_DAPM_INPUT("IN3_DMIC2"),
+ SND_SOC_DAPM_INPUT("IN4"),
+ SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS53L30_MICBIAS_CTL,
+ CS53L30_MIC1_BIAS_PDN_SHIFT, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS53L30_MICBIAS_CTL,
+ CS53L30_MIC2_BIAS_PDN_SHIFT, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("MIC3 Bias", CS53L30_MICBIAS_CTL,
+ CS53L30_MIC3_BIAS_PDN_SHIFT, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("MIC4 Bias", CS53L30_MICBIAS_CTL,
+ CS53L30_MIC4_BIAS_PDN_SHIFT, 1, NULL, 0),
+
+ SND_SOC_DAPM_AIF_OUT("ASP_SDOUT1", NULL, 0, CS53L30_ASP_CTL1,
+ CS53L30_ASP_SDOUTx_PDN_SHIFT, 1),
+ SND_SOC_DAPM_AIF_OUT("ASP_SDOUT2", NULL, 0, CS53L30_ASP_CTL2,
+ CS53L30_ASP_SDOUTx_PDN_SHIFT, 1),
+
+ SND_SOC_DAPM_MUX("Input Mux 1", SND_SOC_NOPM, 0, 0,
+ &input1_route_sel_mux),
+ SND_SOC_DAPM_MUX("Input Mux 2", SND_SOC_NOPM, 0, 0,
+ &input2_route_sel_mux),
+
+ SND_SOC_DAPM_ADC("ADC1A", NULL, CS53L30_ADCDMIC1_CTL1,
+ CS53L30_ADCxA_PDN_SHIFT, 1),
+ SND_SOC_DAPM_ADC("ADC1B", NULL, CS53L30_ADCDMIC1_CTL1,
+ CS53L30_ADCxB_PDN_SHIFT, 1),
+ SND_SOC_DAPM_ADC("ADC2A", NULL, CS53L30_ADCDMIC2_CTL1,
+ CS53L30_ADCxA_PDN_SHIFT, 1),
+ SND_SOC_DAPM_ADC("ADC2B", NULL, CS53L30_ADCDMIC2_CTL1,
+ CS53L30_ADCxB_PDN_SHIFT, 1),
+ SND_SOC_DAPM_ADC("DMIC1", NULL, CS53L30_ADCDMIC1_CTL1,
+ CS53L30_DMICx_PDN_SHIFT, 1),
+ SND_SOC_DAPM_ADC("DMIC2", NULL, CS53L30_ADCDMIC2_CTL1,
+ CS53L30_DMICx_PDN_SHIFT, 1),
+};
+
+static const struct snd_soc_dapm_route cs53l30_dapm_routes[] = {
+ /* ADC Input Paths */
+ {"ADC1A", NULL, "IN1_DMIC1"},
+ {"Input Mux 1", "ADC1_SEL", "ADC1A"},
+ {"ADC1B", NULL, "IN2"},
+
+ {"ADC2A", NULL, "IN3_DMIC2"},
+ {"Input Mux 2", "ADC2_SEL", "ADC2A"},
+ {"ADC2B", NULL, "IN4"},
+
+ /* MIC Bias Paths */
+ {"ADC1A", NULL, "MIC1 Bias"},
+ {"ADC1B", NULL, "MIC2 Bias"},
+ {"ADC2A", NULL, "MIC3 Bias"},
+ {"ADC2B", NULL, "MIC4 Bias"},
+
+ /* DMIC Paths */
+ {"DMIC1", NULL, "IN1_DMIC1"},
+ {"Input Mux 1", "DMIC1_SEL", "DMIC1"},
+
+ {"DMIC2", NULL, "IN3_DMIC2"},
+ {"Input Mux 2", "DMIC2_SEL", "DMIC2"},
+};
+
+static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout1[] = {
+ /* Output Paths when using SDOUT1 only */
+ {"ASP_SDOUT1", NULL, "ADC1A" },
+ {"ASP_SDOUT1", NULL, "Input Mux 1"},
+ {"ASP_SDOUT1", NULL, "ADC1B"},
+
+ {"ASP_SDOUT1", NULL, "ADC2A"},
+ {"ASP_SDOUT1", NULL, "Input Mux 2"},
+ {"ASP_SDOUT1", NULL, "ADC2B"},
+
+ {"Capture", NULL, "ASP_SDOUT1"},
+};
+
+static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout2[] = {
+ /* Output Paths when using both SDOUT1 and SDOUT2 */
+ {"ASP_SDOUT1", NULL, "ADC1A" },
+ {"ASP_SDOUT1", NULL, "Input Mux 1"},
+ {"ASP_SDOUT1", NULL, "ADC1B"},
+
+ {"ASP_SDOUT2", NULL, "ADC2A"},
+ {"ASP_SDOUT2", NULL, "Input Mux 2"},
+ {"ASP_SDOUT2", NULL, "ADC2B"},
+
+ {"Capture", NULL, "ASP_SDOUT1"},
+ {"Capture", NULL, "ASP_SDOUT2"},
+};
+
+struct cs53l30_mclk_div {
+ u32 mclk_rate;
+ u32 srate;
+ u8 asp_rate;
+ u8 internal_fs_ratio;
+ u8 mclk_int_scale;
+};
+
+static struct cs53l30_mclk_div cs53l30_mclk_coeffs[] = {
+ /* NOTE: Enable MCLK_INT_SCALE to save power. */
+
+ /* MCLK, Sample Rate, asp_rate, internal_fs_ratio, mclk_int_scale */
+ {5644800, 11025, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {5644800, 22050, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {5644800, 44100, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+
+ {6000000, 8000, 0x1, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 11025, 0x2, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 12000, 0x4, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 16000, 0x5, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 22050, 0x6, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 24000, 0x8, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 32000, 0x9, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 44100, 0xA, 0, CS53L30_MCLK_INT_SCALE},
+ {6000000, 48000, 0xC, 0, CS53L30_MCLK_INT_SCALE},
+
+ {6144000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6144000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+
+ {6400000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+ {6400000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE},
+};
+
+struct cs53l30_mclkx_div {
+ u32 mclkx;
+ u8 ratio;
+ u8 mclkdiv;
+};
+
+static struct cs53l30_mclkx_div cs53l30_mclkx_coeffs[] = {
+ {5644800, 1, CS53L30_MCLK_DIV_BY_1},
+ {6000000, 1, CS53L30_MCLK_DIV_BY_1},
+ {6144000, 1, CS53L30_MCLK_DIV_BY_1},
+ {11289600, 2, CS53L30_MCLK_DIV_BY_2},
+ {12288000, 2, CS53L30_MCLK_DIV_BY_2},
+ {12000000, 2, CS53L30_MCLK_DIV_BY_2},
+ {19200000, 3, CS53L30_MCLK_DIV_BY_3},
+};
+
+static int cs53l30_get_mclkx_coeff(int mclkx)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs53l30_mclkx_coeffs); i++) {
+ if (cs53l30_mclkx_coeffs[i].mclkx == mclkx)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int cs53l30_get_mclk_coeff(int mclk_rate, int srate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs53l30_mclk_coeffs); i++) {
+ if (cs53l30_mclk_coeffs[i].mclk_rate == mclk_rate &&
+ cs53l30_mclk_coeffs[i].srate == srate)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int cs53l30_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+ int mclkx_coeff;
+ u32 mclk_rate;
+
+ /* MCLKX -> MCLK */
+ mclkx_coeff = cs53l30_get_mclkx_coeff(freq);
+ if (mclkx_coeff < 0)
+ return mclkx_coeff;
+
+ mclk_rate = cs53l30_mclkx_coeffs[mclkx_coeff].mclkx /
+ cs53l30_mclkx_coeffs[mclkx_coeff].ratio;
+
+ regmap_update_bits(priv->regmap, CS53L30_MCLKCTL,
+ CS53L30_MCLK_DIV_MASK,
+ cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv);
+
+ priv->mclk_rate = mclk_rate;
+
+ return 0;
+}
+
+static int cs53l30_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+ u8 aspcfg = 0, aspctl1 = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aspcfg |= CS53L30_ASP_MS;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* Set TDM_PDN to turn off TDM mode -- Reset default */
+ aspctl1 |= CS53L30_ASP_TDM_PDN;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /*
+ * Clear TDM_PDN to turn on TDM mode; Use ASP_SCLK_INV = 0
+ * with SHIFT_LEFT = 1 combination as Figure 4-13 shows in
+ * the CS53L30 datasheet
+ */
+ aspctl1 |= CS53L30_SHIFT_LEFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check to see if the SCLK is inverted */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ case SND_SOC_DAIFMT_IB_IF:
+ aspcfg ^= CS53L30_ASP_SCLK_INV;
+ break;
+ default:
+ break;
+ }
+
+ regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL,
+ CS53L30_ASP_MS | CS53L30_ASP_SCLK_INV, aspcfg);
+
+ regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1,
+ CS53L30_ASP_TDM_PDN | CS53L30_SHIFT_LEFT, aspctl1);
+
+ return 0;
+}
+
+static int cs53l30_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+ int srate = params_rate(params);
+ int mclk_coeff;
+
+ /* MCLK -> srate */
+ mclk_coeff = cs53l30_get_mclk_coeff(priv->mclk_rate, srate);
+ if (mclk_coeff < 0)
+ return -EINVAL;
+
+ regmap_update_bits(priv->regmap, CS53L30_INT_SR_CTL,
+ CS53L30_INTRNL_FS_RATIO_MASK,
+ cs53l30_mclk_coeffs[mclk_coeff].internal_fs_ratio);
+
+ regmap_update_bits(priv->regmap, CS53L30_MCLKCTL,
+ CS53L30_MCLK_INT_SCALE_MASK,
+ cs53l30_mclk_coeffs[mclk_coeff].mclk_int_scale);
+
+ regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL,
+ CS53L30_ASP_RATE_MASK,
+ cs53l30_mclk_coeffs[mclk_coeff].asp_rate);
+
+ return 0;
+}
+
+static int cs53l30_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec);
+ unsigned int reg;
+ int i, inter_max_check, ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level == SND_SOC_BIAS_STANDBY)
+ regmap_update_bits(priv->regmap, CS53L30_PWRCTL,
+ CS53L30_PDN_LP_MASK, 0);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (dapm->bias_level == SND_SOC_BIAS_OFF) {
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret) {
+ dev_err(codec->dev,
+ "failed to enable MCLK: %d\n", ret);
+ return ret;
+ }
+ regmap_update_bits(priv->regmap, CS53L30_MCLKCTL,
+ CS53L30_MCLK_DIS_MASK, 0);
+ regmap_update_bits(priv->regmap, CS53L30_PWRCTL,
+ CS53L30_PDN_ULP_MASK, 0);
+ msleep(50);
+ } else {
+ regmap_update_bits(priv->regmap, CS53L30_PWRCTL,
+ CS53L30_PDN_ULP_MASK,
+ CS53L30_PDN_ULP);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ regmap_update_bits(priv->regmap, CS53L30_INT_MASK,
+ CS53L30_PDN_DONE, 0);
+ /*
+ * If digital softramp is set, the amount of time required
+ * for power down increases and depends on the digital
+ * volume setting.
+ */
+
+ /* Set the max possible time if digsft is set */
+ regmap_read(priv->regmap, CS53L30_SFT_RAMP, &reg);
+ if (reg & CS53L30_DIGSFT_MASK)
+ inter_max_check = CS53L30_PDN_POLL_MAX;
+ else
+ inter_max_check = 10;
+
+ regmap_update_bits(priv->regmap, CS53L30_PWRCTL,
+ CS53L30_PDN_ULP_MASK,
+ CS53L30_PDN_ULP);
+ /* PDN_DONE will take a min of 20ms to be set.*/
+ msleep(20);
+ /* Clr status */
+ regmap_read(priv->regmap, CS53L30_IS, &reg);
+ for (i = 0; i < inter_max_check; i++) {
+ if (inter_max_check < 10) {
+ usleep_range(1000, 1100);
+ regmap_read(priv->regmap, CS53L30_IS, &reg);
+ if (reg & CS53L30_PDN_DONE)
+ break;
+ } else {
+ usleep_range(10000, 10100);
+ regmap_read(priv->regmap, CS53L30_IS, &reg);
+ if (reg & CS53L30_PDN_DONE)
+ break;
+ }
+ }
+ /* PDN_DONE is set. We now can disable the MCLK */
+ regmap_update_bits(priv->regmap, CS53L30_INT_MASK,
+ CS53L30_PDN_DONE, CS53L30_PDN_DONE);
+ regmap_update_bits(priv->regmap, CS53L30_MCLKCTL,
+ CS53L30_MCLK_DIS_MASK,
+ CS53L30_MCLK_DIS);
+ clk_disable_unprepare(priv->mclk);
+ break;
+ }
+
+ return 0;
+}
+
+static int cs53l30_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+ u8 val = tristate ? CS53L30_ASP_3ST : 0;
+
+ return regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1,
+ CS53L30_ASP_3ST_MASK, val);
+}
+
+static unsigned int const cs53l30_src_rates[] = {
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list src_constraints = {
+ .count = ARRAY_SIZE(cs53l30_src_rates),
+ .list = cs53l30_src_rates,
+};
+
+static int cs53l30_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &src_constraints);
+
+ return 0;
+}
+
+/*
+ * Note: CS53L30 counts the slot number per byte while ASoC counts the slot
+ * number per slot_width. So there is a difference between the slots of ASoC
+ * and the slots of CS53L30.
+ */
+static int cs53l30_set_dai_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+ unsigned int loc[CS53L30_TDM_SLOT_MAX] = {48, 48, 48, 48};
+ unsigned int slot_next, slot_step;
+ u64 tx_enable = 0;
+ int i;
+
+ if (!rx_mask) {
+ dev_err(dai->dev, "rx masks must not be 0\n");
+ return -EINVAL;
+ }
+
+ /* Assuming slot_width is not supposed to be greater than 64 */
+ if (slots <= 0 || slot_width <= 0 || slot_width > 64) {
+ dev_err(dai->dev, "invalid slot number or slot width\n");
+ return -EINVAL;
+ }
+
+ if (slot_width & 0x7) {
+ dev_err(dai->dev, "slot width must count in byte\n");
+ return -EINVAL;
+ }
+
+ /* How many bytes in each ASoC slot */
+ slot_step = slot_width >> 3;
+
+ for (i = 0; rx_mask && i < CS53L30_TDM_SLOT_MAX; i++) {
+ /* Find the first slot from LSB */
+ slot_next = __ffs(rx_mask);
+ /* Save the slot location by converting to CS53L30 slot */
+ loc[i] = slot_next * slot_step;
+ /* Create the mask of CS53L30 slot */
+ tx_enable |= (u64)((u64)(1 << slot_step) - 1) << (u64)loc[i];
+ /* Clear this slot from rx_mask */
+ rx_mask &= ~(1 << slot_next);
+ }
+
+ /* Error out to avoid slot shift */
+ if (rx_mask && i == CS53L30_TDM_SLOT_MAX) {
+ dev_err(dai->dev, "rx_mask exceeds max slot number: %d\n",
+ CS53L30_TDM_SLOT_MAX);
+ return -EINVAL;
+ }
+
+ /* Validate the last active CS53L30 slot */
+ slot_next = loc[i - 1] + slot_step - 1;
+ if (slot_next > 47) {
+ dev_err(dai->dev, "slot selection out of bounds: %u\n",
+ slot_next);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < CS53L30_TDM_SLOT_MAX && loc[i] != 48; i++) {
+ regmap_update_bits(priv->regmap, CS53L30_ASP_TDMTX_CTL(i),
+ CS53L30_ASP_CHx_TX_LOC_MASK, loc[i]);
+ dev_dbg(dai->dev, "loc[%d]=%x\n", i, loc[i]);
+ }
+
+ for (i = 0; i < CS53L30_ASP_TDMTX_ENx_MAX && tx_enable; i++) {
+ regmap_write(priv->regmap, CS53L30_ASP_TDMTX_ENx(i),
+ tx_enable & 0xff);
+ tx_enable >>= 8;
+ dev_dbg(dai->dev, "en_reg=%x, tx_enable=%llx\n",
+ CS53L30_ASP_TDMTX_ENx(i), tx_enable & 0xff);
+ }
+
+ return 0;
+}
+
+static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec);
+
+ if (priv->mute_gpio)
+ gpiod_set_value_cansleep(priv->mute_gpio, mute);
+
+ return 0;
+}
+
+/* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */
+#define CS53L30_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT)
+
+#define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static const struct snd_soc_dai_ops cs53l30_ops = {
+ .startup = cs53l30_pcm_startup,
+ .hw_params = cs53l30_pcm_hw_params,
+ .set_fmt = cs53l30_set_dai_fmt,
+ .set_sysclk = cs53l30_set_sysclk,
+ .set_tristate = cs53l30_set_tristate,
+ .set_tdm_slot = cs53l30_set_dai_tdm_slot,
+ .mute_stream = cs53l30_mute_stream,
+};
+
+static struct snd_soc_dai_driver cs53l30_dai = {
+ .name = "cs53l30",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = CS53L30_RATES,
+ .formats = CS53L30_FORMATS,
+ },
+ .ops = &cs53l30_ops,
+ .symmetric_rates = 1,
+};
+
+static int cs53l30_codec_probe(struct snd_soc_codec *codec)
+{
+ struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+
+ if (priv->use_sdout2)
+ snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout2,
+ ARRAY_SIZE(cs53l30_dapm_routes_sdout2));
+ else
+ snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout1,
+ ARRAY_SIZE(cs53l30_dapm_routes_sdout1));
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver cs53l30_driver = {
+ .probe = cs53l30_codec_probe,
+ .set_bias_level = cs53l30_set_bias_level,
+ .idle_bias_off = true,
+
+ .dapm_widgets = cs53l30_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs53l30_dapm_widgets),
+ .dapm_routes = cs53l30_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs53l30_dapm_routes),
+
+ .controls = cs53l30_snd_controls,
+ .num_controls = ARRAY_SIZE(cs53l30_snd_controls),
+};
+
+static struct regmap_config cs53l30_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = CS53L30_MAX_REGISTER,
+ .reg_defaults = cs53l30_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cs53l30_reg_defaults),
+ .volatile_reg = cs53l30_volatile_register,
+ .writeable_reg = cs53l30_writeable_register,
+ .readable_reg = cs53l30_readable_register,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int cs53l30_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct device_node *np = client->dev.of_node;
+ struct device *dev = &client->dev;
+ struct cs53l30_private *cs53l30;
+ unsigned int devid = 0;
+ unsigned int reg;
+ int ret = 0, i;
+ u8 val;
+
+ cs53l30 = devm_kzalloc(dev, sizeof(*cs53l30), GFP_KERNEL);
+ if (!cs53l30)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(cs53l30->supplies); i++)
+ cs53l30->supplies[i].supply = cs53l30_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+ if (ret) {
+ dev_err(dev, "failed to get supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /* Reset the Device */
+ cs53l30->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(cs53l30->reset_gpio)) {
+ ret = PTR_ERR(cs53l30->reset_gpio);
+ goto error;
+ }
+
+ if (cs53l30->reset_gpio)
+ gpiod_set_value_cansleep(cs53l30->reset_gpio, 1);
+
+ i2c_set_clientdata(client, cs53l30);
+
+ cs53l30->mclk_rate = 0;
+
+ cs53l30->regmap = devm_regmap_init_i2c(client, &cs53l30_regmap);
+ if (IS_ERR(cs53l30->regmap)) {
+ ret = PTR_ERR(cs53l30->regmap);
+ dev_err(dev, "regmap_init() failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Initialize codec */
+ ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_AB, &reg);
+ devid = reg << 12;
+
+ ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, &reg);
+ devid |= reg << 4;
+
+ ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, &reg);
+ devid |= (reg & 0xF0) >> 4;
+
+ if (devid != CS53L30_DEVID) {
+ ret = -ENODEV;
+ dev_err(dev, "Device ID (%X). Expected %X\n",
+ devid, CS53L30_DEVID);
+ goto error;
+ }
+
+ ret = regmap_read(cs53l30->regmap, CS53L30_REVID, &reg);
+ if (ret < 0) {
+ dev_err(dev, "failed to get Revision ID: %d\n", ret);
+ goto error;
+ }
+
+ /* Check if MCLK provided */
+ cs53l30->mclk = devm_clk_get(dev, "mclk");
+ if (IS_ERR(cs53l30->mclk)) {
+ if (PTR_ERR(cs53l30->mclk) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto error;
+ }
+ /* Otherwise mark the mclk pointer to NULL */
+ cs53l30->mclk = NULL;
+ }
+
+ /* Fetch the MUTE control */
+ cs53l30->mute_gpio = devm_gpiod_get_optional(dev, "mute",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(cs53l30->mute_gpio)) {
+ ret = PTR_ERR(cs53l30->mute_gpio);
+ goto error;
+ }
+
+ if (cs53l30->mute_gpio) {
+ /* Enable MUTE controls via MUTE pin */
+ regmap_write(cs53l30->regmap, CS53L30_MUTEP_CTL1,
+ CS53L30_MUTEP_CTL1_MUTEALL);
+ /* Flip the polarity of MUTE pin */
+ if (gpiod_is_active_low(cs53l30->mute_gpio))
+ regmap_update_bits(cs53l30->regmap, CS53L30_MUTEP_CTL2,
+ CS53L30_MUTE_PIN_POLARITY, 0);
+ }
+
+ if (!of_property_read_u8(np, "cirrus,micbias-lvl", &val))
+ regmap_update_bits(cs53l30->regmap, CS53L30_MICBIAS_CTL,
+ CS53L30_MIC_BIAS_CTRL_MASK, val);
+
+ if (of_property_read_bool(np, "cirrus,use-sdout2"))
+ cs53l30->use_sdout2 = true;
+
+ dev_info(dev, "Cirrus Logic CS53L30, Revision: %02X\n", reg & 0xFF);
+
+ ret = snd_soc_register_codec(dev, &cs53l30_driver, &cs53l30_dai, 1);
+ if (ret) {
+ dev_err(dev, "failed to register codec: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+ return ret;
+}
+
+static int cs53l30_i2c_remove(struct i2c_client *client)
+{
+ struct cs53l30_private *cs53l30 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+
+ /* Hold down reset */
+ if (cs53l30->reset_gpio)
+ gpiod_set_value_cansleep(cs53l30->reset_gpio, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int cs53l30_runtime_suspend(struct device *dev)
+{
+ struct cs53l30_private *cs53l30 = dev_get_drvdata(dev);
+
+ regcache_cache_only(cs53l30->regmap, true);
+
+ /* Hold down reset */
+ if (cs53l30->reset_gpio)
+ gpiod_set_value_cansleep(cs53l30->reset_gpio, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+
+ return 0;
+}
+
+static int cs53l30_runtime_resume(struct device *dev)
+{
+ struct cs53l30_private *cs53l30 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies),
+ cs53l30->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ if (cs53l30->reset_gpio)
+ gpiod_set_value_cansleep(cs53l30->reset_gpio, 1);
+
+ regcache_cache_only(cs53l30->regmap, false);
+ ret = regcache_sync(cs53l30->regmap);
+ if (ret) {
+ dev_err(dev, "failed to synchronize regcache: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops cs53l30_runtime_pm = {
+ SET_RUNTIME_PM_OPS(cs53l30_runtime_suspend, cs53l30_runtime_resume,
+ NULL)
+};
+
+static const struct of_device_id cs53l30_of_match[] = {
+ { .compatible = "cirrus,cs53l30", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, cs53l30_of_match);
+
+static const struct i2c_device_id cs53l30_id[] = {
+ { "cs53l30", 0 },
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, cs53l30_id);
+
+static struct i2c_driver cs53l30_i2c_driver = {
+ .driver = {
+ .name = "cs53l30",
+ .pm = &cs53l30_runtime_pm,
+ },
+ .id_table = cs53l30_id,
+ .probe = cs53l30_i2c_probe,
+ .remove = cs53l30_i2c_remove,
+};
+
+module_i2c_driver(cs53l30_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS53L30 driver");
+MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <Paul.Handrigan@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs53l30.h b/sound/soc/codecs/cs53l30.h
new file mode 100644
index 000000000000..5e39da568749
--- /dev/null
+++ b/sound/soc/codecs/cs53l30.h
@@ -0,0 +1,459 @@
+/*
+ * ALSA SoC CS53L30 codec driver
+ *
+ * Copyright 2015 Cirrus Logic, Inc.
+ *
+ * Author: Paul Handrigan <Paul.Handrigan@cirrus.com>,
+ * Tim Howe <Tim.Howe@cirrus.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __CS53L30_H__
+#define __CS53L30_H__
+
+/* I2C Registers */
+#define CS53L30_DEVID_AB 0x01 /* Device ID A & B [RO]. */
+#define CS53L30_DEVID_CD 0x02 /* Device ID C & D [RO]. */
+#define CS53L30_DEVID_E 0x03 /* Device ID E [RO]. */
+#define CS53L30_REVID 0x05 /* Revision ID [RO]. */
+#define CS53L30_PWRCTL 0x06 /* Power Control. */
+#define CS53L30_MCLKCTL 0x07 /* MCLK Control. */
+#define CS53L30_INT_SR_CTL 0x08 /* Internal Sample Rate Control. */
+#define CS53L30_MICBIAS_CTL 0x0A /* Mic Bias Control. */
+#define CS53L30_ASPCFG_CTL 0x0C /* ASP Config Control. */
+#define CS53L30_ASP_CTL1 0x0D /* ASP1 Control. */
+#define CS53L30_ASP_TDMTX_CTL1 0x0E /* ASP1 TDM TX Control 1 */
+#define CS53L30_ASP_TDMTX_CTL2 0x0F /* ASP1 TDM TX Control 2 */
+#define CS53L30_ASP_TDMTX_CTL3 0x10 /* ASP1 TDM TX Control 3 */
+#define CS53L30_ASP_TDMTX_CTL4 0x11 /* ASP1 TDM TX Control 4 */
+#define CS53L30_ASP_TDMTX_EN1 0x12 /* ASP1 TDM TX Enable 1 */
+#define CS53L30_ASP_TDMTX_EN2 0x13 /* ASP1 TDM TX Enable 2 */
+#define CS53L30_ASP_TDMTX_EN3 0x14 /* ASP1 TDM TX Enable 3 */
+#define CS53L30_ASP_TDMTX_EN4 0x15 /* ASP1 TDM TX Enable 4 */
+#define CS53L30_ASP_TDMTX_EN5 0x16 /* ASP1 TDM TX Enable 5 */
+#define CS53L30_ASP_TDMTX_EN6 0x17 /* ASP1 TDM TX Enable 6 */
+#define CS53L30_ASP_CTL2 0x18 /* ASP2 Control. */
+#define CS53L30_SFT_RAMP 0x1A /* Soft Ramp Control. */
+#define CS53L30_LRCK_CTL1 0x1B /* LRCK Control 1. */
+#define CS53L30_LRCK_CTL2 0x1C /* LRCK Control 2. */
+#define CS53L30_MUTEP_CTL1 0x1F /* Mute Pin Control 1. */
+#define CS53L30_MUTEP_CTL2 0x20 /* Mute Pin Control 2. */
+#define CS53L30_INBIAS_CTL1 0x21 /* Input Bias Control 1. */
+#define CS53L30_INBIAS_CTL2 0x22 /* Input Bias Control 2. */
+#define CS53L30_DMIC1_STR_CTL 0x23 /* DMIC1 Stereo Control. */
+#define CS53L30_DMIC2_STR_CTL 0x24 /* DMIC2 Stereo Control. */
+#define CS53L30_ADCDMIC1_CTL1 0x25 /* ADC1/DMIC1 Control 1. */
+#define CS53L30_ADCDMIC1_CTL2 0x26 /* ADC1/DMIC1 Control 2. */
+#define CS53L30_ADC1_CTL3 0x27 /* ADC1 Control 3. */
+#define CS53L30_ADC1_NG_CTL 0x28 /* ADC1 Noise Gate Control. */
+#define CS53L30_ADC1A_AFE_CTL 0x29 /* ADC1A AFE Control. */
+#define CS53L30_ADC1B_AFE_CTL 0x2A /* ADC1B AFE Control. */
+#define CS53L30_ADC1A_DIG_VOL 0x2B /* ADC1A Digital Volume. */
+#define CS53L30_ADC1B_DIG_VOL 0x2C /* ADC1B Digital Volume. */
+#define CS53L30_ADCDMIC2_CTL1 0x2D /* ADC2/DMIC2 Control 1. */
+#define CS53L30_ADCDMIC2_CTL2 0x2E /* ADC2/DMIC2 Control 2. */
+#define CS53L30_ADC2_CTL3 0x2F /* ADC2 Control 3. */
+#define CS53L30_ADC2_NG_CTL 0x30 /* ADC2 Noise Gate Control. */
+#define CS53L30_ADC2A_AFE_CTL 0x31 /* ADC2A AFE Control. */
+#define CS53L30_ADC2B_AFE_CTL 0x32 /* ADC2B AFE Control. */
+#define CS53L30_ADC2A_DIG_VOL 0x33 /* ADC2A Digital Volume. */
+#define CS53L30_ADC2B_DIG_VOL 0x34 /* ADC2B Digital Volume. */
+#define CS53L30_INT_MASK 0x35 /* Interrupt Mask. */
+#define CS53L30_IS 0x36 /* Interrupt Status. */
+#define CS53L30_MAX_REGISTER 0x36
+
+#define CS53L30_TDM_SLOT_MAX 4
+#define CS53L30_ASP_TDMTX_CTL(x) (CS53L30_ASP_TDMTX_CTL1 + (x))
+/* x : index for registers; n : index for slot; 8 slots per register */
+#define CS53L30_ASP_TDMTX_ENx(x) (CS53L30_ASP_TDMTX_EN6 - (x))
+#define CS53L30_ASP_TDMTX_ENn(n) CS53L30_ASP_TDMTX_ENx((n) >> 3)
+#define CS53L30_ASP_TDMTX_ENx_MAX 6
+
+/* Device ID */
+#define CS53L30_DEVID 0x53A30
+
+/* PDN_DONE Poll Maximum
+ * If soft ramp is set it will take much longer to power down
+ * the system.
+ */
+#define CS53L30_PDN_POLL_MAX 90
+
+/* Bitfield Definitions */
+
+/* R6 (0x06) CS53L30_PWRCTL - Power Control */
+#define CS53L30_PDN_ULP_SHIFT 7
+#define CS53L30_PDN_ULP_MASK (1 << CS53L30_PDN_ULP_SHIFT)
+#define CS53L30_PDN_ULP (1 << CS53L30_PDN_ULP_SHIFT)
+#define CS53L30_PDN_LP_SHIFT 6
+#define CS53L30_PDN_LP_MASK (1 << CS53L30_PDN_LP_SHIFT)
+#define CS53L30_PDN_LP (1 << CS53L30_PDN_LP_SHIFT)
+#define CS53L30_DISCHARGE_FILT_SHIFT 5
+#define CS53L30_DISCHARGE_FILT_MASK (1 << CS53L30_DISCHARGE_FILT_SHIFT)
+#define CS53L30_DISCHARGE_FILT (1 << CS53L30_DISCHARGE_FILT_SHIFT)
+#define CS53L30_THMS_PDN_SHIFT 4
+#define CS53L30_THMS_PDN_MASK (1 << CS53L30_THMS_PDN_SHIFT)
+#define CS53L30_THMS_PDN (1 << CS53L30_THMS_PDN_SHIFT)
+
+#define CS53L30_PWRCTL_DEFAULT (CS53L30_THMS_PDN)
+
+/* R7 (0x07) CS53L30_MCLKCTL - MCLK Control */
+#define CS53L30_MCLK_DIS_SHIFT 7
+#define CS53L30_MCLK_DIS_MASK (1 << CS53L30_MCLK_DIS_SHIFT)
+#define CS53L30_MCLK_DIS (1 << CS53L30_MCLK_DIS_SHIFT)
+#define CS53L30_MCLK_INT_SCALE_SHIFT 6
+#define CS53L30_MCLK_INT_SCALE_MASK (1 << CS53L30_MCLK_INT_SCALE_SHIFT)
+#define CS53L30_MCLK_INT_SCALE (1 << CS53L30_MCLK_INT_SCALE_SHIFT)
+#define CS53L30_DMIC_DRIVE_SHIFT 5
+#define CS53L30_DMIC_DRIVE_MASK (1 << CS53L30_DMIC_DRIVE_SHIFT)
+#define CS53L30_DMIC_DRIVE (1 << CS53L30_DMIC_DRIVE_SHIFT)
+#define CS53L30_MCLK_DIV_SHIFT 2
+#define CS53L30_MCLK_DIV_WIDTH 2
+#define CS53L30_MCLK_DIV_MASK (((1 << CS53L30_MCLK_DIV_WIDTH) - 1) << CS53L30_MCLK_DIV_SHIFT)
+#define CS53L30_MCLK_DIV_BY_1 (0x0 << CS53L30_MCLK_DIV_SHIFT)
+#define CS53L30_MCLK_DIV_BY_2 (0x1 << CS53L30_MCLK_DIV_SHIFT)
+#define CS53L30_MCLK_DIV_BY_3 (0x2 << CS53L30_MCLK_DIV_SHIFT)
+#define CS53L30_SYNC_EN_SHIFT 1
+#define CS53L30_SYNC_EN_MASK (1 << CS53L30_SYNC_EN_SHIFT)
+#define CS53L30_SYNC_EN (1 << CS53L30_SYNC_EN_SHIFT)
+
+#define CS53L30_MCLKCTL_DEFAULT (CS53L30_MCLK_DIV_BY_2)
+
+/* R8 (0x08) CS53L30_INT_SR_CTL - Internal Sample Rate Control */
+#define CS53L30_INTRNL_FS_RATIO_SHIFT 4
+#define CS53L30_INTRNL_FS_RATIO_MASK (1 << CS53L30_INTRNL_FS_RATIO_SHIFT)
+#define CS53L30_INTRNL_FS_RATIO (1 << CS53L30_INTRNL_FS_RATIO_SHIFT)
+#define CS53L30_MCLK_19MHZ_EN_SHIFT 0
+#define CS53L30_MCLK_19MHZ_EN_MASK (1 << CS53L30_MCLK_19MHZ_EN_SHIFT)
+#define CS53L30_MCLK_19MHZ_EN (1 << CS53L30_MCLK_19MHZ_EN_SHIFT)
+
+/* 0x6 << 1 is reserved bits */
+#define CS53L30_INT_SR_CTL_DEFAULT (CS53L30_INTRNL_FS_RATIO | 0x6 << 1)
+
+/* R10 (0x0A) CS53L30_MICBIAS_CTL - Mic Bias Control */
+#define CS53L30_MIC4_BIAS_PDN_SHIFT 7
+#define CS53L30_MIC4_BIAS_PDN_MASK (1 << CS53L30_MIC4_BIAS_PDN_SHIFT)
+#define CS53L30_MIC4_BIAS_PDN (1 << CS53L30_MIC4_BIAS_PDN_SHIFT)
+#define CS53L30_MIC3_BIAS_PDN_SHIFT 6
+#define CS53L30_MIC3_BIAS_PDN_MASK (1 << CS53L30_MIC3_BIAS_PDN_SHIFT)
+#define CS53L30_MIC3_BIAS_PDN (1 << CS53L30_MIC3_BIAS_PDN_SHIFT)
+#define CS53L30_MIC2_BIAS_PDN_SHIFT 5
+#define CS53L30_MIC2_BIAS_PDN_MASK (1 << CS53L30_MIC2_BIAS_PDN_SHIFT)
+#define CS53L30_MIC2_BIAS_PDN (1 << CS53L30_MIC2_BIAS_PDN_SHIFT)
+#define CS53L30_MIC1_BIAS_PDN_SHIFT 4
+#define CS53L30_MIC1_BIAS_PDN_MASK (1 << CS53L30_MIC1_BIAS_PDN_SHIFT)
+#define CS53L30_MIC1_BIAS_PDN (1 << CS53L30_MIC1_BIAS_PDN_SHIFT)
+#define CS53L30_MICx_BIAS_PDN (0xf << CS53L30_MIC1_BIAS_PDN_SHIFT)
+#define CS53L30_VP_MIN_SHIFT 2
+#define CS53L30_VP_MIN_MASK (1 << CS53L30_VP_MIN_SHIFT)
+#define CS53L30_VP_MIN (1 << CS53L30_VP_MIN_SHIFT)
+#define CS53L30_MIC_BIAS_CTRL_SHIFT 0
+#define CS53L30_MIC_BIAS_CTRL_WIDTH 2
+#define CS53L30_MIC_BIAS_CTRL_MASK (((1 << CS53L30_MIC_BIAS_CTRL_WIDTH) - 1) << CS53L30_MIC_BIAS_CTRL_SHIFT)
+#define CS53L30_MIC_BIAS_CTRL_HIZ (0 << CS53L30_MIC_BIAS_CTRL_SHIFT)
+#define CS53L30_MIC_BIAS_CTRL_1V8 (1 << CS53L30_MIC_BIAS_CTRL_SHIFT)
+#define CS53L30_MIC_BIAS_CTRL_2V75 (2 << CS53L30_MIC_BIAS_CTRL_SHIFT)
+
+#define CS53L30_MICBIAS_CTL_DEFAULT (CS53L30_MICx_BIAS_PDN | CS53L30_VP_MIN)
+
+/* R12 (0x0C) CS53L30_ASPCFG_CTL - ASP Configuration Control */
+#define CS53L30_ASP_MS_SHIFT 7
+#define CS53L30_ASP_MS_MASK (1 << CS53L30_ASP_MS_SHIFT)
+#define CS53L30_ASP_MS (1 << CS53L30_ASP_MS_SHIFT)
+#define CS53L30_ASP_SCLK_INV_SHIFT 4
+#define CS53L30_ASP_SCLK_INV_MASK (1 << CS53L30_ASP_SCLK_INV_SHIFT)
+#define CS53L30_ASP_SCLK_INV (1 << CS53L30_ASP_SCLK_INV_SHIFT)
+#define CS53L30_ASP_RATE_SHIFT 0
+#define CS53L30_ASP_RATE_WIDTH 4
+#define CS53L30_ASP_RATE_MASK (((1 << CS53L30_ASP_RATE_WIDTH) - 1) << CS53L30_ASP_RATE_SHIFT)
+#define CS53L30_ASP_RATE_48K (0xc << CS53L30_ASP_RATE_SHIFT)
+
+#define CS53L30_ASPCFG_CTL_DEFAULT (CS53L30_ASP_RATE_48K)
+
+/* R13/R24 (0x0D/0x18) CS53L30_ASP_CTL1 & CS53L30_ASP_CTL2 - ASP Control 1~2 */
+#define CS53L30_ASP_TDM_PDN_SHIFT 7
+#define CS53L30_ASP_TDM_PDN_MASK (1 << CS53L30_ASP_TDM_PDN_SHIFT)
+#define CS53L30_ASP_TDM_PDN (1 << CS53L30_ASP_TDM_PDN_SHIFT)
+#define CS53L30_ASP_SDOUTx_PDN_SHIFT 6
+#define CS53L30_ASP_SDOUTx_PDN_MASK (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT)
+#define CS53L30_ASP_SDOUTx_PDN (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT)
+#define CS53L30_ASP_3ST_SHIFT 5
+#define CS53L30_ASP_3ST_MASK (1 << CS53L30_ASP_3ST_SHIFT)
+#define CS53L30_ASP_3ST (1 << CS53L30_ASP_3ST_SHIFT)
+#define CS53L30_SHIFT_LEFT_SHIFT 4
+#define CS53L30_SHIFT_LEFT_MASK (1 << CS53L30_SHIFT_LEFT_SHIFT)
+#define CS53L30_SHIFT_LEFT (1 << CS53L30_SHIFT_LEFT_SHIFT)
+#define CS53L30_ASP_SDOUTx_DRIVE_SHIFT 0
+#define CS53L30_ASP_SDOUTx_DRIVE_MASK (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT)
+#define CS53L30_ASP_SDOUTx_DRIVE (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT)
+
+#define CS53L30_ASP_CTL1_DEFAULT (CS53L30_ASP_TDM_PDN)
+#define CS53L30_ASP_CTL2_DEFAULT (0)
+
+/* R14 (0x0E) ~ R17 (0x11) CS53L30_ASP_TDMTX_CTLx - ASP TDM TX Control 1~4 */
+#define CS53L30_ASP_CHx_TX_STATE_SHIFT 7
+#define CS53L30_ASP_CHx_TX_STATE_MASK (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT)
+#define CS53L30_ASP_CHx_TX_STATE (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT)
+#define CS53L30_ASP_CHx_TX_LOC_SHIFT 0
+#define CS53L30_ASP_CHx_TX_LOC_WIDTH 6
+#define CS53L30_ASP_CHx_TX_LOC_MASK (((1 << CS53L30_ASP_CHx_TX_LOC_WIDTH) - 1) << CS53L30_ASP_CHx_TX_LOC_SHIFT)
+#define CS53L30_ASP_CHx_TX_LOC_MAX (47 << CS53L30_ASP_CHx_TX_LOC_SHIFT)
+#define CS53L30_ASP_CHx_TX_LOC(x) ((x) << CS53L30_ASP_CHx_TX_LOC_SHIFT)
+
+#define CS53L30_ASP_TDMTX_CTLx_DEFAULT (CS53L30_ASP_CHx_TX_LOC_MAX)
+
+/* R18 (0x12) ~ R23 (0x17) CS53L30_ASP_TDMTX_ENx - ASP TDM TX Enable 1~6 */
+#define CS53L30_ASP_TDMTX_ENx_DEFAULT (0)
+
+/* R26 (0x1A) CS53L30_SFT_RAMP - Soft Ramp Control */
+#define CS53L30_DIGSFT_SHIFT 5
+#define CS53L30_DIGSFT_MASK (1 << CS53L30_DIGSFT_SHIFT)
+#define CS53L30_DIGSFT (1 << CS53L30_DIGSFT_SHIFT)
+
+#define CS53L30_SFT_RMP_DEFAULT (0)
+
+/* R28 (0x1C) CS53L30_LRCK_CTL2 - LRCK Control 2 */
+#define CS53L30_LRCK_50_NPW_SHIFT 3
+#define CS53L30_LRCK_50_NPW_MASK (1 << CS53L30_LRCK_50_NPW_SHIFT)
+#define CS53L30_LRCK_50_NPW (1 << CS53L30_LRCK_50_NPW_SHIFT)
+#define CS53L30_LRCK_TPWH_SHIFT 0
+#define CS53L30_LRCK_TPWH_WIDTH 3
+#define CS53L30_LRCK_TPWH_MASK (((1 << CS53L30_LRCK_TPWH_WIDTH) - 1) << CS53L30_LRCK_TPWH_SHIFT)
+#define CS53L30_LRCK_TPWH(x) (((x) << CS53L30_LRCK_TPWH_SHIFT) & CS53L30_LRCK_TPWH_MASK)
+
+#define CS53L30_LRCK_CTLx_DEFAULT (0)
+
+/* R31 (0x1F) CS53L30_MUTEP_CTL1 - MUTE Pin Control 1 */
+#define CS53L30_MUTE_PDN_ULP_SHIFT 7
+#define CS53L30_MUTE_PDN_ULP_MASK (1 << CS53L30_MUTE_PDN_ULP_SHIFT)
+#define CS53L30_MUTE_PDN_ULP (1 << CS53L30_MUTE_PDN_ULP_SHIFT)
+#define CS53L30_MUTE_PDN_LP_SHIFT 6
+#define CS53L30_MUTE_PDN_LP_MASK (1 << CS53L30_MUTE_PDN_LP_SHIFT)
+#define CS53L30_MUTE_PDN_LP (1 << CS53L30_MUTE_PDN_LP_SHIFT)
+#define CS53L30_MUTE_M4B_PDN_SHIFT 4
+#define CS53L30_MUTE_M4B_PDN_MASK (1 << CS53L30_MUTE_M4B_PDN_SHIFT)
+#define CS53L30_MUTE_M4B_PDN (1 << CS53L30_MUTE_M4B_PDN_SHIFT)
+#define CS53L30_MUTE_M3B_PDN_SHIFT 3
+#define CS53L30_MUTE_M3B_PDN_MASK (1 << CS53L30_MUTE_M3B_PDN_SHIFT)
+#define CS53L30_MUTE_M3B_PDN (1 << CS53L30_MUTE_M3B_PDN_SHIFT)
+#define CS53L30_MUTE_M2B_PDN_SHIFT 2
+#define CS53L30_MUTE_M2B_PDN_MASK (1 << CS53L30_MUTE_M2B_PDN_SHIFT)
+#define CS53L30_MUTE_M2B_PDN (1 << CS53L30_MUTE_M2B_PDN_SHIFT)
+#define CS53L30_MUTE_M1B_PDN_SHIFT 1
+#define CS53L30_MUTE_M1B_PDN_MASK (1 << CS53L30_MUTE_M1B_PDN_SHIFT)
+#define CS53L30_MUTE_M1B_PDN (1 << CS53L30_MUTE_M1B_PDN_SHIFT)
+/* Note: be careful - x starts from 0 */
+#define CS53L30_MUTE_MxB_PDN_SHIFT(x) (CS53L30_MUTE_M1B_PDN_SHIFT + (x))
+#define CS53L30_MUTE_MxB_PDN_MASK(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x))
+#define CS53L30_MUTE_MxB_PDN(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x))
+#define CS53L30_MUTE_MB_ALL_PDN_SHIFT 0
+#define CS53L30_MUTE_MB_ALL_PDN_MASK (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT)
+#define CS53L30_MUTE_MB_ALL_PDN (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT)
+
+#define CS53L30_MUTEP_CTL1_MUTEALL (0xdf)
+#define CS53L30_MUTEP_CTL1_DEFAULT (0)
+
+/* R32 (0x20) CS53L30_MUTEP_CTL2 - MUTE Pin Control 2 */
+#define CS53L30_MUTE_PIN_POLARITY_SHIFT 7
+#define CS53L30_MUTE_PIN_POLARITY_MASK (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT)
+#define CS53L30_MUTE_PIN_POLARITY (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT)
+#define CS53L30_MUTE_ASP_TDM_PDN_SHIFT 6
+#define CS53L30_MUTE_ASP_TDM_PDN_MASK (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_TDM_PDN (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT 5
+#define CS53L30_MUTE_ASP_SDOUT2_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_SDOUT2_PDN (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT 4
+#define CS53L30_MUTE_ASP_SDOUT1_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_SDOUT1_PDN (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT)
+/* Note: be careful - x starts from 0 */
+#define CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x) ((x) + CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT)
+#define CS53L30_MUTE_ASP_SDOUTx_PDN_MASK(x) (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x))
+#define CS53L30_MUTE_ASP_SDOUTx_PDN (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x))
+#define CS53L30_MUTE_ADC2B_PDN_SHIFT 3
+#define CS53L30_MUTE_ADC2B_PDN_MASK (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT)
+#define CS53L30_MUTE_ADC2B_PDN (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT)
+#define CS53L30_MUTE_ADC2A_PDN_SHIFT 2
+#define CS53L30_MUTE_ADC2A_PDN_MASK (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT)
+#define CS53L30_MUTE_ADC2A_PDN (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT)
+#define CS53L30_MUTE_ADC1B_PDN_SHIFT 1
+#define CS53L30_MUTE_ADC1B_PDN_MASK (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT)
+#define CS53L30_MUTE_ADC1B_PDN (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT)
+#define CS53L30_MUTE_ADC1A_PDN_SHIFT 0
+#define CS53L30_MUTE_ADC1A_PDN_MASK (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT)
+#define CS53L30_MUTE_ADC1A_PDN (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT)
+
+#define CS53L30_MUTEP_CTL2_DEFAULT (CS53L30_MUTE_PIN_POLARITY)
+
+/* R33 (0x21) CS53L30_INBIAS_CTL1 - Input Bias Control 1 */
+#define CS53L30_IN4M_BIAS_SHIFT 6
+#define CS53L30_IN4M_BIAS_WIDTH 2
+#define CS53L30_IN4M_BIAS_MASK (((1 << CS53L30_IN4M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT)
+#define CS53L30_IN4M_BIAS_OPEN (0 << CS53L30_IN4M_BIAS_SHIFT)
+#define CS53L30_IN4M_BIAS_PULL_DOWN (1 << CS53L30_IN4M_BIAS_SHIFT)
+#define CS53L30_IN4M_BIAS_VCM (2 << CS53L30_IN4M_BIAS_SHIFT)
+#define CS53L30_IN4P_BIAS_SHIFT 4
+#define CS53L30_IN4P_BIAS_WIDTH 2
+#define CS53L30_IN4P_BIAS_MASK (((1 << CS53L30_IN4P_BIAS_WIDTH) - 1) << CS53L30_IN4P_BIAS_SHIFT)
+#define CS53L30_IN4P_BIAS_OPEN (0 << CS53L30_IN4P_BIAS_SHIFT)
+#define CS53L30_IN4P_BIAS_PULL_DOWN (1 << CS53L30_IN4P_BIAS_SHIFT)
+#define CS53L30_IN4P_BIAS_VCM (2 << CS53L30_IN4P_BIAS_SHIFT)
+#define CS53L30_IN3M_BIAS_SHIFT 2
+#define CS53L30_IN3M_BIAS_WIDTH 2
+#define CS53L30_IN3M_BIAS_MASK (((1 << CS53L30_IN3M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT)
+#define CS53L30_IN3M_BIAS_OPEN (0 << CS53L30_IN3M_BIAS_SHIFT)
+#define CS53L30_IN3M_BIAS_PULL_DOWN (1 << CS53L30_IN3M_BIAS_SHIFT)
+#define CS53L30_IN3M_BIAS_VCM (2 << CS53L30_IN3M_BIAS_SHIFT)
+#define CS53L30_IN3P_BIAS_SHIFT 0
+#define CS53L30_IN3P_BIAS_WIDTH 2
+#define CS53L30_IN3P_BIAS_MASK (((1 << CS53L30_IN3P_BIAS_WIDTH) - 1) << CS53L30_IN3P_BIAS_SHIFT)
+#define CS53L30_IN3P_BIAS_OPEN (0 << CS53L30_IN3P_BIAS_SHIFT)
+#define CS53L30_IN3P_BIAS_PULL_DOWN (1 << CS53L30_IN3P_BIAS_SHIFT)
+#define CS53L30_IN3P_BIAS_VCM (2 << CS53L30_IN3P_BIAS_SHIFT)
+
+#define CS53L30_INBIAS_CTL1_DEFAULT (CS53L30_IN4M_BIAS_VCM | CS53L30_IN4P_BIAS_VCM |\
+ CS53L30_IN3M_BIAS_VCM | CS53L30_IN3P_BIAS_VCM)
+
+/* R34 (0x22) CS53L30_INBIAS_CTL2 - Input Bias Control 2 */
+#define CS53L30_IN2M_BIAS_SHIFT 6
+#define CS53L30_IN2M_BIAS_WIDTH 2
+#define CS53L30_IN2M_BIAS_MASK (((1 << CS53L30_IN2M_BIAS_WIDTH) - 1) << CS53L30_IN2M_BIAS_SHIFT)
+#define CS53L30_IN2M_BIAS_OPEN (0 << CS53L30_IN2M_BIAS_SHIFT)
+#define CS53L30_IN2M_BIAS_PULL_DOWN (1 << CS53L30_IN2M_BIAS_SHIFT)
+#define CS53L30_IN2M_BIAS_VCM (2 << CS53L30_IN2M_BIAS_SHIFT)
+#define CS53L30_IN2P_BIAS_SHIFT 4
+#define CS53L30_IN2P_BIAS_WIDTH 2
+#define CS53L30_IN2P_BIAS_MASK (((1 << CS53L30_IN2P_BIAS_WIDTH) - 1) << CS53L30_IN2P_BIAS_SHIFT)
+#define CS53L30_IN2P_BIAS_OPEN (0 << CS53L30_IN2P_BIAS_SHIFT)
+#define CS53L30_IN2P_BIAS_PULL_DOWN (1 << CS53L30_IN2P_BIAS_SHIFT)
+#define CS53L30_IN2P_BIAS_VCM (2 << CS53L30_IN2P_BIAS_SHIFT)
+#define CS53L30_IN1M_BIAS_SHIFT 2
+#define CS53L30_IN1M_BIAS_WIDTH 2
+#define CS53L30_IN1M_BIAS_MASK (((1 << CS53L30_IN1M_BIAS_WIDTH) - 1) << CS53L30_IN1M_BIAS_SHIFT)
+#define CS53L30_IN1M_BIAS_OPEN (0 << CS53L30_IN1M_BIAS_SHIFT)
+#define CS53L30_IN1M_BIAS_PULL_DOWN (1 << CS53L30_IN1M_BIAS_SHIFT)
+#define CS53L30_IN1M_BIAS_VCM (2 << CS53L30_IN1M_BIAS_SHIFT)
+#define CS53L30_IN1P_BIAS_SHIFT 0
+#define CS53L30_IN1P_BIAS_WIDTH 2
+#define CS53L30_IN1P_BIAS_MASK (((1 << CS53L30_IN1P_BIAS_WIDTH) - 1) << CS53L30_IN1P_BIAS_SHIFT)
+#define CS53L30_IN1P_BIAS_OPEN (0 << CS53L30_IN1P_BIAS_SHIFT)
+#define CS53L30_IN1P_BIAS_PULL_DOWN (1 << CS53L30_IN1P_BIAS_SHIFT)
+#define CS53L30_IN1P_BIAS_VCM (2 << CS53L30_IN1P_BIAS_SHIFT)
+
+#define CS53L30_INBIAS_CTL2_DEFAULT (CS53L30_IN2M_BIAS_VCM | CS53L30_IN2P_BIAS_VCM |\
+ CS53L30_IN1M_BIAS_VCM | CS53L30_IN1P_BIAS_VCM)
+
+/* R35 (0x23) & R36 (0x24) CS53L30_DMICx_STR_CTL - DMIC1 & DMIC2 Stereo Control */
+#define CS53L30_DMICx_STEREO_ENB_SHIFT 5
+#define CS53L30_DMICx_STEREO_ENB_MASK (1 << CS53L30_DMICx_STEREO_ENB_SHIFT)
+#define CS53L30_DMICx_STEREO_ENB (1 << CS53L30_DMICx_STEREO_ENB_SHIFT)
+
+/* 0x88 and 0xCC are reserved bits */
+#define CS53L30_DMIC1_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0x88)
+#define CS53L30_DMIC2_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0xCC)
+
+/* R37/R45 (0x25/0x2D) CS53L30_ADCDMICx_CTL1 - ADC1/DMIC1 & ADC2/DMIC2 Control 1 */
+#define CS53L30_ADCxB_PDN_SHIFT 7
+#define CS53L30_ADCxB_PDN_MASK (1 << CS53L30_ADCxB_PDN_SHIFT)
+#define CS53L30_ADCxB_PDN (1 << CS53L30_ADCxB_PDN_SHIFT)
+#define CS53L30_ADCxA_PDN_SHIFT 6
+#define CS53L30_ADCxA_PDN_MASK (1 << CS53L30_ADCxA_PDN_SHIFT)
+#define CS53L30_ADCxA_PDN (1 << CS53L30_ADCxA_PDN_SHIFT)
+#define CS53L30_DMICx_PDN_SHIFT 2
+#define CS53L30_DMICx_PDN_MASK (1 << CS53L30_DMICx_PDN_SHIFT)
+#define CS53L30_DMICx_PDN (1 << CS53L30_DMICx_PDN_SHIFT)
+#define CS53L30_DMICx_SCLK_DIV_SHIFT 1
+#define CS53L30_DMICx_SCLK_DIV_MASK (1 << CS53L30_DMICx_SCLK_DIV_SHIFT)
+#define CS53L30_DMICx_SCLK_DIV (1 << CS53L30_DMICx_SCLK_DIV_SHIFT)
+#define CS53L30_CH_TYPE_SHIFT 0
+#define CS53L30_CH_TYPE_MASK (1 << CS53L30_CH_TYPE_SHIFT)
+#define CS53L30_CH_TYPE (1 << CS53L30_CH_TYPE_SHIFT)
+
+#define CS53L30_ADCDMICx_PDN_MASK 0xFF
+#define CS53L30_ADCDMICx_CTL1_DEFAULT (CS53L30_DMICx_PDN)
+
+/* R38/R46 (0x26/0x2E) CS53L30_ADCDMICx_CTL2 - ADC1/DMIC1 & ADC2/DMIC2 Control 2 */
+#define CS53L30_ADCx_NOTCH_DIS_SHIFT 7
+#define CS53L30_ADCx_NOTCH_DIS_MASK (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT)
+#define CS53L30_ADCx_NOTCH_DIS (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT)
+#define CS53L30_ADCxB_INV_SHIFT 5
+#define CS53L30_ADCxB_INV_MASK (1 << CS53L30_ADCxB_INV_SHIFT)
+#define CS53L30_ADCxB_INV (1 << CS53L30_ADCxB_INV_SHIFT)
+#define CS53L30_ADCxA_INV_SHIFT 4
+#define CS53L30_ADCxA_INV_MASK (1 << CS53L30_ADCxA_INV_SHIFT)
+#define CS53L30_ADCxA_INV (1 << CS53L30_ADCxA_INV_SHIFT)
+#define CS53L30_ADCxB_DIG_BOOST_SHIFT 1
+#define CS53L30_ADCxB_DIG_BOOST_MASK (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT)
+#define CS53L30_ADCxB_DIG_BOOST (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT)
+#define CS53L30_ADCxA_DIG_BOOST_SHIFT 0
+#define CS53L30_ADCxA_DIG_BOOST_MASK (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT)
+#define CS53L30_ADCxA_DIG_BOOST (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT)
+
+#define CS53L30_ADCDMIC1_CTL2_DEFAULT (0)
+
+/* R39/R47 (0x27/0x2F) CS53L30_ADCx_CTL3 - ADC1/ADC2 Control 3 */
+#define CS53L30_ADCx_HPF_EN_SHIFT 3
+#define CS53L30_ADCx_HPF_EN_MASK (1 << CS53L30_ADCx_HPF_EN_SHIFT)
+#define CS53L30_ADCx_HPF_EN (1 << CS53L30_ADCx_HPF_EN_SHIFT)
+#define CS53L30_ADCx_HPF_CF_SHIFT 1
+#define CS53L30_ADCx_HPF_CF_WIDTH 2
+#define CS53L30_ADCx_HPF_CF_MASK (((1 << CS53L30_ADCx_HPF_CF_WIDTH) - 1) << CS53L30_ADCx_HPF_CF_SHIFT)
+#define CS53L30_ADCx_HPF_CF_1HZ86 (0 << CS53L30_ADCx_HPF_CF_SHIFT)
+#define CS53L30_ADCx_HPF_CF_120HZ (1 << CS53L30_ADCx_HPF_CF_SHIFT)
+#define CS53L30_ADCx_HPF_CF_235HZ (2 << CS53L30_ADCx_HPF_CF_SHIFT)
+#define CS53L30_ADCx_HPF_CF_466HZ (3 << CS53L30_ADCx_HPF_CF_SHIFT)
+#define CS53L30_ADCx_NG_ALL_SHIFT 0
+#define CS53L30_ADCx_NG_ALL_MASK (1 << CS53L30_ADCx_NG_ALL_SHIFT)
+#define CS53L30_ADCx_NG_ALL (1 << CS53L30_ADCx_NG_ALL_SHIFT)
+
+#define CS53L30_ADCx_CTL3_DEFAULT (CS53L30_ADCx_HPF_EN)
+
+/* R40/R48 (0x28/0x30) CS53L30_ADCx_NG_CTL - ADC1/ADC2 Noise Gate Control */
+#define CS53L30_ADCxB_NG_SHIFT 7
+#define CS53L30_ADCxB_NG_MASK (1 << CS53L30_ADCxB_NG_SHIFT)
+#define CS53L30_ADCxB_NG (1 << CS53L30_ADCxB_NG_SHIFT)
+#define CS53L30_ADCxA_NG_SHIFT 6
+#define CS53L30_ADCxA_NG_MASK (1 << CS53L30_ADCxA_NG_SHIFT)
+#define CS53L30_ADCxA_NG (1 << CS53L30_ADCxA_NG_SHIFT)
+#define CS53L30_ADCx_NG_BOOST_SHIFT 5
+#define CS53L30_ADCx_NG_BOOST_MASK (1 << CS53L30_ADCx_NG_BOOST_SHIFT)
+#define CS53L30_ADCx_NG_BOOST (1 << CS53L30_ADCx_NG_BOOST_SHIFT)
+#define CS53L30_ADCx_NG_THRESH_SHIFT 2
+#define CS53L30_ADCx_NG_THRESH_WIDTH 3
+#define CS53L30_ADCx_NG_THRESH_MASK (((1 << CS53L30_ADCx_NG_THRESH_WIDTH) - 1) << CS53L30_ADCx_NG_THRESH_SHIFT)
+#define CS53L30_ADCx_NG_DELAY_SHIFT 0
+#define CS53L30_ADCx_NG_DELAY_WIDTH 2
+#define CS53L30_ADCx_NG_DELAY_MASK (((1 << CS53L30_ADCx_NG_DELAY_WIDTH) - 1) << CS53L30_ADCx_NG_DELAY_SHIFT)
+
+#define CS53L30_ADCx_NG_CTL_DEFAULT (0)
+
+/* R41/R42/R49/R50 (0x29/0x2A/0x31/0x32) CS53L30_ADCxy_AFE_CTL - ADC1A/1B/2A/2B AFE Control */
+#define CS53L30_ADCxy_PREAMP_SHIFT 6
+#define CS53L30_ADCxy_PREAMP_WIDTH 2
+#define CS53L30_ADCxy_PREAMP_MASK (((1 << CS53L30_ADCxy_PREAMP_WIDTH) - 1) << CS53L30_ADCxy_PREAMP_SHIFT)
+#define CS53L30_ADCxy_PGA_VOL_SHIFT 0
+#define CS53L30_ADCxy_PGA_VOL_WIDTH 6
+#define CS53L30_ADCxy_PGA_VOL_MASK (((1 << CS53L30_ADCxy_PGA_VOL_WIDTH) - 1) << CS53L30_ADCxy_PGA_VOL_SHIFT)
+
+#define CS53L30_ADCxy_AFE_CTL_DEFAULT (0)
+
+/* R43/R44/R51/R52 (0x2B/0x2C/0x33/0x34) CS53L30_ADCxy_DIG_VOL - ADC1A/1B/2A/2B Digital Volume */
+#define CS53L30_ADCxy_VOL_MUTE (0x80)
+
+#define CS53L30_ADCxy_DIG_VOL_DEFAULT (0x0)
+
+/* CS53L30_INT */
+#define CS53L30_PDN_DONE (1 << 7)
+#define CS53L30_THMS_TRIP (1 << 6)
+#define CS53L30_SYNC_DONE (1 << 5)
+#define CS53L30_ADC2B_OVFL (1 << 4)
+#define CS53L30_ADC2A_OVFL (1 << 3)
+#define CS53L30_ADC1B_OVFL (1 << 2)
+#define CS53L30_ADC1A_OVFL (1 << 1)
+#define CS53L30_MUTE_PIN (1 << 0)
+#define CS53L30_DEVICE_INT_MASK 0xFF
+
+#endif /* __CS53L30_H__ */
diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c
index d6f4abbbf8a7..fb3885fe0afb 100644
--- a/sound/soc/codecs/cx20442.c
+++ b/sound/soc/codecs/cx20442.c
@@ -226,6 +226,7 @@ static int v253_open(struct tty_struct *tty)
if (!tty->disc_data)
return -ENODEV;
+ tty->receive_room = 16;
if (tty->ops->write(tty, v253_init, len) != len) {
ret = -EIO;
goto err;
diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c
index 9459593eef13..f0057cd223a4 100644
--- a/sound/soc/codecs/da7219-aad.c
+++ b/sound/soc/codecs/da7219-aad.c
@@ -13,8 +13,8 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/of_device.h>
-#include <linux/of_irq.h>
+#include <linux/i2c.h>
+#include <linux/property.h>
#include <linux/pm_wakeirq.h>
#include <linux/slab.h>
#include <linux/delay.h>
@@ -382,11 +382,11 @@ static irqreturn_t da7219_aad_irq_thread(int irq, void *data)
}
/*
- * DT to pdata conversion
+ * DT/ACPI to pdata conversion
*/
static enum da7219_aad_micbias_pulse_lvl
- da7219_aad_of_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 2800:
@@ -400,7 +400,7 @@ static enum da7219_aad_micbias_pulse_lvl
}
static enum da7219_aad_btn_cfg
- da7219_aad_of_btn_cfg(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_btn_cfg(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 2:
@@ -424,7 +424,7 @@ static enum da7219_aad_btn_cfg
}
static enum da7219_aad_mic_det_thr
- da7219_aad_of_mic_det_thr(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_mic_det_thr(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 200:
@@ -442,7 +442,7 @@ static enum da7219_aad_mic_det_thr
}
static enum da7219_aad_jack_ins_deb
- da7219_aad_of_jack_ins_deb(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_jack_ins_deb(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 5:
@@ -468,7 +468,7 @@ static enum da7219_aad_jack_ins_deb
}
static enum da7219_aad_jack_det_rate
- da7219_aad_of_jack_det_rate(struct snd_soc_codec *codec, const char *str)
+ da7219_aad_fw_jack_det_rate(struct snd_soc_codec *codec, const char *str)
{
if (!strcmp(str, "32ms_64ms")) {
return DA7219_AAD_JACK_DET_RATE_32_64MS;
@@ -485,7 +485,7 @@ static enum da7219_aad_jack_det_rate
}
static enum da7219_aad_jack_rem_deb
- da7219_aad_of_jack_rem_deb(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_jack_rem_deb(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 1:
@@ -503,7 +503,7 @@ static enum da7219_aad_jack_rem_deb
}
static enum da7219_aad_btn_avg
- da7219_aad_of_btn_avg(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_btn_avg(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 1:
@@ -521,7 +521,7 @@ static enum da7219_aad_btn_avg
}
static enum da7219_aad_adc_1bit_rpt
- da7219_aad_of_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val)
+ da7219_aad_fw_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val)
{
switch (val) {
case 1:
@@ -538,97 +538,96 @@ static enum da7219_aad_adc_1bit_rpt
}
}
-static struct da7219_aad_pdata *da7219_aad_of_to_pdata(struct snd_soc_codec *codec)
+static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct snd_soc_codec *codec)
{
- struct device_node *np = codec->dev->of_node;
- struct device_node *aad_np = of_find_node_by_name(np, "da7219_aad");
+ struct device *dev = codec->dev;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct fwnode_handle *aad_np;
struct da7219_aad_pdata *aad_pdata;
- const char *of_str;
- u32 of_val32;
+ const char *fw_str;
+ u32 fw_val32;
+ aad_np = device_get_named_child_node(dev, "da7219_aad");
if (!aad_np)
return NULL;
aad_pdata = devm_kzalloc(codec->dev, sizeof(*aad_pdata), GFP_KERNEL);
if (!aad_pdata)
- goto out;
+ return NULL;
- aad_pdata->irq = irq_of_parse_and_map(np, 0);
+ aad_pdata->irq = i2c->irq;
- if (of_property_read_u32(aad_np, "dlg,micbias-pulse-lvl",
- &of_val32) >= 0)
+ if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-lvl",
+ &fw_val32) >= 0)
aad_pdata->micbias_pulse_lvl =
- da7219_aad_of_micbias_pulse_lvl(codec, of_val32);
+ da7219_aad_fw_micbias_pulse_lvl(codec, fw_val32);
else
aad_pdata->micbias_pulse_lvl = DA7219_AAD_MICBIAS_PULSE_LVL_OFF;
- if (of_property_read_u32(aad_np, "dlg,micbias-pulse-time",
- &of_val32) >= 0)
- aad_pdata->micbias_pulse_time = of_val32;
+ if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-time",
+ &fw_val32) >= 0)
+ aad_pdata->micbias_pulse_time = fw_val32;
- if (of_property_read_u32(aad_np, "dlg,btn-cfg", &of_val32) >= 0)
- aad_pdata->btn_cfg = da7219_aad_of_btn_cfg(codec, of_val32);
+ if (fwnode_property_read_u32(aad_np, "dlg,btn-cfg", &fw_val32) >= 0)
+ aad_pdata->btn_cfg = da7219_aad_fw_btn_cfg(codec, fw_val32);
else
aad_pdata->btn_cfg = DA7219_AAD_BTN_CFG_10MS;
- if (of_property_read_u32(aad_np, "dlg,mic-det-thr", &of_val32) >= 0)
+ if (fwnode_property_read_u32(aad_np, "dlg,mic-det-thr", &fw_val32) >= 0)
aad_pdata->mic_det_thr =
- da7219_aad_of_mic_det_thr(codec, of_val32);
+ da7219_aad_fw_mic_det_thr(codec, fw_val32);
else
aad_pdata->mic_det_thr = DA7219_AAD_MIC_DET_THR_500_OHMS;
- if (of_property_read_u32(aad_np, "dlg,jack-ins-deb", &of_val32) >= 0)
+ if (fwnode_property_read_u32(aad_np, "dlg,jack-ins-deb", &fw_val32) >= 0)
aad_pdata->jack_ins_deb =
- da7219_aad_of_jack_ins_deb(codec, of_val32);
+ da7219_aad_fw_jack_ins_deb(codec, fw_val32);
else
aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS;
- if (!of_property_read_string(aad_np, "dlg,jack-det-rate", &of_str))
+ if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str))
aad_pdata->jack_det_rate =
- da7219_aad_of_jack_det_rate(codec, of_str);
+ da7219_aad_fw_jack_det_rate(codec, fw_str);
else
aad_pdata->jack_det_rate = DA7219_AAD_JACK_DET_RATE_256_512MS;
- if (of_property_read_u32(aad_np, "dlg,jack-rem-deb", &of_val32) >= 0)
+ if (fwnode_property_read_u32(aad_np, "dlg,jack-rem-deb", &fw_val32) >= 0)
aad_pdata->jack_rem_deb =
- da7219_aad_of_jack_rem_deb(codec, of_val32);
+ da7219_aad_fw_jack_rem_deb(codec, fw_val32);
else
aad_pdata->jack_rem_deb = DA7219_AAD_JACK_REM_DEB_1MS;
- if (of_property_read_u32(aad_np, "dlg,a-d-btn-thr", &of_val32) >= 0)
- aad_pdata->a_d_btn_thr = (u8) of_val32;
+ if (fwnode_property_read_u32(aad_np, "dlg,a-d-btn-thr", &fw_val32) >= 0)
+ aad_pdata->a_d_btn_thr = (u8) fw_val32;
else
aad_pdata->a_d_btn_thr = 0xA;
- if (of_property_read_u32(aad_np, "dlg,d-b-btn-thr", &of_val32) >= 0)
- aad_pdata->d_b_btn_thr = (u8) of_val32;
+ if (fwnode_property_read_u32(aad_np, "dlg,d-b-btn-thr", &fw_val32) >= 0)
+ aad_pdata->d_b_btn_thr = (u8) fw_val32;
else
aad_pdata->d_b_btn_thr = 0x16;
- if (of_property_read_u32(aad_np, "dlg,b-c-btn-thr", &of_val32) >= 0)
- aad_pdata->b_c_btn_thr = (u8) of_val32;
+ if (fwnode_property_read_u32(aad_np, "dlg,b-c-btn-thr", &fw_val32) >= 0)
+ aad_pdata->b_c_btn_thr = (u8) fw_val32;
else
aad_pdata->b_c_btn_thr = 0x21;
- if (of_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &of_val32) >= 0)
- aad_pdata->c_mic_btn_thr = (u8) of_val32;
+ if (fwnode_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &fw_val32) >= 0)
+ aad_pdata->c_mic_btn_thr = (u8) fw_val32;
else
aad_pdata->c_mic_btn_thr = 0x3E;
- if (of_property_read_u32(aad_np, "dlg,btn-avg", &of_val32) >= 0)
- aad_pdata->btn_avg = da7219_aad_of_btn_avg(codec, of_val32);
+ if (fwnode_property_read_u32(aad_np, "dlg,btn-avg", &fw_val32) >= 0)
+ aad_pdata->btn_avg = da7219_aad_fw_btn_avg(codec, fw_val32);
else
aad_pdata->btn_avg = DA7219_AAD_BTN_AVG_2;
- if (of_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &of_val32) >= 0)
+ if (fwnode_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &fw_val32) >= 0)
aad_pdata->adc_1bit_rpt =
- da7219_aad_of_adc_1bit_rpt(codec, of_val32);
+ da7219_aad_fw_adc_1bit_rpt(codec, fw_val32);
else
aad_pdata->adc_1bit_rpt = DA7219_AAD_ADC_1BIT_RPT_1;
-out:
- of_node_put(aad_np);
-
return aad_pdata;
}
@@ -769,9 +768,9 @@ int da7219_aad_init(struct snd_soc_codec *codec)
da7219->aad = da7219_aad;
da7219_aad->codec = codec;
- /* Handle any DT/platform data */
- if ((codec->dev->of_node) && (da7219->pdata))
- da7219->pdata->aad_pdata = da7219_aad_of_to_pdata(codec);
+ /* Handle any DT/ACPI/platform data */
+ if (da7219->pdata && !da7219->pdata->aad_pdata)
+ da7219->pdata->aad_pdata = da7219_aad_fw_to_pdata(codec);
da7219_aad_handle_pdata(codec);
diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c
index 5c93899f1f0e..50ea94317cb3 100644
--- a/sound/soc/codecs/da7219.c
+++ b/sound/soc/codecs/da7219.c
@@ -15,6 +15,7 @@
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/of_device.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/pm.h>
@@ -1418,7 +1419,7 @@ static struct snd_soc_dai_driver da7219_dai = {
/*
- * DT
+ * DT/ACPI
*/
static const struct of_device_id da7219_of_match[] = {
@@ -1434,7 +1435,7 @@ static const struct acpi_device_id da7219_acpi_match[] = {
MODULE_DEVICE_TABLE(acpi, da7219_acpi_match);
static enum da7219_micbias_voltage
- da7219_of_micbias_lvl(struct snd_soc_codec *codec, u32 val)
+ da7219_fw_micbias_lvl(struct device *dev, u32 val)
{
switch (val) {
case 1600:
@@ -1450,13 +1451,13 @@ static enum da7219_micbias_voltage
case 2600:
return DA7219_MICBIAS_2_6V;
default:
- dev_warn(codec->dev, "Invalid micbias level");
+ dev_warn(dev, "Invalid micbias level");
return DA7219_MICBIAS_2_2V;
}
}
static enum da7219_mic_amp_in_sel
- da7219_of_mic_amp_in_sel(struct snd_soc_codec *codec, const char *str)
+ da7219_fw_mic_amp_in_sel(struct device *dev, const char *str)
{
if (!strcmp(str, "diff")) {
return DA7219_MIC_AMP_IN_SEL_DIFF;
@@ -1465,29 +1466,29 @@ static enum da7219_mic_amp_in_sel
} else if (!strcmp(str, "se_n")) {
return DA7219_MIC_AMP_IN_SEL_SE_N;
} else {
- dev_warn(codec->dev, "Invalid mic input type selection");
+ dev_warn(dev, "Invalid mic input type selection");
return DA7219_MIC_AMP_IN_SEL_DIFF;
}
}
-static struct da7219_pdata *da7219_of_to_pdata(struct snd_soc_codec *codec)
+static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_codec *codec)
{
- struct device_node *np = codec->dev->of_node;
+ struct device *dev = codec->dev;
struct da7219_pdata *pdata;
const char *of_str;
u32 of_val32;
- pdata = devm_kzalloc(codec->dev, sizeof(*pdata), GFP_KERNEL);
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
- if (of_property_read_u32(np, "dlg,micbias-lvl", &of_val32) >= 0)
- pdata->micbias_lvl = da7219_of_micbias_lvl(codec, of_val32);
+ if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0)
+ pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32);
else
pdata->micbias_lvl = DA7219_MICBIAS_2_2V;
- if (!of_property_read_string(np, "dlg,mic-amp-in-sel", &of_str))
- pdata->mic_amp_in_sel = da7219_of_mic_amp_in_sel(codec, of_str);
+ if (!device_property_read_string(dev, "dlg,mic-amp-in-sel", &of_str))
+ pdata->mic_amp_in_sel = da7219_fw_mic_amp_in_sel(dev, of_str);
else
pdata->mic_amp_in_sel = DA7219_MIC_AMP_IN_SEL_DIFF;
@@ -1662,11 +1663,10 @@ static int da7219_probe(struct snd_soc_codec *codec)
break;
}
- /* Handle DT/Platform data */
- if (codec->dev->of_node)
- da7219->pdata = da7219_of_to_pdata(codec);
- else
- da7219->pdata = dev_get_platdata(codec->dev);
+ /* Handle DT/ACPI/Platform data */
+ da7219->pdata = dev_get_platdata(codec->dev);
+ if (!da7219->pdata)
+ da7219->pdata = da7219_fw_to_pdata(codec);
da7219_handle_pdata(codec);
diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c
index 181cd3bf0b92..4e181b270d95 100644
--- a/sound/soc/codecs/hdac_hdmi.c
+++ b/sound/soc/codecs/hdac_hdmi.c
@@ -1124,8 +1124,10 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
}
hdac_hdmi_parse_eld(edev, pin);
- print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET,
- pin->eld.eld_buffer, pin->eld.eld_size);
+ print_hex_dump_debug("ELD: ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ pin->eld.eld_buffer, pin->eld.eld_size,
+ true);
} else {
pin->eld.monitor_present = false;
pin->eld.eld_valid = false;
@@ -1474,6 +1476,11 @@ static int hdmi_codec_probe(struct snd_soc_codec *codec)
* exit, we call pm_runtime_suspend() so that will do for us
*/
hlink = snd_hdac_ext_bus_get_link(edev->ebus, dev_name(&edev->hdac.dev));
+ if (!hlink) {
+ dev_err(&edev->hdac.dev, "hdac link not found\n");
+ return -EIO;
+ }
+
snd_hdac_ext_bus_link_get(edev->ebus, hlink);
ret = create_fill_widget_route_map(dapm);
@@ -1634,6 +1641,11 @@ static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
/* hold the ref while we probe */
hlink = snd_hdac_ext_bus_get_link(edev->ebus, dev_name(&edev->hdac.dev));
+ if (!hlink) {
+ dev_err(&edev->hdac.dev, "hdac link not found\n");
+ return -EIO;
+ }
+
snd_hdac_ext_bus_link_get(edev->ebus, hlink);
hdmi_priv = devm_kzalloc(&codec->dev, sizeof(*hdmi_priv), GFP_KERNEL);
@@ -1744,6 +1756,11 @@ static int hdac_hdmi_runtime_suspend(struct device *dev)
}
hlink = snd_hdac_ext_bus_get_link(ebus, dev_name(dev));
+ if (!hlink) {
+ dev_err(dev, "hdac link not found\n");
+ return -EIO;
+ }
+
snd_hdac_ext_bus_link_put(ebus, hlink);
return 0;
@@ -1765,6 +1782,11 @@ static int hdac_hdmi_runtime_resume(struct device *dev)
return 0;
hlink = snd_hdac_ext_bus_get_link(ebus, dev_name(dev));
+ if (!hlink) {
+ dev_err(dev, "hdac link not found\n");
+ return -EIO;
+ }
+
snd_hdac_ext_bus_link_get(ebus, hlink);
err = snd_hdac_display_power(bus, true);
@@ -1796,6 +1818,7 @@ static const struct dev_pm_ops hdac_hdmi_pm = {
static const struct hda_device_id hdmi_list[] = {
HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0),
HDA_CODEC_EXT_ENTRY(0x8086280a, 0x100000, "Broxton HDMI", 0),
+ HDA_CODEC_EXT_ENTRY(0x8086280b, 0x100000, "Kabylake HDMI", 0),
{}
};
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
index 8e36e883e453..f27d115626db 100644
--- a/sound/soc/codecs/hdmi-codec.c
+++ b/sound/soc/codecs/hdmi-codec.c
@@ -112,7 +112,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
return ret;
if (hcp->hcd.ops->audio_startup) {
- ret = hcp->hcd.ops->audio_startup(dai->dev->parent);
+ ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data);
if (ret) {
mutex_lock(&hcp->current_stream_lock);
hcp->current_stream = NULL;
@@ -122,8 +122,8 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
}
if (hcp->hcd.ops->get_eld) {
- ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->eld,
- sizeof(hcp->eld));
+ ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data,
+ hcp->eld, sizeof(hcp->eld));
if (!ret) {
ret = snd_pcm_hw_constraint_eld(substream->runtime,
@@ -144,7 +144,7 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream,
WARN_ON(hcp->current_stream != substream);
- hcp->hcd.ops->audio_shutdown(dai->dev->parent);
+ hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data);
mutex_lock(&hcp->current_stream_lock);
hcp->current_stream = NULL;
@@ -195,8 +195,8 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
hp.sample_rate = params_rate(params);
hp.channels = params_channels(params);
- return hcp->hcd.ops->hw_params(dai->dev->parent, &hcp->daifmt[dai->id],
- &hp);
+ return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data,
+ &hcp->daifmt[dai->id], &hp);
}
static int hdmi_codec_set_fmt(struct snd_soc_dai *dai,
@@ -280,7 +280,8 @@ static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute)
dev_dbg(dai->dev, "%s()\n", __func__);
if (hcp->hcd.ops->digital_mute)
- return hcp->hcd.ops->digital_mute(dai->dev->parent, mute);
+ return hcp->hcd.ops->digital_mute(dai->dev->parent,
+ hcp->hcd.data, mute);
return 0;
}
diff --git a/sound/soc/codecs/max98504.c b/sound/soc/codecs/max98504.c
new file mode 100644
index 000000000000..a7320e709890
--- /dev/null
+++ b/sound/soc/codecs/max98504.c
@@ -0,0 +1,383 @@
+/*
+ * MAX98504 ALSA SoC Audio driver
+ *
+ * Copyright 2013 - 2014 Maxim Integrated Products
+ * Copyright 2016 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <sound/soc.h>
+
+#include "max98504.h"
+
+static const char * const max98504_supply_names[] = {
+ "DVDD",
+ "DIOVDD",
+ "PVDD",
+};
+#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names)
+
+struct max98504_priv {
+ struct regmap *regmap;
+ struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES];
+ unsigned int pcm_rx_channels;
+ bool brownout_enable;
+ unsigned int brownout_threshold;
+ unsigned int brownout_attenuation;
+ unsigned int brownout_attack_hold;
+ unsigned int brownout_timed_hold;
+ unsigned int brownout_release_rate;
+};
+
+static struct reg_default max98504_reg_defaults[] = {
+ { 0x01, 0},
+ { 0x02, 0},
+ { 0x03, 0},
+ { 0x04, 0},
+ { 0x10, 0},
+ { 0x11, 0},
+ { 0x12, 0},
+ { 0x13, 0},
+ { 0x14, 0},
+ { 0x15, 0},
+ { 0x16, 0},
+ { 0x17, 0},
+ { 0x18, 0},
+ { 0x19, 0},
+ { 0x1A, 0},
+ { 0x20, 0},
+ { 0x21, 0},
+ { 0x22, 0},
+ { 0x23, 0},
+ { 0x24, 0},
+ { 0x25, 0},
+ { 0x26, 0},
+ { 0x27, 0},
+ { 0x28, 0},
+ { 0x30, 0},
+ { 0x31, 0},
+ { 0x32, 0},
+ { 0x33, 0},
+ { 0x34, 0},
+ { 0x35, 0},
+ { 0x36, 0},
+ { 0x37, 0},
+ { 0x38, 0},
+ { 0x39, 0},
+ { 0x40, 0},
+ { 0x41, 0},
+};
+
+static bool max98504_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX98504_INTERRUPT_STATUS:
+ case MAX98504_INTERRUPT_FLAGS:
+ case MAX98504_INTERRUPT_FLAG_CLEARS:
+ case MAX98504_WATCHDOG_CLEAR:
+ case MAX98504_GLOBAL_ENABLE:
+ case MAX98504_SOFTWARE_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool max98504_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX98504_SOFTWARE_RESET:
+ case MAX98504_WATCHDOG_CLEAR:
+ case MAX98504_INTERRUPT_FLAG_CLEARS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
+ struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE,
+ max98504->pcm_rx_channels);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int max98504_component_probe(struct snd_soc_component *c)
+{
+ struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
+ struct regmap *map = max98504->regmap;
+ int ret;
+
+ ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies);
+ if (ret < 0)
+ return ret;
+
+ regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1);
+ msleep(20);
+
+ if (!max98504->brownout_enable)
+ return 0;
+
+ regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1);
+
+ regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1,
+ (max98504->brownout_threshold & 0x1f) << 3 |
+ (max98504->brownout_attenuation & 0x3));
+
+ regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2,
+ max98504->brownout_attack_hold & 0xff);
+
+ regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3,
+ max98504->brownout_timed_hold & 0xff);
+
+ regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4,
+ max98504->brownout_release_rate & 0xff);
+
+ return 0;
+}
+
+static void max98504_component_remove(struct snd_soc_component *c)
+{
+ struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
+
+ regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies);
+}
+
+static const char *spk_source_mux_text[] = {
+ "PCM Monomix", "Analog In", "PDM Left", "PDM Right"
+};
+
+static const struct soc_enum spk_source_mux_enum =
+ SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT,
+ 0, ARRAY_SIZE(spk_source_mux_text),
+ spk_source_mux_text);
+
+static const struct snd_kcontrol_new spk_source_mux =
+ SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum);
+
+static const struct snd_soc_dapm_route max98504_dapm_routes[] = {
+ { "SPKOUT", NULL, "Global Enable" },
+ { "SPK Source", "PCM Monomix", "DAC PCM" },
+ { "SPK Source", "Analog In", "AIN" },
+ { "SPK Source", "PDM Left", "DAC PDM" },
+ { "SPK Source", "PDM Right", "DAC PDM" },
+};
+
+static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE,
+ 0, 0, NULL, 0),
+ SND_SOC_DAPM_INPUT("AIN"),
+ SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0,
+ max98504_pcm_rx_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0),
+ SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux),
+ SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT",
+ MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0),
+};
+
+static int max98504_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
+ struct regmap *map = max98504->regmap;
+
+
+ switch (dai->id) {
+ case MAX98504_DAI_ID_PCM:
+ regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask);
+ max98504->pcm_rx_channels = rx_mask;
+ break;
+
+ case MAX98504_DAI_ID_PDM:
+ regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask);
+ break;
+ default:
+ WARN_ON(1);
+ }
+
+ return 0;
+}
+static int max98504_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, unsigned int *tx_slot,
+ unsigned int rx_num, unsigned int *rx_slot)
+{
+ struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
+ struct regmap *map = max98504->regmap;
+ unsigned int i, sources = 0;
+
+ for (i = 0; i < tx_num; i++)
+ if (tx_slot[i])
+ sources |= (1 << i);
+
+ switch (dai->id) {
+ case MAX98504_DAI_ID_PCM:
+ regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES,
+ sources);
+ break;
+
+ case MAX98504_DAI_ID_PDM:
+ regmap_write(map, MAX98504_PDM_TX_CONTROL, sources);
+ break;
+ default:
+ WARN_ON(1);
+ }
+
+ regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops max98504_dai_ops = {
+ .set_tdm_slot = max98504_set_tdm_slot,
+ .set_channel_map = max98504_set_channel_map,
+};
+
+#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\
+ SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE)
+#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
+ SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\
+ SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\
+ SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_driver max98504_dai[] = {
+ /* TODO: Add the PCM interface definitions */
+ {
+ .name = "max98504-aif2",
+ .id = MAX98504_DAI_ID_PDM,
+ .playback = {
+ .stream_name = "AIF2 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98504_PDM_RATES,
+ .formats = MAX98504_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AIF2 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX98504_PDM_RATES,
+ .formats = MAX98504_FORMATS,
+ },
+ .ops = &max98504_dai_ops,
+ },
+};
+
+static const struct snd_soc_component_driver max98504_component_driver = {
+ .probe = max98504_component_probe,
+ .remove = max98504_component_remove,
+ .dapm_widgets = max98504_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets),
+ .dapm_routes = max98504_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes),
+};
+
+static const struct regmap_config max98504_regmap = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = MAX98504_MAX_REGISTER,
+ .reg_defaults = max98504_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults),
+ .volatile_reg = max98504_volatile_register,
+ .readable_reg = max98504_readable_register,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int max98504_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct device_node *node = dev->of_node;
+ struct max98504_priv *max98504;
+ int i, ret;
+
+ max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL);
+ if (!max98504)
+ return -ENOMEM;
+
+ if (node) {
+ if (!of_property_read_u32(node, "maxim,brownout-threshold",
+ &max98504->brownout_threshold))
+ max98504->brownout_enable = true;
+
+ of_property_read_u32(node, "maxim,brownout-attenuation",
+ &max98504->brownout_attenuation);
+ of_property_read_u32(node, "maxim,brownout-attack-hold-ms",
+ &max98504->brownout_attack_hold);
+ of_property_read_u32(node, "maxim,brownout-timed-hold-ms",
+ &max98504->brownout_timed_hold);
+ of_property_read_u32(node, "maxim,brownout-release-rate-ms",
+ &max98504->brownout_release_rate);
+ }
+
+ max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap);
+ if (IS_ERR(max98504->regmap)) {
+ ret = PTR_ERR(max98504->regmap);
+ dev_err(&client->dev, "regmap initialization failed: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < MAX98504_NUM_SUPPLIES; i++)
+ max98504->supplies[i].supply = max98504_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES,
+ max98504->supplies);
+ if (ret < 0)
+ return ret;
+
+ i2c_set_clientdata(client, max98504);
+
+ return devm_snd_soc_register_component(dev, &max98504_component_driver,
+ max98504_dai, ARRAY_SIZE(max98504_dai));
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id max98504_of_match[] = {
+ { .compatible = "maxim,max98504" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max98504_of_match);
+#endif
+
+static const struct i2c_device_id max98504_i2c_id[] = {
+ { "max98504" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max98504_i2c_id);
+
+static struct i2c_driver max98504_i2c_driver = {
+ .driver = {
+ .name = "max98504",
+ .of_match_table = of_match_ptr(max98504_of_match),
+ },
+ .probe = max98504_i2c_probe,
+ .id_table = max98504_i2c_id,
+};
+module_i2c_driver(max98504_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC MAX98504 driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98504.h b/sound/soc/codecs/max98504.h
new file mode 100644
index 000000000000..afbefad2d5ce
--- /dev/null
+++ b/sound/soc/codecs/max98504.h
@@ -0,0 +1,59 @@
+/*
+ * MAX98504 ALSA SoC Audio driver
+ *
+ * Copyright 2011 - 2012 Maxim Integrated Products
+ * Copyright 2016 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef MAX98504_H_
+#define MAX98504_H_
+
+/*
+ * MAX98504 Register Definitions
+ */
+#define MAX98504_INTERRUPT_STATUS 0x01
+#define MAX98504_INTERRUPT_FLAGS 0x02
+#define MAX98504_INTERRUPT_ENABLE 0x03
+#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04
+#define MAX98504_GPIO_ENABLE 0x10
+#define MAX98504_GPIO_CONFIG 0x11
+#define MAX98504_WATCHDOG_ENABLE 0x12
+#define MAX98504_WATCHDOG_CONFIG 0x13
+#define MAX98504_WATCHDOG_CLEAR 0x14
+#define MAX98504_CLOCK_MONITOR_ENABLE 0x15
+#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16
+#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17
+#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18
+#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19
+#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a
+#define MAX98504_PCM_RX_ENABLE 0x20
+#define MAX98504_PCM_TX_ENABLE 0x21
+#define MAX98504_PCM_TX_HIZ_CONTROL 0x22
+#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23
+#define MAX98504_PCM_MODE_CONFIG 0x24
+#define MAX98504_PCM_DSP_CONFIG 0x25
+#define MAX98504_PCM_CLOCK_SETUP 0x26
+#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27
+#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28
+#define MAX98504_PDM_TX_ENABLE 0x30
+#define MAX98504_PDM_TX_HIZ_CONTROL 0x31
+#define MAX98504_PDM_TX_CONTROL 0x32
+#define MAX98504_PDM_RX_ENABLE 0x33
+#define MAX98504_SPEAKER_ENABLE 0x34
+#define MAX98504_SPEAKER_SOURCE_SELECT 0x35
+#define MAX98504_MEASUREMENT_ENABLE 0x36
+#define MAX98504_ANALOGUE_INPUT_GAIN 0x37
+#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38
+#define MAX98504_GLOBAL_ENABLE 0x40
+#define MAX98504_SOFTWARE_RESET 0x41
+#define MAX98504_REV_ID 0x7fff
+
+#define MAX98504_MAX_REGISTER 0x7fff
+
+#define MAX98504_DAI_ID_PCM 1
+#define MAX98504_DAI_ID_PDM 2
+
+#endif /* MAX98504_H_ */
diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c
new file mode 100644
index 000000000000..68074c92a7c0
--- /dev/null
+++ b/sound/soc/codecs/max9860.c
@@ -0,0 +1,753 @@
+/*
+ * Driver for the MAX9860 Mono Audio Voice Codec
+ *
+ * https://datasheets.maximintegrated.com/en/ds/MAX9860.pdf
+ *
+ * The driver does not support sidetone since the DVST register field is
+ * backwards with the mute near the maximum level instead of the minimum.
+ *
+ * Author: Peter Rosin <peda@axentia.s>
+ * Copyright 2016 Axentia Technologies
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "max9860.h"
+
+struct max9860_priv {
+ struct regmap *regmap;
+ struct regulator *dvddio;
+ struct notifier_block dvddio_nb;
+ u8 psclk;
+ unsigned long pclk_rate;
+ int fmt;
+};
+
+static int max9860_dvddio_event(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct max9860_priv *max9860 = container_of(nb, struct max9860_priv,
+ dvddio_nb);
+ if (event & REGULATOR_EVENT_DISABLE) {
+ regcache_mark_dirty(max9860->regmap);
+ regcache_cache_only(max9860->regmap, true);
+ }
+
+ return 0;
+}
+
+static const struct reg_default max9860_reg_defaults[] = {
+ { MAX9860_PWRMAN, 0x00 },
+ { MAX9860_INTEN, 0x00 },
+ { MAX9860_SYSCLK, 0x00 },
+ { MAX9860_AUDIOCLKHIGH, 0x00 },
+ { MAX9860_AUDIOCLKLOW, 0x00 },
+ { MAX9860_IFC1A, 0x00 },
+ { MAX9860_IFC1B, 0x00 },
+ { MAX9860_VOICEFLTR, 0x00 },
+ { MAX9860_DACATTN, 0x00 },
+ { MAX9860_ADCLEVEL, 0x00 },
+ { MAX9860_DACGAIN, 0x00 },
+ { MAX9860_MICGAIN, 0x00 },
+ { MAX9860_MICADC, 0x00 },
+ { MAX9860_NOISEGATE, 0x00 },
+};
+
+static bool max9860_readable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX9860_INTRSTATUS ... MAX9860_MICGAIN:
+ case MAX9860_MICADC ... MAX9860_PWRMAN:
+ case MAX9860_REVISION:
+ return true;
+ }
+
+ return false;
+}
+
+static bool max9860_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX9860_INTEN ... MAX9860_MICGAIN:
+ case MAX9860_MICADC ... MAX9860_PWRMAN:
+ return true;
+ }
+
+ return false;
+}
+
+static bool max9860_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX9860_INTRSTATUS:
+ case MAX9860_MICREADBACK:
+ return true;
+ }
+
+ return false;
+}
+
+static bool max9860_precious(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX9860_INTRSTATUS:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config max9860_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .readable_reg = max9860_readable,
+ .writeable_reg = max9860_writeable,
+ .volatile_reg = max9860_volatile,
+ .precious_reg = max9860_precious,
+
+ .max_register = MAX9860_MAX_REGISTER,
+ .reg_defaults = max9860_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(max9860_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const DECLARE_TLV_DB_SCALE(dva_tlv, -9100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(dvg_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_RANGE(pam_tlv,
+ 0, MAX9860_PAM_MAX - 1, TLV_DB_SCALE_ITEM(-2000, 2000, 1),
+ MAX9860_PAM_MAX, MAX9860_PAM_MAX, TLV_DB_SCALE_ITEM(3000, 0, 0));
+static const DECLARE_TLV_DB_SCALE(pgam_tlv, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(anth_tlv, -7600, 400, 1);
+static const DECLARE_TLV_DB_SCALE(agcth_tlv, -1800, 100, 0);
+
+static const char * const agchld_text[] = {
+ "AGC Disabled", "50ms", "100ms", "400ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(agchld_enum, MAX9860_MICADC,
+ MAX9860_AGCHLD_SHIFT, agchld_text);
+
+static const char * const agcsrc_text[] = {
+ "Left ADC", "Left/Right ADC"
+};
+
+static SOC_ENUM_SINGLE_DECL(agcsrc_enum, MAX9860_MICADC,
+ MAX9860_AGCSRC_SHIFT, agcsrc_text);
+
+static const char * const agcatk_text[] = {
+ "3ms", "12ms", "50ms", "200ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(agcatk_enum, MAX9860_MICADC,
+ MAX9860_AGCATK_SHIFT, agcatk_text);
+
+static const char * const agcrls_text[] = {
+ "78ms", "156ms", "312ms", "625ms",
+ "1.25s", "2.5s", "5s", "10s"
+};
+
+static SOC_ENUM_SINGLE_DECL(agcrls_enum, MAX9860_MICADC,
+ MAX9860_AGCRLS_SHIFT, agcrls_text);
+
+static const char * const filter_text[] = {
+ "Disabled",
+ "Elliptical HP 217Hz notch (16kHz)",
+ "Butterworth HP 500Hz (16kHz)",
+ "Elliptical HP 217Hz notch (8kHz)",
+ "Butterworth HP 500Hz (8kHz)",
+ "Butterworth HP 200Hz (48kHz)"
+};
+
+static SOC_ENUM_SINGLE_DECL(avflt_enum, MAX9860_VOICEFLTR,
+ MAX9860_AVFLT_SHIFT, filter_text);
+
+static SOC_ENUM_SINGLE_DECL(dvflt_enum, MAX9860_VOICEFLTR,
+ MAX9860_DVFLT_SHIFT, filter_text);
+
+static const struct snd_kcontrol_new max9860_controls[] = {
+SOC_SINGLE_TLV("Master Playback Volume", MAX9860_DACATTN,
+ MAX9860_DVA_SHIFT, MAX9860_DVA_MUTE, 1, dva_tlv),
+SOC_SINGLE_TLV("DAC Gain Volume", MAX9860_DACGAIN,
+ MAX9860_DVG_SHIFT, MAX9860_DVG_MAX, 0, dvg_tlv),
+SOC_DOUBLE_TLV("Line Capture Volume", MAX9860_ADCLEVEL,
+ MAX9860_ADCLL_SHIFT, MAX9860_ADCRL_SHIFT, MAX9860_ADCxL_MIN, 1,
+ adc_tlv),
+
+SOC_ENUM("AGC Hold Time", agchld_enum),
+SOC_ENUM("AGC/Noise Gate Source", agcsrc_enum),
+SOC_ENUM("AGC Attack Time", agcatk_enum),
+SOC_ENUM("AGC Release Time", agcrls_enum),
+
+SOC_SINGLE_TLV("Noise Gate Threshold Volume", MAX9860_NOISEGATE,
+ MAX9860_ANTH_SHIFT, MAX9860_ANTH_MAX, 0, anth_tlv),
+SOC_SINGLE_TLV("AGC Signal Threshold Volume", MAX9860_NOISEGATE,
+ MAX9860_AGCTH_SHIFT, MAX9860_AGCTH_MIN, 1, agcth_tlv),
+
+SOC_SINGLE_TLV("Mic PGA Volume", MAX9860_MICGAIN,
+ MAX9860_PGAM_SHIFT, MAX9860_PGAM_MIN, 1, pgam_tlv),
+SOC_SINGLE_TLV("Mic Preamp Volume", MAX9860_MICGAIN,
+ MAX9860_PAM_SHIFT, MAX9860_PAM_MAX, 0, pam_tlv),
+
+SOC_ENUM("ADC Filter", avflt_enum),
+SOC_ENUM("DAC Filter", dvflt_enum),
+};
+
+static const struct snd_soc_dapm_widget max9860_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("MICL"),
+SND_SOC_DAPM_INPUT("MICR"),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, MAX9860_PWRMAN, MAX9860_ADCLEN_SHIFT, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, MAX9860_PWRMAN, MAX9860_ADCREN_SHIFT, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_DAC("DAC", NULL, MAX9860_PWRMAN, MAX9860_DACEN_SHIFT, 0),
+
+SND_SOC_DAPM_OUTPUT("OUT"),
+
+SND_SOC_DAPM_SUPPLY("Supply", SND_SOC_NOPM, 0, 0,
+ NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 0, 0),
+SND_SOC_DAPM_REGULATOR_SUPPLY("DVDD", 0, 0),
+SND_SOC_DAPM_CLOCK_SUPPLY("mclk"),
+};
+
+static const struct snd_soc_dapm_route max9860_dapm_routes[] = {
+ { "ADCL", NULL, "MICL" },
+ { "ADCR", NULL, "MICR" },
+ { "AIFOUTL", NULL, "ADCL" },
+ { "AIFOUTR", NULL, "ADCR" },
+
+ { "DAC", NULL, "AIFINL" },
+ { "DAC", NULL, "AIFINR" },
+ { "OUT", NULL, "DAC" },
+
+ { "Supply", NULL, "AVDD" },
+ { "Supply", NULL, "DVDD" },
+ { "Supply", NULL, "mclk" },
+
+ { "DAC", NULL, "Supply" },
+ { "ADCL", NULL, "Supply" },
+ { "ADCR", NULL, "Supply" },
+};
+
+static int max9860_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 max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec);
+ u8 master;
+ u8 ifc1a = 0;
+ u8 ifc1b = 0;
+ u8 sysclk = 0;
+ unsigned long n;
+ int ret;
+
+ dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n",
+ params_rate(params),
+ params_channels(params));
+
+ if (params_channels(params) == 2)
+ ifc1b |= MAX9860_ST;
+
+ switch (max9860->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ master = 0;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master = MAX9860_MASTER;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ifc1a |= master;
+
+ if (master) {
+ if (params_width(params) * params_channels(params) > 48)
+ ifc1b |= MAX9860_BSEL_64X;
+ else
+ ifc1b |= MAX9860_BSEL_48X;
+ }
+
+ switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ifc1a |= MAX9860_DDLY;
+ ifc1b |= MAX9860_ADLY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ifc1a |= MAX9860_WCI;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ if (params_width(params) != 16) {
+ dev_err(codec->dev,
+ "DSP_A works for 16 bits per sample only.\n");
+ return -EINVAL;
+ }
+ ifc1a |= MAX9860_DDLY | MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM;
+ ifc1b |= MAX9860_ADLY;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ if (params_width(params) != 16) {
+ dev_err(codec->dev,
+ "DSP_B works for 16 bits per sample only.\n");
+ return -EINVAL;
+ }
+ ifc1a |= MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (max9860->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ return -EINVAL;
+ }
+ ifc1a ^= MAX9860_WCI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ return -EINVAL;
+ }
+ ifc1a ^= MAX9860_WCI;
+ /* fall through */
+ case SND_SOC_DAIFMT_IB_NF:
+ ifc1a ^= MAX9860_DBCI;
+ ifc1b ^= MAX9860_ABCI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "IFC1A %02x\n", ifc1a);
+ ret = regmap_write(max9860->regmap, MAX9860_IFC1A, ifc1a);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set IFC1A: %d\n", ret);
+ return ret;
+ }
+ dev_dbg(codec->dev, "IFC1B %02x\n", ifc1b);
+ ret = regmap_write(max9860->regmap, MAX9860_IFC1B, ifc1b);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set IFC1B: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Check if Integer Clock Mode is possible, but avoid it in slave mode
+ * since we then do not know if lrclk is derived from pclk and the
+ * datasheet mentions that the frequencies have to match exactly in
+ * order for this to work.
+ */
+ if (params_rate(params) == 8000 || params_rate(params) == 16000) {
+ if (master) {
+ switch (max9860->pclk_rate) {
+ case 12000000:
+ sysclk = MAX9860_FREQ_12MHZ;
+ break;
+ case 13000000:
+ sysclk = MAX9860_FREQ_13MHZ;
+ break;
+ case 19200000:
+ sysclk = MAX9860_FREQ_19_2MHZ;
+ break;
+ default:
+ /*
+ * Integer Clock Mode not possible. Leave
+ * sysclk at zero and fall through to the
+ * code below for PLL mode.
+ */
+ break;
+ }
+
+ if (sysclk && params_rate(params) == 16000)
+ sysclk |= MAX9860_16KHZ;
+ }
+ }
+
+ /*
+ * Largest possible n:
+ * 65536 * 96 * 48kHz / 10MHz -> 30199
+ * Smallest possible n:
+ * 65536 * 96 * 8kHz / 20MHz -> 2517
+ * Both fit nicely in the available 15 bits, no need to apply any mask.
+ */
+ n = DIV_ROUND_CLOSEST_ULL(65536ULL * 96 * params_rate(params),
+ max9860->pclk_rate);
+
+ if (!sysclk) {
+ /* PLL mode */
+ if (params_rate(params) > 24000)
+ sysclk |= MAX9860_16KHZ;
+
+ if (!master)
+ n |= 1; /* trigger rapid pll lock mode */
+ }
+
+ sysclk |= max9860->psclk;
+ dev_dbg(codec->dev, "SYSCLK %02x\n", sysclk);
+ ret = regmap_write(max9860->regmap,
+ MAX9860_SYSCLK, sysclk);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+ dev_dbg(codec->dev, "N %lu\n", n);
+ ret = regmap_write(max9860->regmap,
+ MAX9860_AUDIOCLKHIGH, n >> 8);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set NHI: %d\n", ret);
+ return ret;
+ }
+ ret = regmap_write(max9860->regmap,
+ MAX9860_AUDIOCLKLOW, n & 0xff);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set NLO: %d\n", ret);
+ return ret;
+ }
+
+ if (!master) {
+ dev_dbg(codec->dev, "Enable PLL\n");
+ ret = regmap_update_bits(max9860->regmap, MAX9860_AUDIOCLKHIGH,
+ MAX9860_PLL, MAX9860_PLL);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable PLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBS_CFS:
+ max9860->fmt = fmt;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct snd_soc_dai_ops max9860_dai_ops = {
+ .hw_params = max9860_hw_params,
+ .set_fmt = max9860_set_fmt,
+};
+
+static struct snd_soc_dai_driver max9860_dai = {
+ .name = "max9860-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &max9860_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int max9860_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct max9860_priv *max9860 = dev_get_drvdata(codec->dev);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN,
+ MAX9860_SHDN, MAX9860_SHDN);
+ if (ret) {
+ dev_err(codec->dev, "Failed to remove SHDN: %d\n", ret);
+ return ret;
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN,
+ MAX9860_SHDN, 0);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request SHDN: %d\n",
+ ret);
+ return ret;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver max9860_codec_driver = {
+ .set_bias_level = max9860_set_bias_level,
+ .idle_bias_off = true,
+
+ .controls = max9860_controls,
+ .num_controls = ARRAY_SIZE(max9860_controls),
+ .dapm_widgets = max9860_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max9860_dapm_widgets),
+ .dapm_routes = max9860_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(max9860_dapm_routes),
+};
+
+#ifdef CONFIG_PM
+static int max9860_suspend(struct device *dev)
+{
+ struct max9860_priv *max9860 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK,
+ MAX9860_PSCLK, MAX9860_PSCLK_OFF);
+ if (ret) {
+ dev_err(dev, "Failed to disable clock: %d\n", ret);
+ return ret;
+ }
+
+ regulator_disable(max9860->dvddio);
+
+ return 0;
+}
+
+static int max9860_resume(struct device *dev)
+{
+ struct max9860_priv *max9860 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_enable(max9860->dvddio);
+ if (ret) {
+ dev_err(dev, "Failed to enable DVDDIO: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(max9860->regmap, false);
+ ret = regcache_sync(max9860->regmap);
+ if (ret) {
+ dev_err(dev, "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK,
+ MAX9860_PSCLK, max9860->psclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops max9860_pm_ops = {
+ SET_RUNTIME_PM_OPS(max9860_suspend, max9860_resume, NULL)
+};
+
+static int max9860_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct max9860_priv *max9860;
+ int ret;
+ struct clk *mclk;
+ unsigned long mclk_rate;
+ int i;
+ int intr;
+
+ max9860 = devm_kzalloc(dev, sizeof(struct max9860_priv), GFP_KERNEL);
+ if (!max9860)
+ return -ENOMEM;
+
+ max9860->dvddio = devm_regulator_get(dev, "DVDDIO");
+ if (IS_ERR(max9860->dvddio)) {
+ ret = PTR_ERR(max9860->dvddio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get DVDDIO supply: %d\n", ret);
+ return ret;
+ }
+
+ max9860->dvddio_nb.notifier_call = max9860_dvddio_event;
+
+ ret = regulator_register_notifier(max9860->dvddio, &max9860->dvddio_nb);
+ if (ret)
+ dev_err(dev, "Failed to register DVDDIO notifier: %d\n", ret);
+
+ ret = regulator_enable(max9860->dvddio);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable DVDDIO: %d\n", ret);
+ return ret;
+ }
+
+ max9860->regmap = devm_regmap_init_i2c(i2c, &max9860_regmap);
+ if (IS_ERR(max9860->regmap)) {
+ ret = PTR_ERR(max9860->regmap);
+ goto err_regulator;
+ }
+
+ dev_set_drvdata(dev, max9860);
+
+ /*
+ * mclk has to be in the 10MHz to 60MHz range.
+ * psclk is used to scale mclk into pclk so that
+ * pclk is in the 10MHz to 20MHz range.
+ */
+ mclk = clk_get(dev, "mclk");
+
+ if (IS_ERR(mclk)) {
+ ret = PTR_ERR(mclk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get MCLK: %d\n", ret);
+ goto err_regulator;
+ }
+
+ mclk_rate = clk_get_rate(mclk);
+ clk_put(mclk);
+
+ if (mclk_rate > 60000000 || mclk_rate < 10000000) {
+ dev_err(dev, "Bad mclk %luHz (needs 10MHz - 60MHz)\n",
+ mclk_rate);
+ ret = -EINVAL;
+ goto err_regulator;
+ }
+ if (mclk_rate >= 40000000)
+ max9860->psclk = 3;
+ else if (mclk_rate >= 20000000)
+ max9860->psclk = 2;
+ else
+ max9860->psclk = 1;
+ max9860->pclk_rate = mclk_rate >> (max9860->psclk - 1);
+ max9860->psclk <<= MAX9860_PSCLK_SHIFT;
+ dev_dbg(dev, "mclk %lu pclk %lu\n", mclk_rate, max9860->pclk_rate);
+
+ regcache_cache_bypass(max9860->regmap, true);
+ for (i = 0; i < max9860_regmap.num_reg_defaults; ++i) {
+ ret = regmap_write(max9860->regmap,
+ max9860_regmap.reg_defaults[i].reg,
+ max9860_regmap.reg_defaults[i].def);
+ if (ret) {
+ dev_err(dev, "Failed to initialize register %u: %d\n",
+ max9860_regmap.reg_defaults[i].reg, ret);
+ goto err_regulator;
+ }
+ }
+ regcache_cache_bypass(max9860->regmap, false);
+
+ ret = regmap_read(max9860->regmap, MAX9860_INTRSTATUS, &intr);
+ if (ret) {
+ dev_err(dev, "Failed to clear INTRSTATUS: %d\n", ret);
+ goto err_regulator;
+ }
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
+
+ ret = snd_soc_register_codec(dev, &max9860_codec_driver,
+ &max9860_dai, 1);
+ if (ret) {
+ dev_err(dev, "Failed to register CODEC: %d\n", ret);
+ goto err_pm;
+ }
+
+ return 0;
+
+err_pm:
+ pm_runtime_disable(dev);
+err_regulator:
+ regulator_disable(max9860->dvddio);
+ return ret;
+}
+
+static int max9860_remove(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct max9860_priv *max9860 = dev_get_drvdata(dev);
+
+ snd_soc_unregister_codec(dev);
+ pm_runtime_disable(dev);
+ regulator_disable(max9860->dvddio);
+ return 0;
+}
+
+static const struct i2c_device_id max9860_i2c_id[] = {
+ { "max9860", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max9860_i2c_id);
+
+static const struct of_device_id max9860_of_match[] = {
+ { .compatible = "maxim,max9860", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max9860_of_match);
+
+static struct i2c_driver max9860_i2c_driver = {
+ .probe = max9860_probe,
+ .remove = max9860_remove,
+ .id_table = max9860_i2c_id,
+ .driver = {
+ .name = "max9860",
+ .of_match_table = max9860_of_match,
+ .pm = &max9860_pm_ops,
+ },
+};
+
+module_i2c_driver(max9860_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC MAX9860 Mono Audio Voice Codec driver");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/max9860.h b/sound/soc/codecs/max9860.h
new file mode 100644
index 000000000000..22041bd67a7d
--- /dev/null
+++ b/sound/soc/codecs/max9860.h
@@ -0,0 +1,162 @@
+/*
+ * Driver for the MAX9860 Mono Audio Voice Codec
+ *
+ * Author: Peter Rosin <peda@axentia.s>
+ * Copyright 2016 Axentia Technologies
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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 _SND_SOC_MAX9860
+#define _SND_SOC_MAX9860
+
+#define MAX9860_INTRSTATUS 0x00
+#define MAX9860_MICREADBACK 0x01
+#define MAX9860_INTEN 0x02
+#define MAX9860_SYSCLK 0x03
+#define MAX9860_AUDIOCLKHIGH 0x04
+#define MAX9860_AUDIOCLKLOW 0x05
+#define MAX9860_IFC1A 0x06
+#define MAX9860_IFC1B 0x07
+#define MAX9860_VOICEFLTR 0x08
+#define MAX9860_DACATTN 0x09
+#define MAX9860_ADCLEVEL 0x0a
+#define MAX9860_DACGAIN 0x0b
+#define MAX9860_MICGAIN 0x0c
+#define MAX9860_RESERVED 0x0d
+#define MAX9860_MICADC 0x0e
+#define MAX9860_NOISEGATE 0x0f
+#define MAX9860_PWRMAN 0x10
+#define MAX9860_REVISION 0xff
+
+#define MAX9860_MAX_REGISTER 0xff
+
+/* INTRSTATUS */
+#define MAX9860_CLD 0x80
+#define MAX9860_SLD 0x40
+#define MAX9860_ULK 0x20
+
+/* MICREADBACK */
+#define MAX9860_NG 0xe0
+#define MAX9860_AGC 0x1f
+
+/* INTEN */
+#define MAX9860_ICLD 0x80
+#define MAX9860_ISLD 0x40
+#define MAX9860_IULK 0x20
+
+/* SYSCLK */
+#define MAX9860_PSCLK 0x30
+#define MAX9860_PSCLK_OFF 0x00
+#define MAX9860_PSCLK_SHIFT 4
+#define MAX9860_FREQ 0x06
+#define MAX9860_FREQ_NORMAL 0x00
+#define MAX9860_FREQ_12MHZ 0x02
+#define MAX9860_FREQ_13MHZ 0x04
+#define MAX9860_FREQ_19_2MHZ 0x06
+#define MAX9860_16KHZ 0x01
+
+/* AUDIOCLKHIGH */
+#define MAX9860_PLL 0x80
+#define MAX9860_NHI 0x7f
+
+/* AUDIOCLKLOW */
+#define MAX9860_NLO 0xff
+
+/* IFC1A */
+#define MAX9860_MASTER 0x80
+#define MAX9860_WCI 0x40
+#define MAX9860_DBCI 0x20
+#define MAX9860_DDLY 0x10
+#define MAX9860_HIZ 0x08
+#define MAX9860_TDM 0x04
+
+/* IFC1B */
+#define MAX9860_ABCI 0x20
+#define MAX9860_ADLY 0x10
+#define MAX9860_ST 0x08
+#define MAX9860_BSEL 0x07
+#define MAX9860_BSEL_OFF 0x00
+#define MAX9860_BSEL_64X 0x01
+#define MAX9860_BSEL_48X 0x02
+#define MAX9860_BSEL_PCLK_2 0x04
+#define MAX9860_BSEL_PCLK_4 0x05
+#define MAX9860_BSEL_PCLK_8 0x06
+#define MAX9860_BSEL_PCLK_16 0x07
+
+/* VOICEFLTR */
+#define MAX9860_AVFLT 0xf0
+#define MAX9860_AVFLT_SHIFT 4
+#define MAX9860_AVFLT_COUNT 6
+#define MAX9860_DVFLT 0x0f
+#define MAX9860_DVFLT_SHIFT 0
+#define MAX9860_DVFLT_COUNT 6
+
+/* DACATTN */
+#define MAX9860_DVA 0xfe
+#define MAX9860_DVA_SHIFT 1
+#define MAX9860_DVA_MUTE 0x5e
+
+/* ADCLEVEL */
+#define MAX9860_ADCRL 0xf0
+#define MAX9860_ADCRL_SHIFT 4
+#define MAX9860_ADCLL 0x0f
+#define MAX9860_ADCLL_SHIFT 0
+#define MAX9860_ADCxL_MIN 15
+
+/* DACGAIN */
+#define MAX9860_DVG 0x60
+#define MAX9860_DVG_SHIFT 5
+#define MAX9860_DVG_MAX 3
+#define MAX9860_DVST 0x1f
+#define MAX9860_DVST_SHIFT 0
+#define MAX9860_DVST_MIN 31
+
+/* MICGAIN */
+#define MAX9860_PAM 0x60
+#define MAX9860_PAM_SHIFT 5
+#define MAX9860_PAM_MAX 3
+#define MAX9860_PGAM 0x1f
+#define MAX9860_PGAM_SHIFT 0
+#define MAX9860_PGAM_MIN 20
+
+/* MICADC */
+#define MAX9860_AGCSRC 0x80
+#define MAX9860_AGCSRC_SHIFT 7
+#define MAX9860_AGCSRC_COUNT 2
+#define MAX9860_AGCRLS 0x70
+#define MAX9860_AGCRLS_SHIFT 4
+#define MAX9860_AGCRLS_COUNT 8
+#define MAX9860_AGCATK 0x0c
+#define MAX9860_AGCATK_SHIFT 2
+#define MAX9860_AGCATK_COUNT 4
+#define MAX9860_AGCHLD 0x03
+#define MAX9860_AGCHLD_OFF 0x00
+#define MAX9860_AGCHLD_SHIFT 0
+#define MAX9860_AGCHLD_COUNT 4
+
+/* NOISEGATE */
+#define MAX9860_ANTH 0xf0
+#define MAX9860_ANTH_SHIFT 4
+#define MAX9860_ANTH_MAX 15
+#define MAX9860_AGCTH 0x0f
+#define MAX9860_AGCTH_SHIFT 0
+#define MAX9860_AGCTH_MIN 15
+
+/* PWRMAN */
+#define MAX9860_SHDN 0x80
+#define MAX9860_DACEN 0x08
+#define MAX9860_DACEN_SHIFT 3
+#define MAX9860_ADCLEN 0x02
+#define MAX9860_ADCLEN_SHIFT 1
+#define MAX9860_ADCREN 0x01
+#define MAX9860_ADCREN_SHIFT 0
+
+#endif /* _SND_SOC_MAX9860 */
diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c
index 2a22fddeb6af..2a22fddeb6af 100755..100644
--- a/sound/soc/codecs/max9867.c
+++ b/sound/soc/codecs/max9867.c
diff --git a/sound/soc/codecs/max9867.h b/sound/soc/codecs/max9867.h
index 65590b4ad62a..65590b4ad62a 100755..100644
--- a/sound/soc/codecs/max9867.h
+++ b/sound/soc/codecs/max9867.h
diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h
index 6da72290ac58..368343f29dd0 100644
--- a/sound/soc/codecs/max9877.h
+++ b/sound/soc/codecs/max9877.h
@@ -32,6 +32,4 @@
#define MAX9877_BYPASS (1 << 6)
#define MAX9877_SHDN (1 << 7)
-extern int max9877_add_controls(struct snd_soc_codec *codec);
-
#endif
diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c
index 683769f0f246..5c9707ac4bbf 100644
--- a/sound/soc/codecs/nau8825.c
+++ b/sound/soc/codecs/nau8825.c
@@ -18,6 +18,7 @@
#include <linux/clk.h>
#include <linux/acpi.h>
#include <linux/math64.h>
+#include <linux/semaphore.h>
#include <sound/initval.h>
#include <sound/tlv.h>
@@ -30,10 +31,22 @@
#include "nau8825.h"
+
+#define NUVOTON_CODEC_DAI "nau8825-hifi"
+
#define NAU_FREF_MAX 13500000
-#define NAU_FVCO_MAX 100000000
+#define NAU_FVCO_MAX 124000000
#define NAU_FVCO_MIN 90000000
+/* cross talk suppression detection */
+#define LOG10_MAGIC 646456993
+#define GAIN_AUGMENT 22500
+#define SIDETONE_BASE 207000
+
+
+static int nau8825_configure_sysclk(struct nau8825 *nau8825,
+ int clk_id, unsigned int freq);
+
struct nau8825_fll {
int mclk_src;
int ratio;
@@ -156,6 +169,661 @@ static const struct reg_default nau8825_reg_defaults[] = {
{ NAU8825_REG_CHARGE_PUMP, 0x0 },
};
+/* register backup table when cross talk detection */
+static struct reg_default nau8825_xtalk_baktab[] = {
+ { NAU8825_REG_ADC_DGAIN_CTRL, 0 },
+ { NAU8825_REG_HSVOL_CTRL, 0 },
+ { NAU8825_REG_DACL_CTRL, 0 },
+ { NAU8825_REG_DACR_CTRL, 0 },
+};
+
+static const unsigned short logtable[256] = {
+ 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7,
+ 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508,
+ 0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6,
+ 0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37,
+ 0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f,
+ 0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41,
+ 0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1,
+ 0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142,
+ 0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68,
+ 0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355,
+ 0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c,
+ 0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490,
+ 0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3,
+ 0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507,
+ 0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe,
+ 0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca,
+ 0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c,
+ 0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7,
+ 0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c,
+ 0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c,
+ 0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a,
+ 0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065,
+ 0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730,
+ 0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc,
+ 0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469,
+ 0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9,
+ 0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c,
+ 0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765,
+ 0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83,
+ 0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387,
+ 0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973,
+ 0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47
+};
+
+static struct snd_soc_dai *nau8825_get_codec_dai(struct nau8825 *nau8825)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(nau8825->dapm);
+ struct snd_soc_component *component = &codec->component;
+ struct snd_soc_dai *codec_dai, *_dai;
+
+ list_for_each_entry_safe(codec_dai, _dai, &component->dai_list, list) {
+ if (!strncmp(codec_dai->name, NUVOTON_CODEC_DAI,
+ strlen(NUVOTON_CODEC_DAI)))
+ return codec_dai;
+ }
+ return NULL;
+}
+
+static bool nau8825_dai_is_active(struct nau8825 *nau8825)
+{
+ struct snd_soc_dai *codec_dai = nau8825_get_codec_dai(nau8825);
+
+ if (codec_dai) {
+ if (codec_dai->playback_active || codec_dai->capture_active)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * nau8825_sema_acquire - acquire the semaphore of nau88l25
+ * @nau8825: component to register the codec private data with
+ * @timeout: how long in jiffies to wait before failure or zero to wait
+ * until release
+ *
+ * Attempts to acquire the semaphore with number of jiffies. If no more
+ * tasks are allowed to acquire the semaphore, calling this function will
+ * put the task to sleep. If the semaphore is not released within the
+ * specified number of jiffies, this function returns.
+ * Acquires the semaphore without jiffies. If no more tasks are allowed
+ * to acquire the semaphore, calling this function will put the task to
+ * sleep until the semaphore is released.
+ * It returns if the semaphore was acquired.
+ */
+static void nau8825_sema_acquire(struct nau8825 *nau8825, long timeout)
+{
+ int ret;
+
+ if (timeout)
+ ret = down_timeout(&nau8825->xtalk_sem, timeout);
+ else
+ ret = down_interruptible(&nau8825->xtalk_sem);
+
+ if (ret < 0)
+ dev_warn(nau8825->dev, "Acquire semaphone fail\n");
+}
+
+/**
+ * nau8825_sema_release - release the semaphore of nau88l25
+ * @nau8825: component to register the codec private data with
+ *
+ * Release the semaphore which may be called from any context and
+ * even by tasks which have never called down().
+ */
+static inline void nau8825_sema_release(struct nau8825 *nau8825)
+{
+ up(&nau8825->xtalk_sem);
+}
+
+/**
+ * nau8825_sema_reset - reset the semaphore for nau88l25
+ * @nau8825: component to register the codec private data with
+ *
+ * Reset the counter of the semaphore. Call this function to restart
+ * a new round task management.
+ */
+static inline void nau8825_sema_reset(struct nau8825 *nau8825)
+{
+ nau8825->xtalk_sem.count = 1;
+}
+
+/**
+ * Ramp up the headphone volume change gradually to target level.
+ *
+ * @nau8825: component to register the codec private data with
+ * @vol_from: the volume to start up
+ * @vol_to: the target volume
+ * @step: the volume span to move on
+ *
+ * The headphone volume is from 0dB to minimum -54dB and -1dB per step.
+ * If the volume changes sharp, there is a pop noise heard in headphone. We
+ * provide the function to ramp up the volume up or down by delaying 10ms
+ * per step.
+ */
+static void nau8825_hpvol_ramp(struct nau8825 *nau8825,
+ unsigned int vol_from, unsigned int vol_to, unsigned int step)
+{
+ unsigned int value, volume, ramp_up, from, to;
+
+ if (vol_from == vol_to || step == 0) {
+ return;
+ } else if (vol_from < vol_to) {
+ ramp_up = true;
+ from = vol_from;
+ to = vol_to;
+ } else {
+ ramp_up = false;
+ from = vol_to;
+ to = vol_from;
+ }
+ /* only handle volume from 0dB to minimum -54dB */
+ if (to > NAU8825_HP_VOL_MIN)
+ to = NAU8825_HP_VOL_MIN;
+
+ for (volume = from; volume < to; volume += step) {
+ if (ramp_up)
+ value = volume;
+ else
+ value = to - volume + from;
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
+ NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
+ (value << NAU8825_HPL_VOL_SFT) | value);
+ usleep_range(10000, 10500);
+ }
+ if (ramp_up)
+ value = to;
+ else
+ value = from;
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
+ NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
+ (value << NAU8825_HPL_VOL_SFT) | value);
+}
+
+/**
+ * Computes log10 of a value; the result is round off to 3 decimal. This func-
+ * tion takes reference to dvb-math. The source code locates as the following.
+ * Linux/drivers/media/dvb-core/dvb_math.c
+ *
+ * return log10(value) * 1000
+ */
+static u32 nau8825_intlog10_dec3(u32 value)
+{
+ u32 msb, logentry, significand, interpolation, log10val;
+ u64 log2val;
+
+ /* first detect the msb (count begins at 0) */
+ msb = fls(value) - 1;
+ /**
+ * now we use a logtable after the following method:
+ *
+ * log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24
+ * where x = msb and therefore 1 <= y < 2
+ * first y is determined by shifting the value left
+ * so that msb is bit 31
+ * 0x00231f56 -> 0x8C7D5800
+ * the result is y * 2^31 -> "significand"
+ * then the highest 9 bits are used for a table lookup
+ * the highest bit is discarded because it's always set
+ * the highest nine bits in our example are 100011000
+ * so we would use the entry 0x18
+ */
+ significand = value << (31 - msb);
+ logentry = (significand >> 23) & 0xff;
+ /**
+ * last step we do is interpolation because of the
+ * limitations of the log table the error is that part of
+ * the significand which isn't used for lookup then we
+ * compute the ratio between the error and the next table entry
+ * and interpolate it between the log table entry used and the
+ * next one the biggest error possible is 0x7fffff
+ * (in our example it's 0x7D5800)
+ * needed value for next table entry is 0x800000
+ * so the interpolation is
+ * (error / 0x800000) * (logtable_next - logtable_current)
+ * in the implementation the division is moved to the end for
+ * better accuracy there is also an overflow correction if
+ * logtable_next is 256
+ */
+ interpolation = ((significand & 0x7fffff) *
+ ((logtable[(logentry + 1) & 0xff] -
+ logtable[logentry]) & 0xffff)) >> 15;
+
+ log2val = ((msb << 24) + (logtable[logentry] << 8) + interpolation);
+ /**
+ * log10(x) = log2(x) * log10(2)
+ */
+ log10val = (log2val * LOG10_MAGIC) >> 31;
+ /**
+ * the result is round off to 3 decimal
+ */
+ return log10val / ((1 << 24) / 1000);
+}
+
+/**
+ * computes cross talk suppression sidetone gain.
+ *
+ * @sig_org: orignal signal level
+ * @sig_cros: cross talk signal level
+ *
+ * The orignal and cross talk signal vlues need to be characterized.
+ * Once these values have been characterized, this sidetone value
+ * can be converted to decibel with the equation below.
+ * sidetone = 20 * log (original signal level / crosstalk signal level)
+ *
+ * return cross talk sidetone gain
+ */
+static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros)
+{
+ u32 gain, sidetone;
+
+ if (unlikely(sig_org == 0) || unlikely(sig_cros == 0)) {
+ WARN_ON(1);
+ return 0;
+ }
+
+ sig_org = nau8825_intlog10_dec3(sig_org);
+ sig_cros = nau8825_intlog10_dec3(sig_cros);
+ if (sig_org >= sig_cros)
+ gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT;
+ else
+ gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT;
+ sidetone = SIDETONE_BASE - gain * 2;
+ sidetone /= 1000;
+
+ return sidetone;
+}
+
+static int nau8825_xtalk_baktab_index_by_reg(unsigned int reg)
+{
+ int index;
+
+ for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++)
+ if (nau8825_xtalk_baktab[index].reg == reg)
+ return index;
+ return -EINVAL;
+}
+
+static void nau8825_xtalk_backup(struct nau8825 *nau8825)
+{
+ int i;
+
+ /* Backup some register values to backup table */
+ for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++)
+ regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
+ &nau8825_xtalk_baktab[i].def);
+}
+
+static void nau8825_xtalk_restore(struct nau8825 *nau8825)
+{
+ int i, volume;
+
+ /* Restore register values from backup table; When the driver restores
+ * the headphone volumem, it needs recover to original level gradually
+ * with 3dB per step for less pop noise.
+ */
+ for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) {
+ if (nau8825_xtalk_baktab[i].reg == NAU8825_REG_HSVOL_CTRL) {
+ /* Ramping up the volume change to reduce pop noise */
+ volume = nau8825_xtalk_baktab[i].def &
+ NAU8825_HPR_VOL_MASK;
+ nau8825_hpvol_ramp(nau8825, 0, volume, 3);
+ continue;
+ }
+ regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
+ nau8825_xtalk_baktab[i].def);
+ }
+}
+
+static void nau8825_xtalk_prepare_dac(struct nau8825 *nau8825)
+{
+ /* Enable power of DAC path */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
+ NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK |
+ NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR |
+ NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC |
+ NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK);
+ /* Prevent startup click by letting charge pump to ramp up and
+ * change bump enable
+ */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
+ NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN,
+ NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN);
+ /* Enable clock sync of DAC and DAC clock */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
+ NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN |
+ NAU8825_RDAC_FS_BCLK_ENB,
+ NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN);
+ /* Power up output driver with 2 stage */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
+ NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
+ NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L,
+ NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
+ NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
+ NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L,
+ NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L);
+ /* HP outputs not shouted to ground */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
+ NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0);
+ /* Enable HP boost driver */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
+ NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS);
+ /* Enable class G compare path to supply 1.8V or 0.9V. */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL,
+ NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN,
+ NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN);
+}
+
+static void nau8825_xtalk_prepare_adc(struct nau8825 *nau8825)
+{
+ /* Power up left ADC and raise 5dB than Vmid for Vref */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
+ NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK,
+ NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB);
+}
+
+static void nau8825_xtalk_clock(struct nau8825 *nau8825)
+{
+ /* Recover FLL default value */
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0);
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126);
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008);
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010);
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0);
+ regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000);
+ /* Enable internal VCO clock for detection signal generated */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
+ NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN,
+ NAU8825_DCO_EN);
+ /* Given specific clock frequency of internal clock to
+ * generate signal.
+ */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
+ NAU8825_CLK_MCLK_SRC_MASK, 0xf);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
+ NAU8825_FLL_RATIO_MASK, 0x10);
+}
+
+static void nau8825_xtalk_prepare(struct nau8825 *nau8825)
+{
+ int volume, index;
+
+ /* Backup those registers changed by cross talk detection */
+ nau8825_xtalk_backup(nau8825);
+ /* Config IIS as master to output signal by codec */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
+ NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK |
+ NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER |
+ (0x2 << NAU8825_I2S_DRV_SFT) | 0x1);
+ /* Ramp up headphone volume to 0dB to get better performance and
+ * avoid pop noise in headphone.
+ */
+ index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL);
+ if (index != -EINVAL) {
+ volume = nau8825_xtalk_baktab[index].def &
+ NAU8825_HPR_VOL_MASK;
+ nau8825_hpvol_ramp(nau8825, volume, 0, 3);
+ }
+ nau8825_xtalk_clock(nau8825);
+ nau8825_xtalk_prepare_dac(nau8825);
+ nau8825_xtalk_prepare_adc(nau8825);
+ /* Config channel path and digital gain */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL,
+ NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK,
+ NAU8825_DACL_CH_SEL_L | 0xab);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL,
+ NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK,
+ NAU8825_DACR_CH_SEL_R | 0xab);
+ /* Config cross talk parameters and generate the 23Hz sine wave with
+ * 1/16 full scale of signal level for impedance measurement.
+ */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
+ NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK |
+ NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK,
+ (0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th |
+ NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN);
+ /* RMS intrruption enable */
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0);
+ /* Power up left and right DAC */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
+ NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0);
+}
+
+static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825)
+{
+ /* Disable HP boost driver */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
+ NAU8825_HP_BOOST_DIS, 0);
+ /* HP outputs shouted to ground */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
+ NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
+ NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L);
+ /* Power down left and right DAC */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
+ NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL,
+ NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL);
+ /* Enable the TESTDAC and disable L/R HP impedance */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
+ NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP |
+ NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN);
+ /* Power down output driver with 2 stage */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
+ NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
+ NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
+ NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0);
+ /* Disable clock sync of DAC and DAC clock */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
+ NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0);
+ /* Disable charge pump ramp up function and change bump */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
+ NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0);
+ /* Disable power of DAC path */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
+ NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0);
+ if (!nau8825->irq)
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0);
+}
+
+static void nau8825_xtalk_clean_adc(struct nau8825 *nau8825)
+{
+ /* Power down left ADC and restore voltage to Vmid */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
+ NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0);
+}
+
+static void nau8825_xtalk_clean(struct nau8825 *nau8825)
+{
+ /* Enable internal VCO needed for interruptions */
+ nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
+ nau8825_xtalk_clean_dac(nau8825);
+ nau8825_xtalk_clean_adc(nau8825);
+ /* Clear cross talk parameters and disable */
+ regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0);
+ /* RMS intrruption disable */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK,
+ NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN);
+ /* Recover default value for IIS */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
+ NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK |
+ NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE);
+ /* Restore value of specific register for cross talk */
+ nau8825_xtalk_restore(nau8825);
+}
+
+static void nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol)
+{
+ /* Apply ADC volume for better cross talk performance */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL,
+ NAU8825_ADC_DIG_VOL_MASK, vol);
+ /* Disables JKTIP(HPL) DAC channel for right to left measurement.
+ * Do it before sending signal in order to erase pop noise.
+ */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
+ NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN,
+ NAU8825_BIAS_TESTDACL_EN);
+ switch (nau8825->xtalk_state) {
+ case NAU8825_XTALK_HPR_R2L:
+ /* Enable right headphone impedance */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
+ NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
+ NAU8825_BIAS_HPR_IMP);
+ break;
+ case NAU8825_XTALK_HPL_R2L:
+ /* Enable left headphone impedance */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
+ NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
+ NAU8825_BIAS_HPL_IMP);
+ break;
+ default:
+ break;
+ }
+ msleep(100);
+ /* Impedance measurement mode enable */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
+ NAU8825_IMM_EN, NAU8825_IMM_EN);
+}
+
+static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825)
+{
+ /* Impedance measurement mode disable */
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_IMM_MODE_CTRL, NAU8825_IMM_EN, 0);
+}
+
+/* The cross talk measurement function can reduce cross talk across the
+ * JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal
+ * level to determine what cross talk reduction gain is. This system works by
+ * sending a 23Hz -24dBV sine wave into the headset output DAC and through
+ * the PGA. The output of the PGA is then connected to an internal current
+ * sense which measures the attenuated 23Hz signal and passing the output to
+ * an ADC which converts the measurement to a binary code. With two separated
+ * measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data
+ * can be separated read in IMM_RMS_L for HSR and HSL after each measurement.
+ * Thus, the measurement function has four states to complete whole sequence.
+ * 1. Prepare state : Prepare the resource for detection and transfer to HPR
+ * IMM stat to make JKR1(HPR) impedance measure.
+ * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer
+ * to HPL IMM state to make JKTIP(HPL) impedance measure.
+ * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and
+ * transfer to IMM state to determine suppression sidetone gain.
+ * 4. IMM state : Computes cross talk suppression sidetone gain with orignal
+ * and cross talk signal level. Apply this gain and then restore codec
+ * configuration. Then transfer to Done state for ending.
+ */
+static void nau8825_xtalk_measure(struct nau8825 *nau8825)
+{
+ u32 sidetone;
+
+ switch (nau8825->xtalk_state) {
+ case NAU8825_XTALK_PREPARE:
+ /* In prepare state, set up clock, intrruption, DAC path, ADC
+ * path and cross talk detection parameters for preparation.
+ */
+ nau8825_xtalk_prepare(nau8825);
+ msleep(280);
+ /* Trigger right headphone impedance detection */
+ nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L;
+ nau8825_xtalk_imm_start(nau8825, 0x00d2);
+ break;
+ case NAU8825_XTALK_HPR_R2L:
+ /* In right headphone IMM state, read out right headphone
+ * impedance measure result, and then start up left side.
+ */
+ regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
+ &nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]);
+ dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n",
+ nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]);
+ /* Disable then re-enable IMM mode to update */
+ nau8825_xtalk_imm_stop(nau8825);
+ /* Trigger left headphone impedance detection */
+ nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L;
+ nau8825_xtalk_imm_start(nau8825, 0x00ff);
+ break;
+ case NAU8825_XTALK_HPL_R2L:
+ /* In left headphone IMM state, read out left headphone
+ * impedance measure result, and delay some time to wait
+ * detection sine wave output finish. Then, we can calculate
+ * the cross talk suppresstion side tone according to the L/R
+ * headphone imedance.
+ */
+ regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
+ &nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
+ dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n",
+ nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
+ nau8825_xtalk_imm_stop(nau8825);
+ msleep(150);
+ nau8825->xtalk_state = NAU8825_XTALK_IMM;
+ break;
+ case NAU8825_XTALK_IMM:
+ /* In impedance measure state, the orignal and cross talk
+ * signal level vlues are ready. The side tone gain is deter-
+ * mined with these signal level. After all, restore codec
+ * configuration.
+ */
+ sidetone = nau8825_xtalk_sidetone(
+ nau8825->imp_rms[NAU8825_XTALK_HPR_R2L],
+ nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
+ dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone);
+ regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL,
+ (sidetone << 8) | sidetone);
+ nau8825_xtalk_clean(nau8825);
+ nau8825->xtalk_state = NAU8825_XTALK_DONE;
+ break;
+ default:
+ break;
+ }
+}
+
+static void nau8825_xtalk_work(struct work_struct *work)
+{
+ struct nau8825 *nau8825 = container_of(
+ work, struct nau8825, xtalk_work);
+
+ nau8825_xtalk_measure(nau8825);
+ /* To determine the cross talk side tone gain when reach
+ * the impedance measure state.
+ */
+ if (nau8825->xtalk_state == NAU8825_XTALK_IMM)
+ nau8825_xtalk_measure(nau8825);
+
+ /* Delay jack report until cross talk detection process
+ * completed. It can avoid application to do playback
+ * preparation before cross talk detection is still working.
+ * Meanwhile, the protection of the cross talk detection
+ * is released.
+ */
+ if (nau8825->xtalk_state == NAU8825_XTALK_DONE) {
+ snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event,
+ nau8825->xtalk_event_mask);
+ nau8825_sema_release(nau8825);
+ nau8825->xtalk_protect = false;
+ }
+}
+
+static void nau8825_xtalk_cancel(struct nau8825 *nau8825)
+{
+ /* If the xtalk_protect is true, that means the process is still
+ * on going. The driver forces to cancel the cross talk task and
+ * restores the configuration to original status.
+ */
+ if (nau8825->xtalk_protect) {
+ cancel_work_sync(&nau8825->xtalk_work);
+ nau8825_xtalk_clean(nau8825);
+ }
+ /* Reset parameters for cross talk suppression function */
+ nau8825_sema_reset(nau8825);
+ nau8825->xtalk_state = NAU8825_XTALK_DONE;
+ nau8825->xtalk_protect = false;
+}
+
static bool nau8825_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -217,12 +885,36 @@ static bool nau8825_volatile_reg(struct device *dev, unsigned int reg)
case NAU8825_REG_SARDOUT_RAM_STATUS:
case NAU8825_REG_CHARGE_PUMP_INPUT_READ:
case NAU8825_REG_GENERAL_STATUS:
+ case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10:
return true;
default:
return false;
}
}
+static int nau8825_adc_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+ struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (!nau8825->irq)
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int nau8825_pump_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -270,6 +962,54 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w,
return 0;
}
+static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+
+ if (!component->regmap)
+ return -EINVAL;
+
+ regmap_raw_read(component->regmap, NAU8825_REG_BIQ_COF1,
+ ucontrol->value.bytes.data, params->max);
+ return 0;
+}
+
+static int nau8825_biq_coeff_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+ void *data;
+
+ if (!component->regmap)
+ return -EINVAL;
+
+ data = kmemdup(ucontrol->value.bytes.data,
+ params->max, GFP_KERNEL | GFP_DMA);
+ if (!data)
+ return -ENOMEM;
+
+ regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL,
+ NAU8825_BIQ_WRT_EN, 0);
+ regmap_raw_write(component->regmap, NAU8825_REG_BIQ_COF1,
+ data, params->max);
+ regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL,
+ NAU8825_BIQ_WRT_EN, NAU8825_BIQ_WRT_EN);
+
+ kfree(data);
+ return 0;
+}
+
+static const char * const nau8825_biq_path[] = {
+ "ADC", "DAC"
+};
+
+static const struct soc_enum nau8825_biq_path_enum =
+ SOC_ENUM_SINGLE(NAU8825_REG_BIQ_CTRL, NAU8825_BIQ_PATH_SFT,
+ ARRAY_SIZE(nau8825_biq_path), nau8825_biq_path);
+
static const char * const nau8825_adc_decimation[] = {
"32", "64", "128", "256"
};
@@ -306,6 +1046,10 @@ static const struct snd_kcontrol_new nau8825_controls[] = {
SOC_ENUM("ADC Decimation Rate", nau8825_adc_decimation_enum),
SOC_ENUM("DAC Oversampling Rate", nau8825_dac_oversampl_enum),
+ /* programmable biquad filter */
+ SOC_ENUM("BIQ Path Select", nau8825_biq_path_enum),
+ SND_SOC_BYTES_EXT("BIQ Coefficients", 20,
+ nau8825_biq_coeff_get, nau8825_biq_coeff_put),
};
/* DAC Mux 0x33[9] and 0x34[9] */
@@ -338,7 +1082,9 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = {
SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0,
NULL, 0),
- SND_SOC_DAPM_ADC("ADC", NULL, NAU8825_REG_ENA_CTRL, 8, 0),
+ SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0,
+ nau8825_adc_event, SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC Power", NAU8825_REG_ANALOG_ADC_2, 6, 0, NULL,
0),
@@ -592,9 +1338,6 @@ int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L);
- regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
- NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0);
-
return 0;
}
EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
@@ -602,24 +1345,21 @@ EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect);
static bool nau8825_is_jack_inserted(struct regmap *regmap)
{
- int status;
+ bool active_high, is_high;
+ int status, jkdet;
+ regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet);
+ active_high = jkdet & NAU8825_JACK_POLARITY;
regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status);
- return !(status & NAU8825_GPIO2JD1);
+ is_high = status & NAU8825_GPIO2JD1;
+ /* return jack connection status according to jack insertion logic
+ * active high or active low.
+ */
+ return active_high == is_high;
}
static void nau8825_restart_jack_detection(struct regmap *regmap)
{
- /* Chip needs one FSCLK cycle in order to generate interrupts,
- * as we cannot guarantee one will be provided by the system. Turning
- * master mode on then off enables us to generate that FSCLK cycle
- * with a minimum of contention on the clock bus.
- */
- regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
- NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER);
- regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
- NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE);
-
/* this will restart the entire jack detection process including MIC/GND
* switching and create interrupts. We have to go from 0 to 1 and back
* to 0 to restart.
@@ -630,11 +1370,30 @@ static void nau8825_restart_jack_detection(struct regmap *regmap)
NAU8825_JACK_DET_RESTART, 0);
}
+static void nau8825_int_status_clear_all(struct regmap *regmap)
+{
+ int active_irq, clear_irq, i;
+
+ /* Reset the intrruption status from rightmost bit if the corres-
+ * ponding irq event occurs.
+ */
+ regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq);
+ for (i = 0; i < NAU8825_REG_DATA_LEN; i++) {
+ clear_irq = (0x1 << i);
+ if (active_irq & clear_irq)
+ regmap_write(regmap,
+ NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
+ }
+}
+
static void nau8825_eject_jack(struct nau8825 *nau8825)
{
struct snd_soc_dapm_context *dapm = nau8825->dapm;
struct regmap *regmap = nau8825->regmap;
+ /* Force to cancel the cross talk detection process */
+ nau8825_xtalk_cancel(nau8825);
+
snd_soc_dapm_disable_pin(dapm, "SAR");
snd_soc_dapm_disable_pin(dapm, "MICBIAS");
/* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */
@@ -644,6 +1403,69 @@ static void nau8825_eject_jack(struct nau8825 *nau8825)
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf);
snd_soc_dapm_sync(dapm);
+
+ /* Clear all interruption status */
+ nau8825_int_status_clear_all(regmap);
+
+ /* Enable the insertion interruption, disable the ejection inter-
+ * ruption, and then bypass de-bounce circuit.
+ */
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL,
+ NAU8825_IRQ_EJECT_DIS | NAU8825_IRQ_INSERT_DIS,
+ NAU8825_IRQ_EJECT_DIS);
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
+ NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN |
+ NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_INSERT_EN,
+ NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN |
+ NAU8825_IRQ_HEADSET_COMPLETE_EN);
+ regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
+ NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS);
+
+ /* Disable ADC needed for interruptions at audo mode */
+ regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_ADC, 0);
+
+ /* Close clock for jack type detection at manual mode */
+ nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0);
+}
+
+/* Enable audo mode interruptions with internal clock. */
+static void nau8825_setup_auto_irq(struct nau8825 *nau8825)
+{
+ struct regmap *regmap = nau8825->regmap;
+
+ /* Enable headset jack type detection complete interruption and
+ * jack ejection interruption.
+ */
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
+ NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0);
+
+ /* Enable internal VCO needed for interruptions */
+ nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
+
+ /* Enable ADC needed for interruptions */
+ regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC);
+
+ /* Chip needs one FSCLK cycle in order to generate interruptions,
+ * as we cannot guarantee one will be provided by the system. Turning
+ * master mode on then off enables us to generate that FSCLK cycle
+ * with a minimum of contention on the clock bus.
+ */
+ regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
+ NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER);
+ regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
+ NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE);
+
+ /* Not bypass de-bounce circuit */
+ regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
+ NAU8825_JACK_DET_DB_BYPASS, 0);
+
+ /* Unmask all interruptions */
+ regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0);
+
+ /* Restart the jack detection process at auto mode */
+ nau8825_restart_jack_detection(regmap);
}
static int nau8825_button_decode(int value)
@@ -676,6 +1498,11 @@ static int nau8825_jack_insert(struct nau8825 *nau8825)
regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg);
mic_detected = (jack_status_reg >> 10) & 3;
+ /* The JKSLV and JKR2 all detected in high impedance headset */
+ if (mic_detected == 0x3)
+ nau8825->high_imped = true;
+ else
+ nau8825->high_imped = false;
switch (mic_detected) {
case 0:
@@ -773,6 +1600,33 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
} else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) {
if (nau8825_is_jack_inserted(regmap)) {
event |= nau8825_jack_insert(nau8825);
+ if (!nau8825->high_imped) {
+ /* Apply the cross talk suppression in the
+ * headset without high impedance.
+ */
+ if (!nau8825->xtalk_protect) {
+ /* Raise protection for cross talk de-
+ * tection if no protection before.
+ * The driver has to cancel the pro-
+ * cess and restore changes if process
+ * is ongoing when ejection.
+ */
+ nau8825->xtalk_protect = true;
+ nau8825_sema_acquire(nau8825, 0);
+ }
+ /* Startup cross talk detection process */
+ nau8825->xtalk_state = NAU8825_XTALK_PREPARE;
+ schedule_work(&nau8825->xtalk_work);
+ } else {
+ /* The cross talk suppression shouldn't apply
+ * in the headset with high impedance. Thus,
+ * relieve the protection raised before.
+ */
+ if (nau8825->xtalk_protect) {
+ nau8825_sema_release(nau8825);
+ nau8825->xtalk_protect = false;
+ }
+ }
} else {
dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
nau8825_eject_jack(nau8825);
@@ -780,6 +1634,37 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
event_mask |= SND_JACK_HEADSET;
clear_irq = NAU8825_HEADSET_COMPLETION_IRQ;
+ /* Record the interruption report event for driver to report
+ * the event later. The jack report will delay until cross
+ * talk detection process is done.
+ */
+ if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) {
+ nau8825->xtalk_event = event;
+ nau8825->xtalk_event_mask = event_mask;
+ }
+ } else if (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) {
+ schedule_work(&nau8825->xtalk_work);
+ clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ;
+ } else if ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) ==
+ NAU8825_JACK_INSERTION_DETECTED) {
+ /* One more step to check GPIO status directly. Thus, the
+ * driver can confirm the real insertion interruption because
+ * the intrruption at manual mode has bypassed debounce
+ * circuit which can get rid of unstable status.
+ */
+ if (nau8825_is_jack_inserted(regmap)) {
+ /* Turn off insertion interruption at manual mode */
+ regmap_update_bits(regmap,
+ NAU8825_REG_INTERRUPT_DIS_CTRL,
+ NAU8825_IRQ_INSERT_DIS,
+ NAU8825_IRQ_INSERT_DIS);
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
+ NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN);
+ /* Enable interruption for jack type detection at audo
+ * mode which can detect microphone and jack type.
+ */
+ nau8825_setup_auto_irq(nau8825);
+ }
}
if (!clear_irq)
@@ -787,7 +1672,12 @@ static irqreturn_t nau8825_interrupt(int irq, void *data)
/* clears the rightmost interruption */
regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
- if (event_mask)
+ /* Delay jack report until cross talk detection is done. It can avoid
+ * application to do playback preparation when cross talk detection
+ * process is still working. Otherwise, the resource like clock and
+ * power will be issued by them at the same time and conflict happens.
+ */
+ if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE)
snd_soc_jack_report(nau8825->jack, event, event_mask);
return IRQ_HANDLED;
@@ -921,11 +1811,16 @@ static void nau8825_init_regs(struct nau8825 *nau8825)
NAU8825_RDAC_CLK_DELAY_MASK | NAU8825_RDAC_VREF_MASK,
(0x2 << NAU8825_RDAC_CLK_DELAY_SFT) |
(0x3 << NAU8825_RDAC_VREF_SFT));
+ /* Config L/R channel */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL,
+ NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_L);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL,
+ NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_R);
}
static const struct regmap_config nau8825_regmap_config = {
- .val_bits = 16,
- .reg_bits = 16,
+ .val_bits = NAU8825_REG_DATA_LEN,
+ .reg_bits = NAU8825_REG_ADDR_LEN,
.max_register = NAU8825_REG_MAX,
.readable_reg = nau8825_readable_reg,
@@ -944,18 +1839,15 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec)
nau8825->dapm = dapm;
- /* The interrupt clock is gated by x1[10:8],
- * one of them needs to be enabled all the time for
- * interrupts to happen.
- */
- snd_soc_dapm_force_enable_pin(dapm, "DDACR");
- snd_soc_dapm_sync(dapm);
+ return 0;
+}
- /* Unmask interruptions. Handler uses dapm object so we can enable
- * interruptions only after dapm is fully initialized.
- */
- regmap_write(nau8825->regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0);
- nau8825_restart_jack_detection(nau8825->regmap);
+static int nau8825_codec_remove(struct snd_soc_codec *codec)
+{
+ struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
+
+ /* Cancel and reset cross tak suppresstion detection funciton */
+ nau8825_xtalk_cancel(nau8825);
return 0;
}
@@ -973,8 +1865,8 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec)
static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs,
struct nau8825_fll *fll_param)
{
- u64 fvco;
- unsigned int fref, i;
+ u64 fvco, fvco_max;
+ unsigned int fref, i, fvco_sel;
/* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing
* freq_in by 1, 2, 4, or 8 using FLL pre-scalar.
@@ -999,18 +1891,23 @@ static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs,
fll_param->ratio = fll_ratio[i].val;
/* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs.
- * FDCO must be within the 90MHz - 100MHz or the FFL cannot be
+ * FDCO must be within the 90MHz - 124MHz or the FFL cannot be
* guaranteed across the full range of operation.
* FDCO = freq_out * 2 * mclk_src_scaling
*/
+ fvco_max = 0;
+ fvco_sel = ARRAY_SIZE(mclk_src_scaling);
for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) {
fvco = 256 * fs * 2 * mclk_src_scaling[i].param;
- if (NAU_FVCO_MIN < fvco && fvco < NAU_FVCO_MAX)
- break;
+ if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX &&
+ fvco_max < fvco) {
+ fvco_max = fvco;
+ fvco_sel = i;
+ }
}
- if (i == ARRAY_SIZE(mclk_src_scaling))
+ if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel)
return -EINVAL;
- fll_param->mclk_src = mclk_src_scaling[i].val;
+ fll_param->mclk_src = mclk_src_scaling[fvco_sel].val;
/* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional
* input based on FDCO, FREF and FLL ratio.
@@ -1025,7 +1922,8 @@ static void nau8825_fll_apply(struct nau8825 *nau8825,
struct nau8825_fll *fll_param)
{
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
- NAU8825_CLK_MCLK_SRC_MASK, fll_param->mclk_src);
+ NAU8825_CLK_SRC_MASK | NAU8825_CLK_MCLK_SRC_MASK,
+ NAU8825_CLK_SRC_MCLK | fll_param->mclk_src);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
NAU8825_FLL_RATIO_MASK, fll_param->ratio);
/* FLL 16-bit fractional input */
@@ -1038,10 +1936,25 @@ static void nau8825_fll_apply(struct nau8825 *nau8825,
NAU8825_FLL_REF_DIV_MASK, fll_param->clk_ref_div);
/* select divided VCO input */
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
- NAU8825_FLL_FILTER_SW_MASK, 0x0000);
- /* FLL sigma delta modulator enable */
- regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6,
- NAU8825_SDM_EN_MASK, NAU8825_SDM_EN);
+ NAU8825_FLL_CLK_SW_MASK, NAU8825_FLL_CLK_SW_REF);
+ /* Disable free-running mode */
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_FLL6, NAU8825_DCO_EN, 0);
+ if (fll_param->fll_frac) {
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
+ NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
+ NAU8825_FLL_FTR_SW_MASK,
+ NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
+ NAU8825_FLL_FTR_SW_FILTER);
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6,
+ NAU8825_SDM_EN, NAU8825_SDM_EN);
+ } else {
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5,
+ NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN |
+ NAU8825_FLL_FTR_SW_MASK, NAU8825_FLL_FTR_SW_ACCU);
+ regmap_update_bits(nau8825->regmap,
+ NAU8825_REG_FLL6, NAU8825_SDM_EN, 0);
+ }
}
/* freq_out must be 256*Fs in order to achieve the best performance */
@@ -1069,6 +1982,45 @@ static int nau8825_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
return 0;
}
+static int nau8825_mclk_prepare(struct nau8825 *nau8825, unsigned int freq)
+{
+ int ret = 0;
+
+ nau8825->mclk = devm_clk_get(nau8825->dev, "mclk");
+ if (IS_ERR(nau8825->mclk)) {
+ dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally");
+ return 0;
+ }
+
+ if (!nau8825->mclk_freq) {
+ ret = clk_prepare_enable(nau8825->mclk);
+ if (ret) {
+ dev_err(nau8825->dev, "Unable to prepare codec mclk\n");
+ return ret;
+ }
+ }
+
+ if (nau8825->mclk_freq != freq) {
+ freq = clk_round_rate(nau8825->mclk, freq);
+ ret = clk_set_rate(nau8825->mclk, freq);
+ if (ret) {
+ dev_err(nau8825->dev, "Unable to set mclk rate\n");
+ return ret;
+ }
+ nau8825->mclk_freq = freq;
+ }
+
+ return 0;
+}
+
+static void nau8825_configure_mclk_as_sysclk(struct regmap *regmap)
+{
+ regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
+ NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK);
+ regmap_update_bits(regmap, NAU8825_REG_FLL6,
+ NAU8825_DCO_EN, 0);
+}
+
static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
unsigned int freq)
{
@@ -1076,40 +2028,106 @@ static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id,
int ret;
switch (clk_id) {
+ case NAU8825_CLK_DIS:
+ /* Clock provided externally and disable internal VCO clock */
+ nau8825_configure_mclk_as_sysclk(regmap);
+ if (nau8825->mclk_freq) {
+ clk_disable_unprepare(nau8825->mclk);
+ nau8825->mclk_freq = 0;
+ }
+
+ break;
case NAU8825_CLK_MCLK:
+ /* Acquire the semaphone to synchronize the playback and
+ * interrupt handler. In order to avoid the playback inter-
+ * fered by cross talk process, the driver make the playback
+ * preparation halted until cross talk process finish.
+ */
+ nau8825_sema_acquire(nau8825, 2 * HZ);
+ nau8825_configure_mclk_as_sysclk(regmap);
+ /* MCLK not changed by clock tree */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
- NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK);
- regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, 0);
+ NAU8825_CLK_MCLK_SRC_MASK, 0);
+ /* Release the semaphone. */
+ nau8825_sema_release(nau8825);
- /* We selected MCLK source but the clock itself managed externally */
- if (!nau8825->mclk)
- break;
+ ret = nau8825_mclk_prepare(nau8825, freq);
+ if (ret)
+ return ret;
- if (!nau8825->mclk_freq) {
- ret = clk_prepare_enable(nau8825->mclk);
- if (ret) {
- dev_err(nau8825->dev, "Unable to prepare codec mclk\n");
- return ret;
- }
+ break;
+ case NAU8825_CLK_INTERNAL:
+ if (nau8825_is_jack_inserted(nau8825->regmap)) {
+ regmap_update_bits(regmap, NAU8825_REG_FLL6,
+ NAU8825_DCO_EN, NAU8825_DCO_EN);
+ regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
+ NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
+ /* Decrease the VCO frequency for power saving */
+ regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
+ NAU8825_CLK_MCLK_SRC_MASK, 0xf);
+ regmap_update_bits(regmap, NAU8825_REG_FLL1,
+ NAU8825_FLL_RATIO_MASK, 0x10);
+ regmap_update_bits(regmap, NAU8825_REG_FLL6,
+ NAU8825_SDM_EN, NAU8825_SDM_EN);
+ } else {
+ /* The clock turns off intentionally for power saving
+ * when no headset connected.
+ */
+ nau8825_configure_mclk_as_sysclk(regmap);
+ dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n");
+ }
+ if (nau8825->mclk_freq) {
+ clk_disable_unprepare(nau8825->mclk);
+ nau8825->mclk_freq = 0;
}
- if (nau8825->mclk_freq != freq) {
- nau8825->mclk_freq = freq;
+ break;
+ case NAU8825_CLK_FLL_MCLK:
+ /* Acquire the semaphone to synchronize the playback and
+ * interrupt handler. In order to avoid the playback inter-
+ * fered by cross talk process, the driver make the playback
+ * preparation halted until cross talk process finish.
+ */
+ nau8825_sema_acquire(nau8825, 2 * HZ);
+ regmap_update_bits(regmap, NAU8825_REG_FLL3,
+ NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_MCLK);
+ /* Release the semaphone. */
+ nau8825_sema_release(nau8825);
- freq = clk_round_rate(nau8825->mclk, freq);
- ret = clk_set_rate(nau8825->mclk, freq);
- if (ret) {
- dev_err(nau8825->dev, "Unable to set mclk rate\n");
- return ret;
- }
+ ret = nau8825_mclk_prepare(nau8825, freq);
+ if (ret)
+ return ret;
+
+ break;
+ case NAU8825_CLK_FLL_BLK:
+ /* Acquire the semaphone to synchronize the playback and
+ * interrupt handler. In order to avoid the playback inter-
+ * fered by cross talk process, the driver make the playback
+ * preparation halted until cross talk process finish.
+ */
+ nau8825_sema_acquire(nau8825, 2 * HZ);
+ regmap_update_bits(regmap, NAU8825_REG_FLL3,
+ NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_BLK);
+ /* Release the semaphone. */
+ nau8825_sema_release(nau8825);
+
+ if (nau8825->mclk_freq) {
+ clk_disable_unprepare(nau8825->mclk);
+ nau8825->mclk_freq = 0;
}
break;
- case NAU8825_CLK_INTERNAL:
- regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN,
- NAU8825_DCO_EN);
- regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
- NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
+ case NAU8825_CLK_FLL_FS:
+ /* Acquire the semaphone to synchronize the playback and
+ * interrupt handler. In order to avoid the playback inter-
+ * fered by cross talk process, the driver make the playback
+ * preparation halted until cross talk process finish.
+ */
+ nau8825_sema_acquire(nau8825, 2 * HZ);
+ regmap_update_bits(regmap, NAU8825_REG_FLL3,
+ NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_FS);
+ /* Release the semaphone. */
+ nau8825_sema_release(nau8825);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
@@ -1135,6 +2153,31 @@ static int nau8825_set_sysclk(struct snd_soc_codec *codec, int clk_id,
return nau8825_configure_sysclk(nau8825, clk_id, freq);
}
+static int nau8825_resume_setup(struct nau8825 *nau8825)
+{
+ struct regmap *regmap = nau8825->regmap;
+
+ /* Close clock when jack type detection at manual mode */
+ nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0);
+
+ /* Clear all interruption status */
+ nau8825_int_status_clear_all(regmap);
+
+ /* Enable both insertion and ejection interruptions, and then
+ * bypass de-bounce circuit.
+ */
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
+ NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN |
+ NAU8825_IRQ_EJECT_EN | NAU8825_IRQ_INSERT_EN,
+ NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN);
+ regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
+ NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS);
+ regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL,
+ NAU8825_IRQ_INSERT_DIS | NAU8825_IRQ_EJECT_DIS, 0);
+
+ return 0;
+}
+
static int nau8825_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
@@ -1157,10 +2200,22 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec,
return ret;
}
}
+ /* Setup codec configuration after resume */
+ nau8825_resume_setup(nau8825);
}
break;
case SND_SOC_BIAS_OFF:
+ /* Cancel and reset cross talk detection funciton */
+ nau8825_xtalk_cancel(nau8825);
+ /* Turn off all interruptions before system shutdown. Keep the
+ * interruption quiet before resume setup completes.
+ */
+ regmap_write(nau8825->regmap,
+ NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff);
+ /* Disable ADC needed for interruptions at audo mode */
+ regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
+ NAU8825_ENABLE_ADC, 0);
if (nau8825->mclk_freq)
clk_disable_unprepare(nau8825->mclk);
break;
@@ -1168,57 +2223,46 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
-#ifdef CONFIG_PM
-static int nau8825_suspend(struct snd_soc_codec *codec)
+static int __maybe_unused nau8825_suspend(struct snd_soc_codec *codec)
{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
disable_irq(nau8825->irq);
+ snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF);
regcache_cache_only(nau8825->regmap, true);
regcache_mark_dirty(nau8825->regmap);
return 0;
}
-static int nau8825_resume(struct snd_soc_codec *codec)
+static int __maybe_unused nau8825_resume(struct snd_soc_codec *codec)
{
struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec);
- /* The chip may lose power and reset in S3. regcache_sync restores
- * register values including configurations for sysclk, irq, and
- * jack/button detection.
- */
regcache_cache_only(nau8825->regmap, false);
regcache_sync(nau8825->regmap);
-
- /* Check the jack plug status directly. If the headset is unplugged
- * during S3 when the chip has no power, there will be no jack
- * detection irq even after the nau8825_restart_jack_detection below,
- * because the chip just thinks no headset has ever been plugged in.
- */
- if (!nau8825_is_jack_inserted(nau8825->regmap)) {
- nau8825_eject_jack(nau8825);
- snd_soc_jack_report(nau8825->jack, 0, SND_JACK_HEADSET);
+ if (nau8825_is_jack_inserted(nau8825->regmap)) {
+ /* If the jack is inserted, we need to check whether the play-
+ * back is active before suspend. If active, the driver has to
+ * raise the protection for cross talk function to avoid the
+ * playback recovers before cross talk process finish. Other-
+ * wise, the playback will be interfered by cross talk func-
+ * tion. It is better to apply hardware related parameters
+ * before starting playback or record.
+ */
+ if (nau8825_dai_is_active(nau8825)) {
+ nau8825->xtalk_protect = true;
+ nau8825_sema_acquire(nau8825, 0);
+ }
}
-
enable_irq(nau8825->irq);
- /* Run jack detection to check the type (OMTP or CTIA) of the headset
- * if there is one. This handles the case where a different type of
- * headset is plugged in during S3. This triggers an IRQ iff a headset
- * is already plugged in.
- */
- nau8825_restart_jack_detection(nau8825->regmap);
-
return 0;
}
-#else
-#define nau8825_suspend NULL
-#define nau8825_resume NULL
-#endif
static struct snd_soc_codec_driver nau8825_codec_driver = {
.probe = nau8825_codec_probe,
+ .remove = nau8825_codec_remove,
.set_sysclk = nau8825_set_sysclk,
.set_pll = nau8825_set_pll,
.set_bias_level = nau8825_set_bias_level,
@@ -1318,22 +2362,8 @@ static int nau8825_read_device_properties(struct device *dev,
static int nau8825_setup_irq(struct nau8825 *nau8825)
{
- struct regmap *regmap = nau8825->regmap;
int ret;
- /* IRQ Output Enable */
- regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
- NAU8825_IRQ_OUTPUT_EN, NAU8825_IRQ_OUTPUT_EN);
-
- /* Enable internal VCO needed for interruptions */
- nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
-
- /* Enable DDACR needed for interrupts
- * It is the same as force_enable_pin("DDACR") we do later
- */
- regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL,
- NAU8825_ENABLE_DACR, NAU8825_ENABLE_DACR);
-
ret = devm_request_threaded_irq(nau8825->dev, nau8825->irq, NULL,
nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"nau8825", nau8825);
@@ -1370,6 +2400,13 @@ static int nau8825_i2c_probe(struct i2c_client *i2c,
return PTR_ERR(nau8825->regmap);
nau8825->dev = dev;
nau8825->irq = i2c->irq;
+ /* Initiate parameters, semaphone and work queue which are needed in
+ * cross talk suppression measurment function.
+ */
+ nau8825->xtalk_state = NAU8825_XTALK_DONE;
+ nau8825->xtalk_protect = false;
+ sema_init(&nau8825->xtalk_sem, 1);
+ INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work);
nau8825_print_device_properties(nau8825);
@@ -1405,6 +2442,7 @@ static const struct i2c_device_id nau8825_i2c_ids[] = {
{ "nau8825", 0 },
{ }
};
+MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids);
#ifdef CONFIG_OF
static const struct of_device_id nau8825_of_ids[] = {
diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h
index 8ceb5f385478..1c63e2abafa9 100644
--- a/sound/soc/codecs/nau8825.h
+++ b/sound/soc/codecs/nau8825.h
@@ -93,12 +93,21 @@
#define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81
#define NAU8825_REG_GENERAL_STATUS 0x82
#define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS
+/* 16-bit control register address, and 16-bits control register data */
+#define NAU8825_REG_ADDR_LEN 16
+#define NAU8825_REG_DATA_LEN 16
/* ENA_CTRL (0x1) */
#define NAU8825_ENABLE_DACR_SFT 10
#define NAU8825_ENABLE_DACR (1 << NAU8825_ENABLE_DACR_SFT)
#define NAU8825_ENABLE_DACL_SFT 9
+#define NAU8825_ENABLE_DACL (1 << NAU8825_ENABLE_DACL_SFT)
#define NAU8825_ENABLE_ADC_SFT 8
+#define NAU8825_ENABLE_ADC (1 << NAU8825_ENABLE_ADC_SFT)
+#define NAU8825_ENABLE_ADC_CLK_SFT 7
+#define NAU8825_ENABLE_ADC_CLK (1 << NAU8825_ENABLE_ADC_CLK_SFT)
+#define NAU8825_ENABLE_DAC_CLK_SFT 6
+#define NAU8825_ENABLE_DAC_CLK (1 << NAU8825_ENABLE_DAC_CLK_SFT)
#define NAU8825_ENABLE_SAR_SFT 1
/* CLK_DIVIDER (0x3) */
@@ -113,20 +122,28 @@
/* FLL3 (0x06) */
#define NAU8825_FLL_INTEGER_MASK (0x3ff << 0)
+#define NAU8825_FLL_CLK_SRC_SFT 10
+#define NAU8825_FLL_CLK_SRC_MASK (0x3 << NAU8825_FLL_CLK_SRC_SFT)
+#define NAU8825_FLL_CLK_SRC_MCLK (0 << NAU8825_FLL_CLK_SRC_SFT)
+#define NAU8825_FLL_CLK_SRC_BLK (0x2 << NAU8825_FLL_CLK_SRC_SFT)
+#define NAU8825_FLL_CLK_SRC_FS (0x3 << NAU8825_FLL_CLK_SRC_SFT)
/* FLL4 (0x07) */
#define NAU8825_FLL_REF_DIV_MASK (0x3 << 10)
/* FLL5 (0x08) */
-#define NAU8825_FLL_FILTER_SW_MASK (0x1 << 14)
+#define NAU8825_FLL_PDB_DAC_EN (0x1 << 15)
+#define NAU8825_FLL_LOOP_FTR_EN (0x1 << 14)
+#define NAU8825_FLL_CLK_SW_MASK (0x1 << 13)
+#define NAU8825_FLL_CLK_SW_N2 (0x1 << 13)
+#define NAU8825_FLL_CLK_SW_REF (0x0 << 13)
+#define NAU8825_FLL_FTR_SW_MASK (0x1 << 12)
+#define NAU8825_FLL_FTR_SW_ACCU (0x1 << 12)
+#define NAU8825_FLL_FTR_SW_FILTER (0x0 << 12)
/* FLL6 (0x9) */
-#define NAU8825_DCO_EN_MASK (0x1 << 15)
#define NAU8825_DCO_EN (0x1 << 15)
-#define NAU8825_DCO_DIS (0x0 << 15)
-#define NAU8825_SDM_EN_MASK (0x1 << 14)
#define NAU8825_SDM_EN (0x1 << 14)
-#define NAU8825_SDM_DIS (0x0 << 14)
/* HSD_CTRL (0xc) */
#define NAU8825_HSD_AUTO_MODE (1 << 6)
@@ -136,6 +153,7 @@
/* JACK_DET_CTRL (0xd) */
#define NAU8825_JACK_DET_RESTART (1 << 9)
+#define NAU8825_JACK_DET_DB_BYPASS (1 << 8)
#define NAU8825_JACK_INSERT_DEBOUNCE_SFT 5
#define NAU8825_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_INSERT_DEBOUNCE_SFT)
#define NAU8825_JACK_EJECT_DEBOUNCE_SFT 2
@@ -145,9 +163,11 @@
/* INTERRUPT_MASK (0xf) */
#define NAU8825_IRQ_OUTPUT_EN (1 << 11)
#define NAU8825_IRQ_HEADSET_COMPLETE_EN (1 << 10)
+#define NAU8825_IRQ_RMS_EN (1 << 8)
#define NAU8825_IRQ_KEY_RELEASE_EN (1 << 7)
#define NAU8825_IRQ_KEY_SHORT_PRESS_EN (1 << 5)
#define NAU8825_IRQ_EJECT_EN (1 << 2)
+#define NAU8825_IRQ_INSERT_EN (1 << 0)
/* IRQ_STATUS (0x10) */
#define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10)
@@ -168,6 +188,7 @@
#define NAU8825_IRQ_KEY_RELEASE_DIS (1 << 7)
#define NAU8825_IRQ_KEY_SHORT_PRESS_DIS (1 << 5)
#define NAU8825_IRQ_EJECT_DIS (1 << 2)
+#define NAU8825_IRQ_INSERT_DIS (1 << 0)
/* SAR_CTRL (0x13) */
#define NAU8825_SAR_ADC_EN_SFT 12
@@ -217,10 +238,21 @@
/* I2S_PCM_CTRL2 (0x1d) */
#define NAU8825_I2S_TRISTATE (1 << 15) /* 0 - normal mode, 1 - Hi-Z output */
+#define NAU8825_I2S_DRV_SFT 12
+#define NAU8825_I2S_DRV_MASK (0x3 << NAU8825_I2S_DRV_SFT)
#define NAU8825_I2S_MS_SFT 3
#define NAU8825_I2S_MS_MASK (1 << NAU8825_I2S_MS_SFT)
#define NAU8825_I2S_MS_MASTER (1 << NAU8825_I2S_MS_SFT)
#define NAU8825_I2S_MS_SLAVE (0 << NAU8825_I2S_MS_SFT)
+#define NAU8825_I2S_BLK_DIV_MASK 0x7
+
+/* BIQ_CTRL (0x20) */
+#define NAU8825_BIQ_WRT_SFT 4
+#define NAU8825_BIQ_WRT_EN (1 << NAU8825_BIQ_WRT_SFT)
+#define NAU8825_BIQ_PATH_SFT 0
+#define NAU8825_BIQ_PATH_MASK (1 << NAU8825_BIQ_PATH_SFT)
+#define NAU8825_BIQ_PATH_ADC (0 << NAU8825_BIQ_PATH_SFT)
+#define NAU8825_BIQ_PATH_DAC (1 << NAU8825_BIQ_PATH_SFT)
/* ADC_RATE (0x2b) */
#define NAU8825_ADC_SYNC_DOWN_SFT 0
@@ -239,22 +271,72 @@
#define NAU8825_DAC_OVERSAMPLE_128 2
#define NAU8825_DAC_OVERSAMPLE_32 4
+/* ADC_DGAIN_CTRL (0x30) */
+#define NAU8825_ADC_DIG_VOL_MASK 0xff
+
/* MUTE_CTRL (0x31) */
#define NAU8825_DAC_ZERO_CROSSING_EN (1 << 9)
#define NAU8825_DAC_SOFT_MUTE (1 << 9)
/* HSVOL_CTRL (0x32) */
#define NAU8825_HP_MUTE (1 << 15)
+#define NAU8825_HP_MUTE_AUTO (1 << 14)
+#define NAU8825_HPL_MUTE (1 << 13)
+#define NAU8825_HPR_MUTE (1 << 12)
+#define NAU8825_HPL_VOL_SFT 6
+#define NAU8825_HPL_VOL_MASK (0x3f << NAU8825_HPL_VOL_SFT)
+#define NAU8825_HPR_VOL_SFT 0
+#define NAU8825_HPR_VOL_MASK (0x3f << NAU8825_HPR_VOL_SFT)
+#define NAU8825_HP_VOL_MIN 0x36
/* DACL_CTRL (0x33) */
#define NAU8825_DACL_CH_SEL_SFT 9
+#define NAU8825_DACL_CH_SEL_MASK (0x1 << NAU8825_DACL_CH_SEL_SFT)
+#define NAU8825_DACL_CH_SEL_L (0x0 << NAU8825_DACL_CH_SEL_SFT)
+#define NAU8825_DACL_CH_SEL_R (0x1 << NAU8825_DACL_CH_SEL_SFT)
+#define NAU8825_DACL_CH_VOL_MASK 0xff
/* DACR_CTRL (0x34) */
#define NAU8825_DACR_CH_SEL_SFT 9
+#define NAU8825_DACR_CH_SEL_MASK (0x1 << NAU8825_DACR_CH_SEL_SFT)
+#define NAU8825_DACR_CH_SEL_L (0x0 << NAU8825_DACR_CH_SEL_SFT)
+#define NAU8825_DACR_CH_SEL_R (0x1 << NAU8825_DACR_CH_SEL_SFT)
+#define NAU8825_DACR_CH_VOL_MASK 0xff
+
+/* IMM_MODE_CTRL (0x4C) */
+#define NAU8825_IMM_THD_SFT 8
+#define NAU8825_IMM_THD_MASK (0x3f << NAU8825_IMM_THD_SFT)
+#define NAU8825_IMM_GEN_VOL_SFT 6
+#define NAU8825_IMM_GEN_VOL_MASK (0x3 << NAU8825_IMM_GEN_VOL_SFT)
+#define NAU8825_IMM_GEN_VOL_1_2nd (0x0 << NAU8825_IMM_GEN_VOL_SFT)
+#define NAU8825_IMM_GEN_VOL_1_4th (0x1 << NAU8825_IMM_GEN_VOL_SFT)
+#define NAU8825_IMM_GEN_VOL_1_8th (0x2 << NAU8825_IMM_GEN_VOL_SFT)
+#define NAU8825_IMM_GEN_VOL_1_16th (0x3 << NAU8825_IMM_GEN_VOL_SFT)
+
+#define NAU8825_IMM_CYC_SFT 4
+#define NAU8825_IMM_CYC_MASK (0x3 << NAU8825_IMM_CYC_SFT)
+#define NAU8825_IMM_CYC_1024 (0x0 << NAU8825_IMM_CYC_SFT)
+#define NAU8825_IMM_CYC_2048 (0x1 << NAU8825_IMM_CYC_SFT)
+#define NAU8825_IMM_CYC_4096 (0x2 << NAU8825_IMM_CYC_SFT)
+#define NAU8825_IMM_CYC_8192 (0x3 << NAU8825_IMM_CYC_SFT)
+#define NAU8825_IMM_EN (1 << 3)
+#define NAU8825_IMM_DAC_SRC_MASK 0x7
+#define NAU8825_IMM_DAC_SRC_BIQ 0x0
+#define NAU8825_IMM_DAC_SRC_DRC 0x1
+#define NAU8825_IMM_DAC_SRC_MIX 0x2
+#define NAU8825_IMM_DAC_SRC_SIN 0x3
/* CLASSG_CTRL (0x50) */
#define NAU8825_CLASSG_TIMER_SFT 8
#define NAU8825_CLASSG_TIMER_MASK (0x3f << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_1ms (0x1 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_2ms (0x2 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_8ms (0x4 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_16ms (0x8 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_32ms (0x10 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_TIMER_64ms (0x20 << NAU8825_CLASSG_TIMER_SFT)
+#define NAU8825_CLASSG_LDAC_EN (0x1 << 2)
+#define NAU8825_CLASSG_RDAC_EN (0x1 << 1)
#define NAU8825_CLASSG_EN (1 << 0)
/* I2C_DEVICE_ID (0x58) */
@@ -263,7 +345,12 @@
#define NAU8825_SOFTWARE_ID_NAU8825 0x0
/* BIAS_ADJ (0x66) */
-#define NAU8825_BIAS_TESTDAC_EN (0x3 << 8)
+#define NAU8825_BIAS_HPR_IMP (1 << 15)
+#define NAU8825_BIAS_HPL_IMP (1 << 14)
+#define NAU8825_BIAS_TESTDAC_SFT 8
+#define NAU8825_BIAS_TESTDAC_EN (0x3 << NAU8825_BIAS_TESTDAC_SFT)
+#define NAU8825_BIAS_TESTDACR_EN (0x2 << NAU8825_BIAS_TESTDAC_SFT)
+#define NAU8825_BIAS_TESTDACL_EN (0x1 << NAU8825_BIAS_TESTDAC_SFT)
#define NAU8825_BIAS_VMID (1 << 6)
#define NAU8825_BIAS_VMID_SEL_SFT 4
#define NAU8825_BIAS_VMID_SEL_MASK (3 << NAU8825_BIAS_VMID_SEL_SFT)
@@ -282,6 +369,11 @@
#define NAU8825_POWERUP_ADCL (1 << 6)
/* RDAC (0x73) */
+#define NAU8825_RDAC_FS_BCLK_ENB (1 << 15)
+#define NAU8825_RDAC_EN_SFT 12
+#define NAU8825_RDAC_EN (0x3 << NAU8825_RDAC_EN_SFT)
+#define NAU8825_RDAC_CLK_EN_SFT 8
+#define NAU8825_RDAC_CLK_EN (0x3 << NAU8825_RDAC_CLK_EN_SFT)
#define NAU8825_RDAC_CLK_DELAY_SFT 4
#define NAU8825_RDAC_CLK_DELAY_MASK (0x7 << NAU8825_RDAC_CLK_DELAY_SFT)
#define NAU8825_RDAC_VREF_SFT 2
@@ -318,8 +410,21 @@
/* System Clock Source */
enum {
- NAU8825_CLK_MCLK = 0,
+ NAU8825_CLK_DIS = 0,
+ NAU8825_CLK_MCLK,
NAU8825_CLK_INTERNAL,
+ NAU8825_CLK_FLL_MCLK,
+ NAU8825_CLK_FLL_BLK,
+ NAU8825_CLK_FLL_FS,
+};
+
+/* Cross talk detection state */
+enum {
+ NAU8825_XTALK_PREPARE = 0,
+ NAU8825_XTALK_HPR_R2L,
+ NAU8825_XTALK_HPL_R2L,
+ NAU8825_XTALK_IMM,
+ NAU8825_XTALK_DONE,
};
struct nau8825 {
@@ -328,6 +433,8 @@ struct nau8825 {
struct snd_soc_dapm_context *dapm;
struct snd_soc_jack *jack;
struct clk *mclk;
+ struct work_struct xtalk_work;
+ struct semaphore xtalk_sem;
int irq;
int mclk_freq; /* 0 - mclk is disabled */
int button_pressed;
@@ -346,6 +453,12 @@ struct nau8825 {
int key_debounce;
int jack_insert_debounce;
int jack_eject_debounce;
+ int high_imped;
+ int xtalk_state;
+ int xtalk_event;
+ int xtalk_event_mask;
+ bool xtalk_protect;
+ int imp_rms[NAU8825_XTALK_IMM];
};
int nau8825_enable_jack_detect(struct snd_soc_codec *codec,
diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c
index 58325234285c..33e1fc2d1598 100644
--- a/sound/soc/codecs/pcm1681.c
+++ b/sound/soc/codecs/pcm1681.c
@@ -73,7 +73,7 @@ static bool pcm1681_accessible_reg(struct device *dev, unsigned int reg)
return !((reg == 0x00) || (reg == 0x0f));
}
-static bool pcm1681_writeable_reg(struct device *dev, unsigned register reg)
+static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg)
{
return pcm1681_accessible_reg(dev, reg) &&
(reg != PCM1681_ZERO_DETECT_STATUS);
diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c
index 06a66579ca6d..88fbdd184aa0 100644
--- a/sound/soc/codecs/pcm179x.c
+++ b/sound/soc/codecs/pcm179x.c
@@ -59,7 +59,7 @@ static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg)
return reg >= 0x10 && reg <= 0x17;
}
-static bool pcm179x_writeable_reg(struct device *dev, unsigned register reg)
+static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg)
{
bool accessible;
diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c
index ed515677409b..8ba322a00363 100644
--- a/sound/soc/codecs/pcm5102a.c
+++ b/sound/soc/codecs/pcm5102a.c
@@ -57,7 +57,6 @@ static struct platform_driver pcm5102a_codec_driver = {
.remove = pcm5102a_remove,
.driver = {
.name = "pcm5102a-codec",
- .owner = THIS_MODULE,
.of_match_table = pcm5102a_of_match,
},
};
diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c
index 1bd31644a782..74c0e4eb3788 100644
--- a/sound/soc/codecs/rt286.c
+++ b/sound/soc/codecs/rt286.c
@@ -1100,6 +1100,13 @@ static const struct dmi_system_id force_combo_jack_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "Skylake Client platform")
}
},
+ {
+ .ident = "Intel Kabylake RVP",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Kabylake Client platform")
+ }
+ },
+
{ }
};
diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c
new file mode 100644
index 000000000000..77ff8ebe6dfb
--- /dev/null
+++ b/sound/soc/codecs/rt5514-spi.c
@@ -0,0 +1,447 @@
+/*
+ * rt5514-spi.c -- RT5514 SPI driver
+ *
+ * Copyright 2015 Realtek Semiconductor Corp.
+ * Author: Oder Chiou <oder_chiou@realtek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/spi/spi.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_qos.h>
+#include <linux/sysfs.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "rt5514-spi.h"
+
+static struct spi_device *rt5514_spi;
+
+struct rt5514_dsp {
+ struct device *dev;
+ struct delayed_work copy_work;
+ struct mutex dma_lock;
+ struct snd_pcm_substream *substream;
+ unsigned int buf_base, buf_limit, buf_rp;
+ size_t buf_size;
+ size_t dma_offset;
+ size_t dsp_offset;
+};
+
+static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = 0x20000 / 8,
+ .periods_min = 8,
+ .periods_max = 8,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = 0x20000,
+};
+
+static struct snd_soc_dai_driver rt5514_spi_dai = {
+ .name = "rt5514-dsp-cpu-dai",
+ .id = 0,
+ .capture = {
+ .stream_name = "DSP Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static void rt5514_spi_copy_work(struct work_struct *work)
+{
+ struct rt5514_dsp *rt5514_dsp =
+ container_of(work, struct rt5514_dsp, copy_work.work);
+ struct snd_pcm_runtime *runtime;
+ size_t period_bytes, truncated_bytes = 0;
+
+ mutex_lock(&rt5514_dsp->dma_lock);
+ if (!rt5514_dsp->substream) {
+ dev_err(rt5514_dsp->dev, "No pcm substream\n");
+ goto done;
+ }
+
+ runtime = rt5514_dsp->substream->runtime;
+ period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream);
+
+ if (rt5514_dsp->buf_size - rt5514_dsp->dsp_offset < period_bytes)
+ period_bytes = rt5514_dsp->buf_size - rt5514_dsp->dsp_offset;
+
+ if (rt5514_dsp->buf_rp + period_bytes <= rt5514_dsp->buf_limit) {
+ rt5514_spi_burst_read(rt5514_dsp->buf_rp,
+ runtime->dma_area + rt5514_dsp->dma_offset,
+ period_bytes);
+
+ if (rt5514_dsp->buf_rp + period_bytes == rt5514_dsp->buf_limit)
+ rt5514_dsp->buf_rp = rt5514_dsp->buf_base;
+ else
+ rt5514_dsp->buf_rp += period_bytes;
+ } else {
+ truncated_bytes = rt5514_dsp->buf_limit - rt5514_dsp->buf_rp;
+ rt5514_spi_burst_read(rt5514_dsp->buf_rp,
+ runtime->dma_area + rt5514_dsp->dma_offset,
+ truncated_bytes);
+
+ rt5514_spi_burst_read(rt5514_dsp->buf_base,
+ runtime->dma_area + rt5514_dsp->dma_offset +
+ truncated_bytes, period_bytes - truncated_bytes);
+
+ rt5514_dsp->buf_rp = rt5514_dsp->buf_base +
+ period_bytes - truncated_bytes;
+ }
+
+ rt5514_dsp->dma_offset += period_bytes;
+ if (rt5514_dsp->dma_offset >= runtime->dma_bytes)
+ rt5514_dsp->dma_offset = 0;
+
+ rt5514_dsp->dsp_offset += period_bytes;
+
+ snd_pcm_period_elapsed(rt5514_dsp->substream);
+
+ if (rt5514_dsp->dsp_offset < rt5514_dsp->buf_size)
+ schedule_delayed_work(&rt5514_dsp->copy_work, 5);
+done:
+ mutex_unlock(&rt5514_dsp->dma_lock);
+}
+
+/* PCM for streaming audio from the DSP buffer */
+static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream)
+{
+ snd_soc_set_runtime_hwparams(substream, &rt5514_spi_pcm_hardware);
+
+ return 0;
+}
+
+static int rt5514_spi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ int ret;
+
+ mutex_lock(&rt5514_dsp->dma_lock);
+ ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ rt5514_dsp->substream = substream;
+ mutex_unlock(&rt5514_dsp->dma_lock);
+
+ return ret;
+}
+
+static int rt5514_spi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+
+ mutex_lock(&rt5514_dsp->dma_lock);
+ rt5514_dsp->substream = NULL;
+ mutex_unlock(&rt5514_dsp->dma_lock);
+
+ cancel_delayed_work_sync(&rt5514_dsp->copy_work);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int rt5514_spi_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ u8 buf[8];
+
+ rt5514_dsp->dma_offset = 0;
+ rt5514_dsp->dsp_offset = 0;
+
+ /**
+ * The address area x1800XXXX is the register address, and it cannot
+ * support spi burst read perfectly. So we use the spi burst read
+ * individually to make sure the data correctly.
+ */
+ rt5514_spi_burst_read(RT5514_BUFFER_VOICE_BASE, (u8 *)&buf,
+ sizeof(buf));
+ rt5514_dsp->buf_base = buf[0] | buf[1] << 8 | buf[2] << 16 |
+ buf[3] << 24;
+
+ rt5514_spi_burst_read(RT5514_BUFFER_VOICE_LIMIT, (u8 *)&buf,
+ sizeof(buf));
+ rt5514_dsp->buf_limit = buf[0] | buf[1] << 8 | buf[2] << 16 |
+ buf[3] << 24;
+
+ rt5514_spi_burst_read(RT5514_BUFFER_VOICE_RP, (u8 *)&buf,
+ sizeof(buf));
+ rt5514_dsp->buf_rp = buf[0] | buf[1] << 8 | buf[2] << 16 |
+ buf[3] << 24;
+
+ rt5514_spi_burst_read(RT5514_BUFFER_VOICE_SIZE, (u8 *)&buf,
+ sizeof(buf));
+ rt5514_dsp->buf_size = buf[0] | buf[1] << 8 | buf[2] << 16 |
+ buf[3] << 24;
+
+ return 0;
+}
+
+static int rt5514_spi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ if (rt5514_dsp->buf_base && rt5514_dsp->buf_limit &&
+ rt5514_dsp->buf_rp && rt5514_dsp->buf_size)
+ schedule_delayed_work(&rt5514_dsp->copy_work, 0);
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t rt5514_spi_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+
+ return bytes_to_frames(runtime, rt5514_dsp->dma_offset);
+}
+
+static struct snd_pcm_ops rt5514_spi_pcm_ops = {
+ .open = rt5514_spi_pcm_open,
+ .hw_params = rt5514_spi_hw_params,
+ .hw_free = rt5514_spi_hw_free,
+ .trigger = rt5514_spi_trigger,
+ .prepare = rt5514_spi_prepare,
+ .pointer = rt5514_spi_pcm_pointer,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+ .page = snd_pcm_lib_get_vmalloc_page,
+};
+
+static int rt5514_spi_pcm_probe(struct snd_soc_platform *platform)
+{
+ struct rt5514_dsp *rt5514_dsp;
+
+ rt5514_dsp = devm_kzalloc(platform->dev, sizeof(*rt5514_dsp),
+ GFP_KERNEL);
+
+ rt5514_dsp->dev = &rt5514_spi->dev;
+ mutex_init(&rt5514_dsp->dma_lock);
+ INIT_DELAYED_WORK(&rt5514_dsp->copy_work, rt5514_spi_copy_work);
+ snd_soc_platform_set_drvdata(platform, rt5514_dsp);
+
+ return 0;
+}
+
+static struct snd_soc_platform_driver rt5514_spi_platform = {
+ .probe = rt5514_spi_pcm_probe,
+ .ops = &rt5514_spi_pcm_ops,
+};
+
+static const struct snd_soc_component_driver rt5514_spi_dai_component = {
+ .name = "rt5514-spi-dai",
+};
+
+/**
+ * rt5514_spi_burst_read - Read data from SPI by rt5514 address.
+ * @addr: Start address.
+ * @rxbuf: Data Buffer for reading.
+ * @len: Data length, it must be a multiple of 8.
+ *
+ *
+ * Returns true for success.
+ */
+int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len)
+{
+ u8 spi_cmd = RT5514_SPI_CMD_BURST_READ;
+ int status;
+ u8 write_buf[8];
+ unsigned int i, end, offset = 0;
+
+ struct spi_message message;
+ struct spi_transfer x[3];
+
+ while (offset < len) {
+ if (offset + RT5514_SPI_BUF_LEN <= len)
+ end = RT5514_SPI_BUF_LEN;
+ else
+ end = len % RT5514_SPI_BUF_LEN;
+
+ write_buf[0] = spi_cmd;
+ write_buf[1] = ((addr + offset) & 0xff000000) >> 24;
+ write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
+ write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
+ write_buf[4] = ((addr + offset) & 0x000000ff) >> 0;
+
+ spi_message_init(&message);
+ memset(x, 0, sizeof(x));
+
+ x[0].len = 5;
+ x[0].tx_buf = write_buf;
+ spi_message_add_tail(&x[0], &message);
+
+ x[1].len = 4;
+ x[1].tx_buf = write_buf;
+ spi_message_add_tail(&x[1], &message);
+
+ x[2].len = end;
+ x[2].rx_buf = rxbuf + offset;
+ spi_message_add_tail(&x[2], &message);
+
+ status = spi_sync(rt5514_spi, &message);
+
+ if (status)
+ return false;
+
+ offset += RT5514_SPI_BUF_LEN;
+ }
+
+ for (i = 0; i < len; i += 8) {
+ write_buf[0] = rxbuf[i + 0];
+ write_buf[1] = rxbuf[i + 1];
+ write_buf[2] = rxbuf[i + 2];
+ write_buf[3] = rxbuf[i + 3];
+ write_buf[4] = rxbuf[i + 4];
+ write_buf[5] = rxbuf[i + 5];
+ write_buf[6] = rxbuf[i + 6];
+ write_buf[7] = rxbuf[i + 7];
+
+ rxbuf[i + 0] = write_buf[7];
+ rxbuf[i + 1] = write_buf[6];
+ rxbuf[i + 2] = write_buf[5];
+ rxbuf[i + 3] = write_buf[4];
+ rxbuf[i + 4] = write_buf[3];
+ rxbuf[i + 5] = write_buf[2];
+ rxbuf[i + 6] = write_buf[1];
+ rxbuf[i + 7] = write_buf[0];
+ }
+
+ return true;
+}
+
+/**
+ * rt5514_spi_burst_write - Write data to SPI by rt5514 address.
+ * @addr: Start address.
+ * @txbuf: Data Buffer for writng.
+ * @len: Data length, it must be a multiple of 8.
+ *
+ *
+ * Returns true for success.
+ */
+int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len)
+{
+ u8 spi_cmd = RT5514_SPI_CMD_BURST_WRITE;
+ u8 *write_buf;
+ unsigned int i, end, offset = 0;
+
+ write_buf = kmalloc(RT5514_SPI_BUF_LEN + 6, GFP_KERNEL);
+
+ if (write_buf == NULL)
+ return -ENOMEM;
+
+ while (offset < len) {
+ if (offset + RT5514_SPI_BUF_LEN <= len)
+ end = RT5514_SPI_BUF_LEN;
+ else
+ end = len % RT5514_SPI_BUF_LEN;
+
+ write_buf[0] = spi_cmd;
+ write_buf[1] = ((addr + offset) & 0xff000000) >> 24;
+ write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
+ write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
+ write_buf[4] = ((addr + offset) & 0x000000ff) >> 0;
+
+ for (i = 0; i < end; i += 8) {
+ write_buf[i + 12] = txbuf[offset + i + 0];
+ write_buf[i + 11] = txbuf[offset + i + 1];
+ write_buf[i + 10] = txbuf[offset + i + 2];
+ write_buf[i + 9] = txbuf[offset + i + 3];
+ write_buf[i + 8] = txbuf[offset + i + 4];
+ write_buf[i + 7] = txbuf[offset + i + 5];
+ write_buf[i + 6] = txbuf[offset + i + 6];
+ write_buf[i + 5] = txbuf[offset + i + 7];
+ }
+
+ write_buf[end + 5] = spi_cmd;
+
+ spi_write(rt5514_spi, write_buf, end + 6);
+
+ offset += RT5514_SPI_BUF_LEN;
+ }
+
+ kfree(write_buf);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rt5514_spi_burst_write);
+
+static int rt5514_spi_probe(struct spi_device *spi)
+{
+ int ret;
+
+ rt5514_spi = spi;
+
+ ret = devm_snd_soc_register_platform(&spi->dev, &rt5514_spi_platform);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Failed to register platform.\n");
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&spi->dev,
+ &rt5514_spi_dai_component,
+ &rt5514_spi_dai, 1);
+ if (ret < 0) {
+ dev_err(&spi->dev, "Failed to register component.\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id rt5514_of_match[] = {
+ { .compatible = "realtek,rt5514", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rt5514_of_match);
+
+static struct spi_driver rt5514_spi_driver = {
+ .driver = {
+ .name = "rt5514",
+ .of_match_table = of_match_ptr(rt5514_of_match),
+ },
+ .probe = rt5514_spi_probe,
+};
+module_spi_driver(rt5514_spi_driver);
+
+MODULE_DESCRIPTION("RT5514 SPI driver");
+MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/rt5514-spi.h b/sound/soc/codecs/rt5514-spi.h
new file mode 100644
index 000000000000..f69b1cdf2f9b
--- /dev/null
+++ b/sound/soc/codecs/rt5514-spi.h
@@ -0,0 +1,38 @@
+/*
+ * rt5514-spi.h -- RT5514 driver
+ *
+ * Copyright 2015 Realtek Semiconductor Corp.
+ * Author: Oder Chiou <oder_chiou@realtek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __RT5514_SPI_H__
+#define __RT5514_SPI_H__
+
+/**
+ * RT5514_SPI_BUF_LEN is the buffer size of SPI master controller.
+*/
+#define RT5514_SPI_BUF_LEN 240
+
+#define RT5514_BUFFER_VOICE_BASE 0x18001034
+#define RT5514_BUFFER_VOICE_LIMIT 0x18001038
+#define RT5514_BUFFER_VOICE_RP 0x1800103c
+#define RT5514_BUFFER_VOICE_SIZE 0x18001040
+
+/* SPI Command */
+enum {
+ RT5514_SPI_CMD_16_READ = 0,
+ RT5514_SPI_CMD_16_WRITE,
+ RT5514_SPI_CMD_32_READ,
+ RT5514_SPI_CMD_32_WRITE,
+ RT5514_SPI_CMD_BURST_READ,
+ RT5514_SPI_CMD_BURST_WRITE,
+};
+
+int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len);
+int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len);
+
+#endif /* __RT5514_SPI_H__ */
diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c
index 879bf60f4965..7162f05101d9 100644
--- a/sound/soc/codecs/rt5514.c
+++ b/sound/soc/codecs/rt5514.c
@@ -30,6 +30,9 @@
#include "rl6231.h"
#include "rt5514.h"
+#if defined(CONFIG_SND_SOC_RT5514_SPI)
+#include "rt5514-spi.h"
+#endif
static const struct reg_sequence rt5514_i2c_patch[] = {
{0x1800101c, 0x00000000},
@@ -110,6 +113,35 @@ static const struct reg_default rt5514_reg[] = {
{RT5514_VENDOR_ID2, 0x10ec5514},
};
+static void rt5514_enable_dsp_prepare(struct rt5514_priv *rt5514)
+{
+ /* Reset */
+ regmap_write(rt5514->i2c_regmap, 0x18002000, 0x000010ec);
+ /* LDO_I_limit */
+ regmap_write(rt5514->i2c_regmap, 0x18002200, 0x00028604);
+ /* I2C bypass enable */
+ regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000001);
+ /* mini-core reset */
+ regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x0005514b);
+ regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x00055149);
+ /* I2C bypass disable */
+ regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000000);
+ /* PIN config */
+ regmap_write(rt5514->i2c_regmap, 0x18002070, 0x00000040);
+ /* PLL3(QN)=RCOSC*(10+2) */
+ regmap_write(rt5514->i2c_regmap, 0x18002240, 0x0000000a);
+ /* PLL3 source=RCOSC, fsi=rt_clk */
+ regmap_write(rt5514->i2c_regmap, 0x18002100, 0x0000000b);
+ /* Power on RCOSC, pll3 */
+ regmap_write(rt5514->i2c_regmap, 0x18002004, 0x00808b81);
+ /* DSP clk source = pll3, ENABLE DSP clk */
+ regmap_write(rt5514->i2c_regmap, 0x18002f08, 0x00000005);
+ /* Enable DSP clk auto switch */
+ regmap_write(rt5514->i2c_regmap, 0x18001114, 0x00000001);
+ /* Reduce DSP power */
+ regmap_write(rt5514->i2c_regmap, 0x18001118, 0x00000001);
+}
+
static bool rt5514_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -248,6 +280,74 @@ static const DECLARE_TLV_DB_RANGE(bst_tlv,
static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0);
+static int rt5514_dsp_voice_wake_up_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = rt5514->dsp_enabled;
+
+ return 0;
+}
+
+static int rt5514_dsp_voice_wake_up_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_codec *codec = rt5514->codec;
+ const struct firmware *fw = NULL;
+
+ if (ucontrol->value.integer.value[0] == rt5514->dsp_enabled)
+ return 0;
+
+ if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) {
+ rt5514->dsp_enabled = ucontrol->value.integer.value[0];
+
+ if (rt5514->dsp_enabled) {
+ rt5514_enable_dsp_prepare(rt5514);
+
+ request_firmware(&fw, RT5514_FIRMWARE1, codec->dev);
+ if (fw) {
+#if defined(CONFIG_SND_SOC_RT5514_SPI)
+ rt5514_spi_burst_write(0x4ff60000, fw->data,
+ ((fw->size/8)+1)*8);
+#else
+ dev_err(codec->dev, "There is no SPI driver for"
+ " loading the firmware\n");
+#endif
+ release_firmware(fw);
+ fw = NULL;
+ }
+
+ request_firmware(&fw, RT5514_FIRMWARE2, codec->dev);
+ if (fw) {
+#if defined(CONFIG_SND_SOC_RT5514_SPI)
+ rt5514_spi_burst_write(0x4ffc0000, fw->data,
+ ((fw->size/8)+1)*8);
+#else
+ dev_err(codec->dev, "There is no SPI driver for"
+ " loading the firmware\n");
+#endif
+ release_firmware(fw);
+ fw = NULL;
+ }
+
+ /* DSP run */
+ regmap_write(rt5514->i2c_regmap, 0x18002f00,
+ 0x00055148);
+ } else {
+ regmap_multi_reg_write(rt5514->i2c_regmap,
+ rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch));
+ regcache_mark_dirty(rt5514->regmap);
+ regcache_sync(rt5514->regmap);
+ }
+ }
+
+ return 0;
+}
+
static const struct snd_kcontrol_new rt5514_snd_controls[] = {
SOC_DOUBLE_TLV("MIC Boost Volume", RT5514_ANA_CTRL_MICBST,
RT5514_SEL_BSTL_SFT, RT5514_SEL_BSTR_SFT, 8, 0, bst_tlv),
@@ -257,6 +357,8 @@ static const struct snd_kcontrol_new rt5514_snd_controls[] = {
SOC_DOUBLE_R_TLV("ADC2 Capture Volume", RT5514_DOWNFILTER1_CTRL1,
RT5514_DOWNFILTER1_CTRL2, RT5514_AD_GAIN_SFT, 127, 0,
adc_vol_tlv),
+ SOC_SINGLE_EXT("DSP Voice Wake Up", SND_SOC_NOPM, 0, 1, 0,
+ rt5514_dsp_voice_wake_up_get, rt5514_dsp_voice_wake_up_put),
};
/* ADC Mixer*/
@@ -365,6 +467,35 @@ static int rt5514_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source,
return 0;
}
+static int rt5514_pre_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
+ struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /**
+ * If the DSP is enabled in start of recording, the DSP
+ * should be disabled, and sync back to normal recording
+ * settings to make sure recording properly.
+ */
+ if (rt5514->dsp_enabled) {
+ rt5514->dsp_enabled = 0;
+ regmap_multi_reg_write(rt5514->i2c_regmap,
+ rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch));
+ regcache_mark_dirty(rt5514->regmap);
+ regcache_sync(rt5514->regmap);
+ }
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = {
/* Input Lines */
SND_SOC_DAPM_INPUT("DMIC1L"),
@@ -472,6 +603,8 @@ static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = {
/* Audio Interface */
SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_PRE("DAPM Pre", rt5514_pre_event),
};
static const struct snd_soc_dapm_route rt5514_dapm_routes[] = {
@@ -799,10 +932,41 @@ static int rt5514_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
return 0;
}
+static int rt5514_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (IS_ERR(rt5514->mclk))
+ break;
+
+ if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON) {
+ clk_disable_unprepare(rt5514->mclk);
+ } else {
+ ret = clk_prepare_enable(rt5514->mclk);
+ if (ret)
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
static int rt5514_probe(struct snd_soc_codec *codec)
{
struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec);
+ rt5514->mclk = devm_clk_get(codec->dev, "mclk");
+ if (PTR_ERR(rt5514->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
rt5514->codec = codec;
return 0;
@@ -858,6 +1022,7 @@ struct snd_soc_dai_driver rt5514_dai[] = {
static struct snd_soc_codec_driver soc_codec_dev_rt5514 = {
.probe = rt5514_probe,
.idle_bias_off = true,
+ .set_bias_level = rt5514_set_bias_level,
.controls = rt5514_snd_controls,
.num_controls = ARRAY_SIZE(rt5514_snd_controls),
.dapm_widgets = rt5514_dapm_widgets,
@@ -871,7 +1036,6 @@ static const struct regmap_config rt5514_i2c_regmap = {
.reg_bits = 32,
.val_bits = 32,
- .max_register = RT5514_DSP_MAPPING | RT5514_VENDOR_ID2,
.readable_reg = rt5514_i2c_readable_register,
.cache_type = REGCACHE_NONE,
@@ -944,7 +1108,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c,
return -ENODEV;
}
- ret = regmap_register_patch(rt5514->i2c_regmap, rt5514_i2c_patch,
+ ret = regmap_multi_reg_write(rt5514->i2c_regmap, rt5514_i2c_patch,
ARRAY_SIZE(rt5514_i2c_patch));
if (ret != 0)
dev_warn(&i2c->dev, "Failed to apply i2c_regmap patch: %d\n",
diff --git a/sound/soc/codecs/rt5514.h b/sound/soc/codecs/rt5514.h
index 6ad8a612f659..68883c68e999 100644
--- a/sound/soc/codecs/rt5514.h
+++ b/sound/soc/codecs/rt5514.h
@@ -12,6 +12,8 @@
#ifndef __RT5514_H__
#define __RT5514_H__
+#include <linux/clk.h>
+
#define RT5514_DEVICE_ID 0x10ec5514
#define RT5514_RESET 0x2000
@@ -225,6 +227,9 @@
#define RT5514_PLL_INP_MAX 40000000
#define RT5514_PLL_INP_MIN 256000
+#define RT5514_FIRMWARE1 "rt5514_dsp_fw1.bin"
+#define RT5514_FIRMWARE2 "rt5514_dsp_fw2.bin"
+
/* System Clock Source */
enum {
RT5514_SCLK_S_MCLK,
@@ -240,6 +245,7 @@ enum {
struct rt5514_priv {
struct snd_soc_codec *codec;
struct regmap *i2c_regmap, *regmap;
+ struct clk *mclk;
int sysclk;
int sysclk_src;
int lrck;
@@ -247,6 +253,7 @@ struct rt5514_priv {
int pll_src;
int pll_in;
int pll_out;
+ int dsp_enabled;
};
#endif /* __RT5514_H__ */
diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c
index 3c6594da6c9c..490bfe661346 100644
--- a/sound/soc/codecs/rt5645.c
+++ b/sound/soc/codecs/rt5645.c
@@ -63,6 +63,7 @@ static const struct reg_sequence init_list[] = {
{RT5645_PR_BASE + 0x20, 0x611f},
{RT5645_PR_BASE + 0x21, 0x4040},
{RT5645_PR_BASE + 0x23, 0x0004},
+ {RT5645_ASRC_4, 0x0120},
};
static const struct reg_sequence rt5650_init_list[] = {
@@ -157,7 +158,7 @@ static const struct reg_default rt5645_reg[] = {
{ 0x83, 0x0000 },
{ 0x84, 0x0000 },
{ 0x85, 0x0000 },
- { 0x8a, 0x0000 },
+ { 0x8a, 0x0120 },
{ 0x8e, 0x0004 },
{ 0x8f, 0x1100 },
{ 0x90, 0x0646 },
@@ -253,7 +254,7 @@ static const struct reg_default rt5650_reg[] = {
{ 0x2b, 0x5454 },
{ 0x2c, 0xaaa0 },
{ 0x2d, 0x0000 },
- { 0x2f, 0x1002 },
+ { 0x2f, 0x5002 },
{ 0x31, 0x5000 },
{ 0x32, 0x0000 },
{ 0x33, 0x0000 },
@@ -314,7 +315,7 @@ static const struct reg_default rt5650_reg[] = {
{ 0x83, 0x0000 },
{ 0x84, 0x0000 },
{ 0x85, 0x0000 },
- { 0x8a, 0x0000 },
+ { 0x8a, 0x0120 },
{ 0x8e, 0x0004 },
{ 0x8f, 0x1100 },
{ 0x90, 0x0646 },
@@ -440,6 +441,7 @@ static bool rt5645_volatile_register(struct device *dev, unsigned int reg)
switch (reg) {
case RT5645_RESET:
+ case RT5645_PRIV_INDEX:
case RT5645_PRIV_DATA:
case RT5645_IN1_CTRL1:
case RT5645_IN1_CTRL2:
@@ -740,6 +742,14 @@ static int rt5645_spk_put_volsw(struct snd_kcontrol *kcontrol,
return ret;
}
+static const char * const rt5645_dac1_vol_ctrl_mode_text[] = {
+ "immediately", "zero crossing", "soft ramp"
+};
+
+static SOC_ENUM_SINGLE_DECL(
+ rt5645_dac1_vol_ctrl_mode, RT5645_PR_BASE,
+ RT5645_DA1_ZDET_SFT, rt5645_dac1_vol_ctrl_mode_text);
+
static const struct snd_kcontrol_new rt5645_snd_controls[] = {
/* Speaker Output Volume */
SOC_DOUBLE("Speaker Channel Switch", RT5645_SPK_VOL,
@@ -806,6 +816,9 @@ static const struct snd_kcontrol_new rt5645_snd_controls[] = {
SOC_SINGLE("I2S2 Func Switch", RT5645_GPIO_CTRL1, RT5645_I2S2_SEL_SFT,
1, 1),
RT5645_HWEQ("Speaker HWEQ"),
+
+ /* Digital Soft Volume Control */
+ SOC_ENUM("DAC1 Digital Volume Control Func", rt5645_dac1_vol_ctrl_mode),
};
/**
@@ -3531,6 +3544,7 @@ MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id);
static const struct acpi_device_id rt5645_acpi_match[] = {
{ "10EC5645", 0 },
{ "10EC5650", 0 },
+ { "10EC5640", 0 },
{},
};
MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match);
@@ -3561,6 +3575,12 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "Setzer"),
},
},
+ {
+ .ident = "Microsoft Surface 3",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ },
+ },
{ }
};
diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h
index 205e0715c99a..cfc5f97549eb 100644
--- a/sound/soc/codecs/rt5645.h
+++ b/sound/soc/codecs/rt5645.h
@@ -2018,6 +2018,9 @@
/* Codec Private Register definition */
+/* DAC ADC Digital Volume (0x00) */
+#define RT5645_DA1_ZDET_SFT 6
+
/* 3D Speaker Control (0x63) */
#define RT5645_3D_SPK_MASK (0x1 << 15)
#define RT5645_3D_SPK_SFT 15
diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c
index 49a9e7049e2b..8ef467f64f03 100644
--- a/sound/soc/codecs/rt5670.c
+++ b/sound/soc/codecs/rt5670.c
@@ -55,6 +55,7 @@ static const struct reg_sequence init_list[] = {
{ RT5670_PR_BASE + 0x14, 0x9a8a },
{ RT5670_PR_BASE + 0x38, 0x3ba1 },
{ RT5670_PR_BASE + 0x3d, 0x3640 },
+ { 0x8a, 0x0123 },
};
static const struct reg_default rt5670_reg[] = {
@@ -131,7 +132,7 @@ static const struct reg_default rt5670_reg[] = {
{ 0x87, 0x0000 },
{ 0x88, 0x0000 },
{ 0x89, 0x0000 },
- { 0x8a, 0x0000 },
+ { 0x8a, 0x0123 },
{ 0x8b, 0x0000 },
{ 0x8c, 0x0003 },
{ 0x8d, 0x0000 },
@@ -619,7 +620,7 @@ static const struct snd_kcontrol_new rt5670_snd_controls[] = {
RT5670_L_MUTE_SFT, RT5670_R_MUTE_SFT, 1, 1),
SOC_DOUBLE_TLV("HP Playback Volume", RT5670_HP_VOL,
RT5670_L_VOL_SFT, RT5670_R_VOL_SFT,
- 39, 0, out_vol_tlv),
+ 39, 1, out_vol_tlv),
/* OUTPUT Control */
SOC_DOUBLE("OUT Channel Switch", RT5670_LOUT1,
RT5670_VOL_L_SFT, RT5670_VOL_R_SFT, 1, 1),
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
index 08b40460663c..527b759c1562 100644
--- a/sound/soc/codecs/sgtl5000.c
+++ b/sound/soc/codecs/sgtl5000.c
@@ -38,7 +38,6 @@
/* default value of sgtl5000 registers */
static const struct reg_default sgtl5000_reg_defaults[] = {
{ SGTL5000_CHIP_DIG_POWER, 0x0000 },
- { SGTL5000_CHIP_CLK_CTRL, 0x0008 },
{ SGTL5000_CHIP_I2S_CTRL, 0x0010 },
{ SGTL5000_CHIP_SSS_CTRL, 0x0010 },
{ SGTL5000_CHIP_ADCDAC_CTRL, 0x020c },
@@ -47,12 +46,10 @@ static const struct reg_default sgtl5000_reg_defaults[] = {
{ SGTL5000_CHIP_ANA_ADC_CTRL, 0x0000 },
{ SGTL5000_CHIP_ANA_HP_CTRL, 0x1818 },
{ SGTL5000_CHIP_ANA_CTRL, 0x0111 },
- { SGTL5000_CHIP_LINREG_CTRL, 0x0000 },
{ SGTL5000_CHIP_REF_CTRL, 0x0000 },
{ SGTL5000_CHIP_MIC_CTRL, 0x0000 },
{ SGTL5000_CHIP_LINE_OUT_CTRL, 0x0000 },
{ SGTL5000_CHIP_LINE_OUT_VOL, 0x0404 },
- { SGTL5000_CHIP_ANA_POWER, 0x7060 },
{ SGTL5000_CHIP_PLL_CTRL, 0x5000 },
{ SGTL5000_CHIP_CLK_TOP_CTRL, 0x0000 },
{ SGTL5000_CHIP_ANA_STATUS, 0x0000 },
@@ -92,35 +89,8 @@ static const char *supply_names[SGTL5000_SUPPLY_NUM] = {
"VDDD"
};
-#define LDO_CONSUMER_NAME "VDDD_LDO"
#define LDO_VOLTAGE 1200000
-
-static struct regulator_consumer_supply ldo_consumer[] = {
- REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL),
-};
-
-static struct regulator_init_data ldo_init_data = {
- .constraints = {
- .min_uV = 1200000,
- .max_uV = 1200000,
- .valid_modes_mask = REGULATOR_MODE_NORMAL,
- .valid_ops_mask = REGULATOR_CHANGE_STATUS,
- },
- .num_consumer_supplies = 1,
- .consumer_supplies = &ldo_consumer[0],
-};
-
-/*
- * sgtl5000 internal ldo regulator,
- * enabled when VDDD not provided
- */
-struct ldo_regulator {
- struct regulator_desc desc;
- struct regulator_dev *dev;
- int voltage;
- void *codec_data;
- bool enabled;
-};
+#define LINREG_VDDD ((1600 - LDO_VOLTAGE / 1000) / 50)
enum sgtl5000_micbias_resistor {
SGTL5000_MICBIAS_OFF = 0,
@@ -135,7 +105,7 @@ struct sgtl5000_priv {
int master; /* i2s master or not */
int fmt; /* i2s data format */
struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM];
- struct ldo_regulator *ldo;
+ int num_supplies;
struct regmap *regmap;
struct clk *mclk;
int revision;
@@ -415,6 +385,9 @@ static const DECLARE_TLV_DB_RANGE(mic_gain_tlv,
/* tlv for hp volume, -51.5db to 12.0db, step .5db */
static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0);
+/* tlv for lineout volume, 31 steps of .5db each */
+static const DECLARE_TLV_DB_SCALE(lineout_volume, -1550, 50, 0);
+
static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
/* SOC_DOUBLE_S8_TLV with invert */
{
@@ -443,6 +416,13 @@ static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL,
0, 3, 0, mic_gain_tlv),
+
+ SOC_DOUBLE_TLV("Lineout Playback Volume",
+ SGTL5000_CHIP_LINE_OUT_VOL,
+ SGTL5000_LINE_OUT_VOL_LEFT_SHIFT,
+ SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT,
+ 0x1f, 1,
+ lineout_volume),
};
/* mute the codec used by alsa core */
@@ -778,155 +758,6 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
return 0;
}
-#ifdef CONFIG_REGULATOR
-static int ldo_regulator_is_enabled(struct regulator_dev *dev)
-{
- struct ldo_regulator *ldo = rdev_get_drvdata(dev);
-
- return ldo->enabled;
-}
-
-static int ldo_regulator_enable(struct regulator_dev *dev)
-{
- struct ldo_regulator *ldo = rdev_get_drvdata(dev);
- struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
- int reg;
-
- if (ldo_regulator_is_enabled(dev))
- return 0;
-
- /* set regulator value firstly */
- reg = (1600 - ldo->voltage / 1000) / 50;
- reg = clamp(reg, 0x0, 0xf);
-
- /* amend the voltage value, unit: uV */
- ldo->voltage = (1600 - reg * 50) * 1000;
-
- /* set voltage to register */
- snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
- SGTL5000_LINREG_VDDD_MASK, reg);
-
- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
- SGTL5000_LINEREG_D_POWERUP,
- SGTL5000_LINEREG_D_POWERUP);
-
- /* when internal ldo is enabled, simple digital power can be disabled */
- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
- SGTL5000_LINREG_SIMPLE_POWERUP,
- 0);
-
- ldo->enabled = 1;
- return 0;
-}
-
-static int ldo_regulator_disable(struct regulator_dev *dev)
-{
- struct ldo_regulator *ldo = rdev_get_drvdata(dev);
- struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
-
- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
- SGTL5000_LINEREG_D_POWERUP,
- 0);
-
- /* clear voltage info */
- snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
- SGTL5000_LINREG_VDDD_MASK, 0);
-
- ldo->enabled = 0;
-
- return 0;
-}
-
-static int ldo_regulator_get_voltage(struct regulator_dev *dev)
-{
- struct ldo_regulator *ldo = rdev_get_drvdata(dev);
-
- return ldo->voltage;
-}
-
-static struct regulator_ops ldo_regulator_ops = {
- .is_enabled = ldo_regulator_is_enabled,
- .enable = ldo_regulator_enable,
- .disable = ldo_regulator_disable,
- .get_voltage = ldo_regulator_get_voltage,
-};
-
-static int ldo_regulator_register(struct snd_soc_codec *codec,
- struct regulator_init_data *init_data,
- int voltage)
-{
- struct ldo_regulator *ldo;
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- struct regulator_config config = { };
-
- ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL);
-
- if (!ldo)
- return -ENOMEM;
-
- ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL);
- if (!ldo->desc.name) {
- kfree(ldo);
- dev_err(codec->dev, "failed to allocate decs name memory\n");
- return -ENOMEM;
- }
-
- ldo->desc.type = REGULATOR_VOLTAGE;
- ldo->desc.owner = THIS_MODULE;
- ldo->desc.ops = &ldo_regulator_ops;
- ldo->desc.n_voltages = 1;
-
- ldo->codec_data = codec;
- ldo->voltage = voltage;
-
- config.dev = codec->dev;
- config.driver_data = ldo;
- config.init_data = init_data;
-
- ldo->dev = regulator_register(&ldo->desc, &config);
- if (IS_ERR(ldo->dev)) {
- int ret = PTR_ERR(ldo->dev);
-
- dev_err(codec->dev, "failed to register regulator\n");
- kfree(ldo->desc.name);
- kfree(ldo);
-
- return ret;
- }
- sgtl5000->ldo = ldo;
-
- return 0;
-}
-
-static int ldo_regulator_remove(struct snd_soc_codec *codec)
-{
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- struct ldo_regulator *ldo = sgtl5000->ldo;
-
- if (!ldo)
- return 0;
-
- regulator_unregister(ldo->dev);
- kfree(ldo->desc.name);
- kfree(ldo);
-
- return 0;
-}
-#else
-static int ldo_regulator_register(struct snd_soc_codec *codec,
- struct regulator_init_data *init_data,
- int voltage)
-{
- dev_err(codec->dev, "this setup needs regulator support in the kernel\n");
- return -EINVAL;
-}
-
-static int ldo_regulator_remove(struct snd_soc_codec *codec)
-{
- return 0;
-}
-#endif
-
/*
* set dac bias
* common state changes:
@@ -940,42 +771,17 @@ static int ldo_regulator_remove(struct snd_soc_codec *codec)
static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
- int ret;
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
-
switch (level) {
case SND_SOC_BIAS_ON:
case SND_SOC_BIAS_PREPARE:
- break;
case SND_SOC_BIAS_STANDBY:
- if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) {
- ret = regulator_bulk_enable(
- ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- if (ret)
- return ret;
- udelay(10);
-
- regcache_cache_only(sgtl5000->regmap, false);
-
- ret = regcache_sync(sgtl5000->regmap);
- if (ret != 0) {
- dev_err(codec->dev,
- "Failed to restore cache: %d\n", ret);
-
- regcache_cache_only(sgtl5000->regmap, true);
- regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
-
- return ret;
- }
- }
-
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_REFTOP_POWERUP,
+ SGTL5000_REFTOP_POWERUP);
break;
case SND_SOC_BIAS_OFF:
- regcache_cache_only(sgtl5000->regmap, true);
- regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_REFTOP_POWERUP, 0);
break;
}
@@ -1113,7 +919,6 @@ static const u8 vol_quot_table[] = {
* and should be set according to:
* 1. vddd provided by external or not
* 2. vdda and vddio voltage value. > 3.1v or not
- * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd.
*/
static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
{
@@ -1131,7 +936,9 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer);
vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer);
- vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer);
+ vddd = (sgtl5000->num_supplies > VDDD)
+ ? regulator_get_voltage(sgtl5000->supplies[VDDD].consumer)
+ : LDO_VOLTAGE;
vdda = vdda / 1000;
vddio = vddio / 1000;
@@ -1178,25 +985,6 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
- /* set voltage to register */
- snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
- SGTL5000_LINREG_VDDD_MASK, 0x8);
-
- /*
- * if vddd linear reg has been enabled,
- * simple digital supply should be clear to get
- * proper VDDD voltage.
- */
- if (ana_pwr & SGTL5000_LINEREG_D_POWERUP)
- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
- SGTL5000_LINREG_SIMPLE_POWERUP,
- 0);
- else
- snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
- SGTL5000_LINREG_SIMPLE_POWERUP |
- SGTL5000_STARTUP_POWERUP,
- 0);
-
/*
* set ADC/DAC VAG to vdda / 2,
* should stay in range (0.8v, 1.575v)
@@ -1256,78 +1044,43 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
return 0;
}
-static int sgtl5000_replace_vddd_with_ldo(struct snd_soc_codec *codec)
-{
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- int ret;
-
- /* set internal ldo to 1.2v */
- ret = ldo_regulator_register(codec, &ldo_init_data, LDO_VOLTAGE);
- if (ret) {
- dev_err(codec->dev,
- "Failed to register vddd internal supplies: %d\n", ret);
- return ret;
- }
-
- sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
-
- dev_info(codec->dev, "Using internal LDO instead of VDDD\n");
- return 0;
-}
-
-static int sgtl5000_enable_regulators(struct snd_soc_codec *codec)
+static int sgtl5000_enable_regulators(struct i2c_client *client)
{
int ret;
int i;
int external_vddd = 0;
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct regulator *vddd;
+ struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++)
sgtl5000->supplies[i].supply = supply_names[i];
- /* External VDDD only works before revision 0x11 */
- if (sgtl5000->revision < 0x11) {
- vddd = regulator_get_optional(codec->dev, "VDDD");
- if (IS_ERR(vddd)) {
- /* See if it's just not registered yet */
- if (PTR_ERR(vddd) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- } else {
- external_vddd = 1;
- regulator_put(vddd);
- }
- }
-
- if (!external_vddd) {
- ret = sgtl5000_replace_vddd_with_ldo(codec);
- if (ret)
- return ret;
+ vddd = regulator_get_optional(&client->dev, "VDDD");
+ if (IS_ERR(vddd)) {
+ /* See if it's just not registered yet */
+ if (PTR_ERR(vddd) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ } else {
+ external_vddd = 1;
+ regulator_put(vddd);
}
- ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->num_supplies = ARRAY_SIZE(sgtl5000->supplies)
+ - 1 + external_vddd;
+ ret = regulator_bulk_get(&client->dev, sgtl5000->num_supplies,
sgtl5000->supplies);
if (ret)
- goto err_ldo_remove;
-
- ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- if (ret)
- goto err_regulator_free;
-
- /* wait for all power rails bring up */
- udelay(10);
+ return ret;
- return 0;
+ ret = regulator_bulk_enable(sgtl5000->num_supplies,
+ sgtl5000->supplies);
+ if (!ret)
+ usleep_range(10, 20);
+ else
+ regulator_bulk_free(sgtl5000->num_supplies,
+ sgtl5000->supplies);
-err_regulator_free:
- regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
-err_ldo_remove:
- if (!external_vddd)
- ldo_regulator_remove(codec);
return ret;
-
}
static int sgtl5000_probe(struct snd_soc_codec *codec)
@@ -1335,10 +1088,6 @@ static int sgtl5000_probe(struct snd_soc_codec *codec)
int ret;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- ret = sgtl5000_enable_regulators(codec);
- if (ret)
- return ret;
-
/* power up sgtl5000 */
ret = sgtl5000_set_power_regs(codec);
if (ret)
@@ -1389,25 +1138,11 @@ static int sgtl5000_probe(struct snd_soc_codec *codec)
return 0;
err:
- regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- ldo_regulator_remove(codec);
-
return ret;
}
static int sgtl5000_remove(struct snd_soc_codec *codec)
{
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
-
- regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
- sgtl5000->supplies);
- ldo_regulator_remove(codec);
-
return 0;
}
@@ -1448,8 +1183,9 @@ static const struct regmap_config sgtl5000_regmap = {
* and avoid problems like, not being able to probe after an audio playback
* followed by a system reset or a 'reboot' command in Linux
*/
-static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000)
+static void sgtl5000_fill_defaults(struct i2c_client *client)
{
+ struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
int i, ret, val, index;
for (i = 0; i < ARRAY_SIZE(sgtl5000_reg_defaults); i++) {
@@ -1457,10 +1193,10 @@ static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000)
index = sgtl5000_reg_defaults[i].reg;
ret = regmap_write(sgtl5000->regmap, index, val);
if (ret)
- return ret;
+ dev_err(&client->dev,
+ "%s: error %d setting reg 0x%02x to 0x%04x\n",
+ __func__, ret, index, val);
}
-
- return 0;
}
static int sgtl5000_i2c_probe(struct i2c_client *client,
@@ -1470,16 +1206,23 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
int ret, reg, rev;
struct device_node *np = client->dev.of_node;
u32 value;
+ u16 ana_pwr;
sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL);
if (!sgtl5000)
return -ENOMEM;
+ i2c_set_clientdata(client, sgtl5000);
+
+ ret = sgtl5000_enable_regulators(client);
+ if (ret)
+ return ret;
+
sgtl5000->regmap = devm_regmap_init_i2c(client, &sgtl5000_regmap);
if (IS_ERR(sgtl5000->regmap)) {
ret = PTR_ERR(sgtl5000->regmap);
dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret);
- return ret;
+ goto disable_regs;
}
sgtl5000->mclk = devm_clk_get(&client->dev, NULL);
@@ -1488,21 +1231,25 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
dev_err(&client->dev, "Failed to get mclock: %d\n", ret);
/* Defer the probe to see if the clk will be provided later */
if (ret == -ENOENT)
- return -EPROBE_DEFER;
- return ret;
+ ret = -EPROBE_DEFER;
+ goto disable_regs;
}
ret = clk_prepare_enable(sgtl5000->mclk);
- if (ret)
- return ret;
+ if (ret) {
+ dev_err(&client->dev, "Error enabling clock %d\n", ret);
+ goto disable_regs;
+ }
/* Need 8 clocks before I2C accesses */
udelay(1);
/* read chip information */
ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ID, &reg);
- if (ret)
+ if (ret) {
+ dev_err(&client->dev, "Error reading chip id %d\n", ret);
goto disable_clk;
+ }
if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
SGTL5000_PARTID_PART_ID) {
@@ -1516,6 +1263,44 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev);
sgtl5000->revision = rev;
+ /* reconfigure the clocks in case we're using the PLL */
+ ret = regmap_write(sgtl5000->regmap,
+ SGTL5000_CHIP_CLK_CTRL,
+ SGTL5000_CHIP_CLK_CTRL_DEFAULT);
+ if (ret)
+ dev_err(&client->dev,
+ "Error %d initializing CHIP_CLK_CTRL\n", ret);
+
+ /* Follow section 2.2.1.1 of AN3663 */
+ ana_pwr = SGTL5000_ANA_POWER_DEFAULT;
+ if (sgtl5000->num_supplies <= VDDD) {
+ /* internal VDDD at 1.2V */
+ ret = regmap_update_bits(sgtl5000->regmap,
+ SGTL5000_CHIP_LINREG_CTRL,
+ SGTL5000_LINREG_VDDD_MASK,
+ LINREG_VDDD);
+ if (ret)
+ dev_err(&client->dev,
+ "Error %d setting LINREG_VDDD\n", ret);
+
+ ana_pwr |= SGTL5000_LINEREG_D_POWERUP;
+ dev_info(&client->dev,
+ "Using internal LDO instead of VDDD: check ER1\n");
+ } else {
+ /* using external LDO for VDDD
+ * Clear startup powerup and simple powerup
+ * bits to save power
+ */
+ ana_pwr &= ~(SGTL5000_STARTUP_POWERUP
+ | SGTL5000_LINREG_SIMPLE_POWERUP);
+ dev_dbg(&client->dev, "Using external VDDD\n");
+ }
+ ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ if (ret)
+ dev_err(&client->dev,
+ "Error %d setting CHIP_ANA_POWER to %04x\n",
+ ret, ana_pwr);
+
if (np) {
if (!of_property_read_u32(np,
"micbias-resistor-k-ohms", &value)) {
@@ -1557,12 +1342,8 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
}
}
- i2c_set_clientdata(client, sgtl5000);
-
/* Ensure sgtl5000 will start with sane register values */
- ret = sgtl5000_fill_defaults(sgtl5000);
- if (ret)
- goto disable_clk;
+ sgtl5000_fill_defaults(client);
ret = snd_soc_register_codec(&client->dev,
&sgtl5000_driver, &sgtl5000_dai, 1);
@@ -1573,6 +1354,11 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
disable_clk:
clk_disable_unprepare(sgtl5000->mclk);
+
+disable_regs:
+ regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies);
+ regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies);
+
return ret;
}
@@ -1582,6 +1368,9 @@ static int sgtl5000_i2c_remove(struct i2c_client *client)
snd_soc_unregister_codec(&client->dev);
clk_disable_unprepare(sgtl5000->mclk);
+ regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies);
+ regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies);
+
return 0;
}
diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h
index 1c317de26176..22f3442af982 100644
--- a/sound/soc/codecs/sgtl5000.h
+++ b/sound/soc/codecs/sgtl5000.h
@@ -92,6 +92,7 @@
/*
* SGTL5000_CHIP_CLK_CTRL
*/
+#define SGTL5000_CHIP_CLK_CTRL_DEFAULT 0x0008
#define SGTL5000_RATE_MODE_MASK 0x0030
#define SGTL5000_RATE_MODE_SHIFT 4
#define SGTL5000_RATE_MODE_WIDTH 2
@@ -325,6 +326,7 @@
/*
* SGTL5000_CHIP_ANA_POWER
*/
+#define SGTL5000_ANA_POWER_DEFAULT 0x7060
#define SGTL5000_DAC_STEREO 0x4000
#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000
#define SGTL5000_STARTUP_POWERUP 0x1000
diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c
index da60e3fe5ee7..846deed6af41 100644
--- a/sound/soc/codecs/wm5102.c
+++ b/sound/soc/codecs/wm5102.c
@@ -1713,6 +1713,7 @@ static const struct snd_soc_dapm_route wm5102_dapm_routes[] = {
{ "MICSUPP", NULL, "SYSCLK" },
+ { "DRC1 Signal Activity", NULL, "SYSCLK" },
{ "DRC1 Signal Activity", NULL, "DRC1L" },
{ "DRC1 Signal Activity", NULL, "DRC1R" },
};
@@ -1872,7 +1873,7 @@ static struct snd_soc_dai_driver wm5102_dai[] = {
.capture = {
.stream_name = "Audio Trace CPU",
.channels_min = 1,
- .channels_max = 6,
+ .channels_max = 4,
.rates = WM5102_RATES,
.formats = WM5102_FORMATS,
},
diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c
index b5820e4d5471..156547026a40 100644
--- a/sound/soc/codecs/wm5110.c
+++ b/sound/soc/codecs/wm5110.c
@@ -1104,6 +1104,11 @@ SND_SOC_DAPM_INPUT("IN4R"),
SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"),
SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"),
+SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"),
+
+SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0,
+ &arizona_voice_trigger_switch[2]),
+
SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT,
0, NULL, 0, wm5110_in_ev,
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD |
@@ -1723,6 +1728,7 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = {
{ "OUT2L", NULL, "SYSCLK" },
{ "OUT2R", NULL, "SYSCLK" },
{ "OUT3L", NULL, "SYSCLK" },
+ { "OUT3R", NULL, "SYSCLK" },
{ "OUT4L", NULL, "SYSCLK" },
{ "OUT4R", NULL, "SYSCLK" },
{ "OUT5L", NULL, "SYSCLK" },
@@ -1997,10 +2003,16 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = {
{ "MICSUPP", NULL, "SYSCLK" },
+ { "DRC1 Signal Activity", NULL, "SYSCLK" },
+ { "DRC2 Signal Activity", NULL, "SYSCLK" },
{ "DRC1 Signal Activity", NULL, "DRC1L" },
{ "DRC1 Signal Activity", NULL, "DRC1R" },
{ "DRC2 Signal Activity", NULL, "DRC2L" },
{ "DRC2 Signal Activity", NULL, "DRC2R" },
+
+ { "DSP Voice Trigger", NULL, "SYSCLK" },
+ { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" },
+ { "DSP3 Voice Trigger", "Switch", "DSP3" },
};
static int wm5110_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
@@ -2222,6 +2234,7 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data)
{
struct wm5110_priv *priv = data;
struct arizona *arizona = priv->core.arizona;
+ struct arizona_voice_trigger_info info;
int serviced = 0;
int i, ret;
@@ -2229,6 +2242,12 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data)
ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]);
if (ret != -ENODEV)
serviced++;
+ if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) {
+ info.core = i;
+ arizona_call_notifiers(arizona,
+ ARIZONA_NOTIFY_VOICE_TRIGGER,
+ &info);
+ }
}
if (!serviced) {
@@ -2251,6 +2270,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec)
arizona_init_spk(codec);
arizona_init_gpio(codec);
arizona_init_mono(codec);
+ arizona_init_notifiers(codec);
ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
"ADSP2 Compressed IRQ", wm5110_adsp2_irq,
diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
index f6f9395ea38e..1c600819f768 100644
--- a/sound/soc/codecs/wm8940.c
+++ b/sound/soc/codecs/wm8940.c
@@ -743,6 +743,7 @@ static const struct regmap_config wm8940_regmap = {
.max_register = WM8940_MONOMIX,
.reg_defaults = wm8940_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(wm8940_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
.readable_reg = wm8940_readable_register,
.volatile_reg = wm8940_volatile_register,
diff --git a/sound/soc/codecs/wm8998.c b/sound/soc/codecs/wm8998.c
index 449f66636205..3a5c896a2d13 100644
--- a/sound/soc/codecs/wm8998.c
+++ b/sound/soc/codecs/wm8998.c
@@ -1166,6 +1166,7 @@ static const struct snd_soc_dapm_route wm8998_dapm_routes[] = {
{ "MICSUPP", NULL, "SYSCLK" },
+ { "DRC1 Signal Activity", NULL, "SYSCLK" },
{ "DRC1 Signal Activity", NULL, "DRC1L" },
{ "DRC1 Signal Activity", NULL, "DRC1R" },
};
diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c
index a07bd7c2c587..21fbe7d07063 100644
--- a/sound/soc/codecs/wm_adsp.c
+++ b/sound/soc/codecs/wm_adsp.c
@@ -394,6 +394,7 @@ static const struct {
int compr_direction;
int num_caps;
const struct wm_adsp_fw_caps *caps;
+ bool voice_trigger;
} wm_adsp_fw[WM_ADSP_NUM_FW] = {
[WM_ADSP_FW_MBC_VSS] = { .file = "mbc-vss" },
[WM_ADSP_FW_HIFI] = { .file = "hifi" },
@@ -406,6 +407,7 @@ static const struct {
.compr_direction = SND_COMPRESS_CAPTURE,
.num_caps = ARRAY_SIZE(ctrl_caps),
.caps = ctrl_caps,
+ .voice_trigger = true,
},
[WM_ADSP_FW_ASR] = { .file = "asr" },
[WM_ADSP_FW_TRACE] = {
@@ -2366,13 +2368,15 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
dsp->running = false;
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
- ADSP2_SYS_ENA | ADSP2_CORE_ENA |
- ADSP2_START, 0);
+ ADSP2_CORE_ENA | ADSP2_START, 0);
/* Make sure DMAs are quiesced */
+ regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0);
regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0);
regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0);
- regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0);
+
+ regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
+ ADSP2_SYS_ENA, 0);
list_for_each_entry(ctl, &dsp->ctl_list, list)
ctl->enabled = 0;
@@ -2998,6 +3002,9 @@ int wm_adsp_compr_handle_irq(struct wm_adsp *dsp)
goto out;
}
+ if (wm_adsp_fw[dsp->fw].voice_trigger && buf->irq_count == 2)
+ ret = WM_ADSP_COMPR_VOICE_TRIGGER;
+
out_notify:
if (compr && compr->stream)
snd_compr_fragment_elapsed(compr->stream);
@@ -3037,12 +3044,8 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
buf = compr->buf;
- if (!compr->buf) {
- ret = -ENXIO;
- goto out;
- }
-
- if (compr->buf->error) {
+ if (!compr->buf || compr->buf->error) {
+ snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN);
ret = -EIO;
goto out;
}
@@ -3060,8 +3063,12 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
*/
if (buf->avail < wm_adsp_compr_frag_words(compr)) {
ret = wm_adsp_buffer_get_error(buf);
- if (ret < 0)
+ if (ret < 0) {
+ if (compr->buf->error)
+ snd_compr_stop_error(stream,
+ SNDRV_PCM_STATE_XRUN);
goto out;
+ }
ret = wm_adsp_buffer_reenable_irq(buf);
if (ret < 0) {
@@ -3156,11 +3163,10 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr,
adsp_dbg(dsp, "Requested read of %zu bytes\n", count);
- if (!compr->buf)
- return -ENXIO;
-
- if (compr->buf->error)
+ if (!compr->buf || compr->buf->error) {
+ snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN);
return -EIO;
+ }
count /= WM_ADSP_DATA_WORD_SIZE;
diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h
index feb61e2c4bb4..be3b5bcb7f17 100644
--- a/sound/soc/codecs/wm_adsp.h
+++ b/sound/soc/codecs/wm_adsp.h
@@ -19,6 +19,10 @@
#include "wmfw.h"
+/* Return values for wm_adsp_compr_handle_irq */
+#define WM_ADSP_COMPR_OK 0
+#define WM_ADSP_COMPR_VOICE_TRIGGER 1
+
struct wm_adsp_region {
int type;
unsigned int base;
OpenPOWER on IntegriCloud