diff options
Diffstat (limited to 'sound/soc/codecs')
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, + ¤t_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, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_E, ®); + 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, ®); + 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, ®); + 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, ®); + for (i = 0; i < inter_max_check; i++) { + if (inter_max_check < 10) { + usleep_range(1000, 1100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } else { + usleep_range(10000, 10100); + regmap_read(priv->regmap, CS53L30_IS, ®); + 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, ®); + devid = reg << 12; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, ®); + devid |= reg << 4; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, ®); + 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, ®); + 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, ®); - 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; |