diff options
Diffstat (limited to 'sound/soc/codecs')
42 files changed, 5240 insertions, 568 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 229cc89f8c5a..4abf37b5083f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -34,6 +34,8 @@ config SND_SOC_ALL_CODECS  	select SND_SOC_ADAU1977_I2C if I2C  	select SND_SOC_ADAU1701 if I2C  	select SND_SOC_ADAU7002 +	select SND_SOC_ADAU7118_I2C if I2C +	select SND_SOC_ADAU7118_HW  	select SND_SOC_ADS117X  	select SND_SOC_AK4104 if SPI_MASTER  	select SND_SOC_AK4118 if I2C @@ -179,6 +181,8 @@ config SND_SOC_ALL_CODECS  	select SND_SOC_STAC9766 if SND_SOC_AC97_BUS  	select SND_SOC_STI_SAS  	select SND_SOC_TAS2552 if I2C +	select SND_SOC_TAS2562 if I2C +	select SND_SOC_TAS2770 if I2C  	select SND_SOC_TAS5086 if I2C  	select SND_SOC_TAS571X if I2C  	select SND_SOC_TAS5720 if I2C @@ -257,16 +261,16 @@ config SND_SOC_ALL_CODECS  	select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)  	select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)  	select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) -        help -          Normally ASoC codec drivers are only built if a machine driver which -          uses them is also built since they are only usable with a machine -          driver.  Selecting this option will allow these drivers to be built -          without an explicit machine driver for test and development purposes. +	help +	  Normally ASoC codec drivers are only built if a machine driver which +	  uses them is also built since they are only usable with a machine +	  driver.  Selecting this option will allow these drivers to be built +	  without an explicit machine driver for test and development purposes.  	  Support for the bus types used to access the codecs to be built must  	  be selected separately. -          If unsure select "N". +	  If unsure select "N".  config SND_SOC_88PM860X  	tristate @@ -395,6 +399,33 @@ config SND_SOC_ADAU1977_I2C  config SND_SOC_ADAU7002  	tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter" +config SND_SOC_ADAU7118 +	tristate + +config SND_SOC_ADAU7118_HW +	tristate "Analog Devices ADAU7118 8 Channel PDM-to-I2S/TDM Converter - HW Mode" +	select SND_SOC_ADAU7118 +	help +	  Enable support for the Analog Devices ADAU7118 8 Channel PDM-to-I2S/TDM +	  Converter. In this mode, the device works in standalone mode which +	  means that there is no bus to comunicate with it. Stereo mode is not +	  supported in this mode. + +	  To compile this driver as a module, choose M here: the module +	  will be called snd-soc-adau7118-hw. + +config SND_SOC_ADAU7118_I2C +	tristate "Analog Devices ADAU7118 8 Channel PDM-to-I2S/TDM Converter - I2C" +	depends on I2C +	select SND_SOC_ADAU7118 +	select REGMAP_I2C +	help +	  Enable support for the Analog Devices ADAU7118 8 Channel PDM-to-I2S/TDM +	  Converter over I2C. This gives full support over the device. + +	  To compile this driver as a module, choose M here: the module +	  will be called snd-soc-adau7118-i2c. +  config SND_SOC_ADAV80X  	tristate @@ -478,6 +509,8 @@ config SND_SOC_CQ0093VC  config SND_SOC_CROS_EC_CODEC  	tristate "codec driver for ChromeOS EC"  	depends on CROS_EC +	select CRYPTO +	select CRYPTO_SHA256  	help  	  If you say yes here you will get support for the  	  ChromeOS Embedded Controller's Audio Codec. @@ -570,8 +603,8 @@ config SND_SOC_CS42XX8_I2C  # Cirrus Logic CS43130 HiFi DAC  config SND_SOC_CS43130 -        tristate "Cirrus Logic CS43130 CODEC" -        depends on I2C +	tristate "Cirrus Logic CS43130 CODEC" +	depends on I2C  config SND_SOC_CS4341  	tristate "Cirrus Logic CS4341 CODEC" @@ -643,19 +676,20 @@ config SND_SOC_L3         tristate  config SND_SOC_DA7210 -        tristate +	tristate  config SND_SOC_DA7213 -        tristate +	tristate "Dialog DA7213 CODEC" +	depends on I2C  config SND_SOC_DA7218  	tristate  config SND_SOC_DA7219 -        tristate +	tristate  config SND_SOC_DA732X -        tristate +	tristate  config SND_SOC_DA9055  	tristate @@ -717,7 +751,7 @@ config SND_SOC_INNO_RK3036  	select REGMAP_MMIO  config SND_SOC_ISABELLE -        tristate +	tristate  config SND_SOC_LM49453  	tristate @@ -988,7 +1022,7 @@ config SND_SOC_RT5640  	tristate  config SND_SOC_RT5645 -        tristate +	tristate  config SND_SOC_RT5651  	tristate @@ -1104,6 +1138,14 @@ config SND_SOC_TAS2552  	tristate "Texas Instruments TAS2552 Mono Audio amplifier"  	depends on I2C +config SND_SOC_TAS2562 +	tristate "Texas Instruments TAS2562 Mono Audio amplifier" +	depends on I2C + +config SND_SOC_TAS2770 +	tristate "Texas Instruments TAS2770 speaker amplifier" +	depends on I2C +  config SND_SOC_TAS5086  	tristate "Texas Instruments TAS5086 speaker amplifier"  	depends on I2C @@ -1220,7 +1262,7 @@ config SND_SOC_UDA134X         tristate  config SND_SOC_UDA1380 -        tristate +	tristate  	depends on I2C  config SND_SOC_WCD9335 @@ -1348,7 +1390,7 @@ config SND_SOC_WM8904  	depends on I2C  config SND_SOC_WM8940 -        tristate +	tristate  config SND_SOC_WM8955  	tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c498373dcc5f..ddfd07071925 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -22,6 +22,9 @@ 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-adau7118-objs := adau7118.o +snd-soc-adau7118-i2c-objs := adau7118-i2c.o +snd-soc-adau7118-hw-objs := adau7118-hw.o  snd-soc-adav80x-objs := adav80x.o  snd-soc-adav801-objs := adav801.o  snd-soc-adav803-objs := adav803.o @@ -196,6 +199,7 @@ snd-soc-tas571x-objs := tas571x.o  snd-soc-tas5720-objs := tas5720.o  snd-soc-tas6424-objs := tas6424.o  snd-soc-tda7419-objs := tda7419.o +snd-soc-tas2770-objs := tas2770.o  snd-soc-tfa9879-objs := tfa9879.o  snd-soc-tlv320aic23-objs := tlv320aic23.o  snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -280,6 +284,7 @@ snd-soc-max98504-objs := max98504.o  snd-soc-simple-amplifier-objs := simple-amplifier.o  snd-soc-tpa6130a2-objs := tpa6130a2.o  snd-soc-tas2552-objs := tas2552.o +snd-soc-tas2562-objs := tas2562.o  obj-$(CONFIG_SND_SOC_88PM860X)	+= snd-soc-88pm860x.o  obj-$(CONFIG_SND_SOC_AB8500_CODEC)	+= snd-soc-ab8500-codec.o @@ -304,6 +309,9 @@ 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_ADAU7118)	+= snd-soc-adau7118.o +obj-$(CONFIG_SND_SOC_ADAU7118_I2C)	+= snd-soc-adau7118-i2c.o +obj-$(CONFIG_SND_SOC_ADAU7118_HW)	+= snd-soc-adau7118-hw.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 @@ -474,11 +482,13 @@ obj-$(CONFIG_SND_SOC_STA529)   += snd-soc-sta529.o  obj-$(CONFIG_SND_SOC_STAC9766)	+= snd-soc-stac9766.o  obj-$(CONFIG_SND_SOC_STI_SAS)	+= snd-soc-sti-sas.o  obj-$(CONFIG_SND_SOC_TAS2552)	+= snd-soc-tas2552.o +obj-$(CONFIG_SND_SOC_TAS2562)	+= snd-soc-tas2562.o  obj-$(CONFIG_SND_SOC_TAS5086)	+= snd-soc-tas5086.o  obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o  obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o  obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o  obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o +obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o  obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o  obj-$(CONFIG_SND_SOC_TLV320AIC23)	+= snd-soc-tlv320aic23.o  obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C)	+= snd-soc-tlv320aic23-i2c.o diff --git a/sound/soc/codecs/adau1761.c b/sound/soc/codecs/adau1761.c index 977f5a63be3f..5ca9b744b7d8 100644 --- a/sound/soc/codecs/adau1761.c +++ b/sound/soc/codecs/adau1761.c @@ -28,6 +28,10 @@  #define ADAU1761_REC_MIXER_RIGHT1	0x400d  #define ADAU1761_LEFT_DIFF_INPUT_VOL	0x400e  #define ADAU1761_RIGHT_DIFF_INPUT_VOL	0x400f +#define ADAU1761_ALC_CTRL0		0x4011 +#define ADAU1761_ALC_CTRL1		0x4012 +#define ADAU1761_ALC_CTRL2		0x4013 +#define ADAU1761_ALC_CTRL3		0x4014  #define ADAU1761_PLAY_LR_MIXER_LEFT	0x4020  #define ADAU1761_PLAY_MIXER_LEFT0	0x401c  #define ADAU1761_PLAY_MIXER_LEFT1	0x401d @@ -71,6 +75,10 @@ static const struct reg_default adau1761_reg_defaults[] = {  	{ ADAU1761_REC_MIXER_RIGHT0,		0x00 },  	{ ADAU1761_REC_MIXER_RIGHT1,		0x00 },  	{ ADAU1761_LEFT_DIFF_INPUT_VOL,		0x00 }, +	{ ADAU1761_ALC_CTRL0,			0x00 }, +	{ ADAU1761_ALC_CTRL1,			0x00 }, +	{ ADAU1761_ALC_CTRL2,			0x00 }, +	{ ADAU1761_ALC_CTRL3,			0x00 },  	{ ADAU1761_RIGHT_DIFF_INPUT_VOL,	0x00 },  	{ ADAU1761_PLAY_LR_MIXER_LEFT,		0x00 },  	{ ADAU1761_PLAY_MIXER_LEFT0,		0x00 }, @@ -121,6 +129,10 @@ static const DECLARE_TLV_DB_SCALE(adau1761_sidetone_tlv, -1800, 300, 1);  static const DECLARE_TLV_DB_SCALE(adau1761_boost_tlv, -600, 600, 1);  static const DECLARE_TLV_DB_SCALE(adau1761_pga_boost_tlv, -2000, 2000, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_alc_max_gain_tlv, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_alc_target_tlv, -2850, 150, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_alc_ng_threshold_tlv, -7650, 150, 0); +  static const unsigned int adau1761_bias_select_values[] = {  	0, 2, 3,  }; @@ -147,6 +159,103 @@ static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_capture_bias_enum,  		ADAU17X1_REC_POWER_MGMT, 1, 0x3, adau1761_bias_select_text,  		adau1761_bias_select_values); +static const unsigned int adau1761_pga_slew_time_values[] = { +	3, 0, 1, 2, +}; + +static const char * const adau1761_pga_slew_time_text[] = { +	"Off", +	"24 ms", +	"48 ms", +	"96 ms", +}; + +static const char * const adau1761_alc_function_text[] = { +	"Off", +	"Right", +	"Left", +	"Stereo", +	"DSP control", +}; + +static const char * const adau1761_alc_hold_time_text[] = { +	"2.67 ms", +	"5.34 ms", +	"10.68 ms", +	"21.36 ms", +	"42.72 ms", +	"85.44 ms", +	"170.88 ms", +	"341.76 ms", +	"683.52 ms", +	"1367 ms", +	"2734.1 ms", +	"5468.2 ms", +	"10936 ms", +	"21873 ms", +	"43745 ms", +	"87491 ms", +}; + +static const char * const adau1761_alc_attack_time_text[] = { +	"6 ms", +	"12 ms", +	"24 ms", +	"48 ms", +	"96 ms", +	"192 ms", +	"384 ms", +	"768 ms", +	"1540 ms", +	"3070 ms", +	"6140 ms", +	"12290 ms", +	"24580 ms", +	"49150 ms", +	"98300 ms", +	"196610 ms", +}; + +static const char * const adau1761_alc_decay_time_text[] = { +	"24 ms", +	"48 ms", +	"96 ms", +	"192 ms", +	"384 ms", +	"768 ms", +	"15400 ms", +	"30700 ms", +	"61400 ms", +	"12290 ms", +	"24580 ms", +	"49150 ms", +	"98300 ms", +	"196610 ms", +	"393220 ms", +	"786430 ms", +}; + +static const char * const adau1761_alc_ng_type_text[] = { +	"Hold", +	"Mute", +	"Fade", +	"Fade + Mute", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_pga_slew_time_enum, +		ADAU1761_ALC_CTRL0, 6, 0x3, adau1761_pga_slew_time_text, +		adau1761_pga_slew_time_values); +static SOC_ENUM_SINGLE_DECL(adau1761_alc_function_enum, +		ADAU1761_ALC_CTRL0, 0, adau1761_alc_function_text); +static SOC_ENUM_SINGLE_DECL(adau1761_alc_hold_time_enum, +		ADAU1761_ALC_CTRL1, 4, adau1761_alc_hold_time_text); +static SOC_ENUM_SINGLE_DECL(adau1761_alc_attack_time_enum, +		ADAU1761_ALC_CTRL2, 4, adau1761_alc_attack_time_text); +static SOC_ENUM_SINGLE_DECL(adau1761_alc_decay_time_enum, +		ADAU1761_ALC_CTRL2, 0, adau1761_alc_decay_time_text); +static SOC_ENUM_SINGLE_DECL(adau1761_alc_ng_type_enum, +		ADAU1761_ALC_CTRL3, 6, adau1761_alc_ng_type_text); +  static const struct snd_kcontrol_new adau1761_jack_detect_controls[] = {  	SOC_SINGLE("Speaker Auto-mute Switch", ADAU1761_DIGMIC_JACKDETECT,  		4, 1, 0), @@ -161,6 +270,22 @@ static const struct snd_kcontrol_new adau1761_differential_mode_controls[] = {  	SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1761_REC_MIXER_LEFT1,  		ADAU1761_REC_MIXER_RIGHT1, 3, 2, 0, adau1761_pga_boost_tlv), + +	SOC_ENUM("PGA Capture Slew Time", adau1761_pga_slew_time_enum), + +	SOC_SINGLE_TLV("ALC Capture Max Gain Volume", ADAU1761_ALC_CTRL0, +		3, 7, 0, adau1761_alc_max_gain_tlv), +	SOC_ENUM("ALC Capture Function", adau1761_alc_function_enum), +	SOC_ENUM("ALC Capture Hold Time", adau1761_alc_hold_time_enum), +	SOC_SINGLE_TLV("ALC Capture Target Volume", ADAU1761_ALC_CTRL1, +		0, 15, 0, adau1761_alc_target_tlv), +	SOC_ENUM("ALC Capture Attack Time", adau1761_alc_decay_time_enum), +	SOC_ENUM("ALC Capture Decay Time", adau1761_alc_attack_time_enum), +	SOC_ENUM("ALC Capture Noise Gate Type", adau1761_alc_ng_type_enum), +	SOC_SINGLE("ALC Capture Noise Gate Switch", +		ADAU1761_ALC_CTRL3, 5, 1, 0), +	SOC_SINGLE_TLV("ALC Capture Noise Gate Threshold Volume", +		ADAU1761_ALC_CTRL3, 0, 31, 0, adau1761_alc_ng_threshold_tlv),  };  static const struct snd_kcontrol_new adau1761_single_mode_controls[] = { @@ -632,6 +757,10 @@ static bool adau1761_readable_register(struct device *dev, unsigned int reg)  	case ADAU1761_DEJITTER:  	case ADAU1761_CLK_ENABLE0:  	case ADAU1761_CLK_ENABLE1: +	case ADAU1761_ALC_CTRL0: +	case ADAU1761_ALC_CTRL1: +	case ADAU1761_ALC_CTRL2: +	case ADAU1761_ALC_CTRL3:  		return true;  	default:  		break; diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c new file mode 100644 index 000000000000..45a5d2dcc0f2 --- /dev/null +++ b/sound/soc/codecs/adau7118-hw.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter Standalone Hw +// driver +// +// Copyright 2019 Analog Devices Inc. + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> + +#include "adau7118.h" + +static int adau7118_probe_hw(struct platform_device *pdev) +{ +	return adau7118_probe(&pdev->dev, NULL, true); +} + +static const struct of_device_id adau7118_of_match[] = { +	{ .compatible = "adi,adau7118" }, +	{} +}; +MODULE_DEVICE_TABLE(of, adau7118_of_match); + +static const struct platform_device_id adau7118_id[] = { +	{ .name	= "adau7118" }, +	{ } +}; +MODULE_DEVICE_TABLE(platform, adau7118_id); + +static struct platform_driver adau7118_driver_hw = { +	.driver = { +		.name = "adau7118", +		.of_match_table = adau7118_of_match, +	}, +	.probe = adau7118_probe_hw, +	.id_table = adau7118_id, +}; +module_platform_driver(adau7118_driver_hw); + +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); +MODULE_DESCRIPTION("ADAU7118 8 channel PDM-to-I2S/TDM Converter driver for standalone hw mode"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau7118-i2c.c b/sound/soc/codecs/adau7118-i2c.c new file mode 100644 index 000000000000..a8211362fe82 --- /dev/null +++ b/sound/soc/codecs/adau7118-i2c.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter driver over I2C +// +// Copyright 2019 Analog Devices Inc. + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "adau7118.h" + +static const struct reg_default adau7118_reg_defaults[] = { +	{ ADAU7118_REG_VENDOR_ID, 0x41 }, +	{ ADAU7118_REG_DEVICE_ID1, 0x71 }, +	{ ADAU7118_REG_DEVICE_ID2, 0x18 }, +	{ ADAU7118_REG_REVISION_ID, 0x00 }, +	{ ADAU7118_REG_ENABLES, 0x3F }, +	{ ADAU7118_REG_DEC_RATIO_CLK_MAP, 0xC0 }, +	{ ADAU7118_REG_HPF_CONTROL, 0xD0 }, +	{ ADAU7118_REG_SPT_CTRL1, 0x41 }, +	{ ADAU7118_REG_SPT_CTRL2, 0x00 }, +	{ ADAU7118_REG_SPT_CX(0), 0x01 }, +	{ ADAU7118_REG_SPT_CX(1), 0x11 }, +	{ ADAU7118_REG_SPT_CX(2), 0x21 }, +	{ ADAU7118_REG_SPT_CX(3), 0x31 }, +	{ ADAU7118_REG_SPT_CX(4), 0x41 }, +	{ ADAU7118_REG_SPT_CX(5), 0x51 }, +	{ ADAU7118_REG_SPT_CX(6), 0x61 }, +	{ ADAU7118_REG_SPT_CX(7), 0x71 }, +	{ ADAU7118_REG_DRIVE_STRENGTH, 0x2a }, +	{ ADAU7118_REG_RESET, 0x00 }, +}; + +static const struct regmap_config adau7118_regmap_config = { +	.reg_bits = 8, +	.val_bits = 8, +	.reg_defaults = adau7118_reg_defaults, +	.num_reg_defaults = ARRAY_SIZE(adau7118_reg_defaults), +	.cache_type = REGCACHE_RBTREE, +	.max_register = ADAU7118_REG_RESET, +}; + +static int adau7118_probe_i2c(struct i2c_client *i2c, +			      const struct i2c_device_id *id) +{ +	struct regmap *map; + +	map = devm_regmap_init_i2c(i2c, &adau7118_regmap_config); +	if (IS_ERR(map)) { +		dev_err(&i2c->dev, "Failed to init regmap %ld\n", PTR_ERR(map)); +		return PTR_ERR(map); +	} + +	return adau7118_probe(&i2c->dev, map, false); +} + +static const struct of_device_id adau7118_of_match[] = { +	{ .compatible = "adi,adau7118" }, +	{} +}; +MODULE_DEVICE_TABLE(of, adau7118_of_match); + +static const struct i2c_device_id adau7118_id[] = { +	{"adau7118", 0}, +	{} +}; +MODULE_DEVICE_TABLE(i2c, adau7118_id); + +static struct i2c_driver adau7118_driver = { +	.driver = { +		.name = "adau7118", +		.of_match_table = adau7118_of_match, +	}, +	.probe = adau7118_probe_i2c, +	.id_table = adau7118_id, +}; +module_i2c_driver(adau7118_driver); + +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); +MODULE_DESCRIPTION("ADAU7118 8 channel PDM-to-I2S/TDM Converter driver over I2C"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau7118.c b/sound/soc/codecs/adau7118.c new file mode 100644 index 000000000000..841229dcbca1 --- /dev/null +++ b/sound/soc/codecs/adau7118.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter driver +// +// Copyright 2019 Analog Devices Inc. + +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "adau7118.h" + +#define ADAU7118_DEC_RATIO_MASK		GENMASK(1, 0) +#define ADAU7118_DEC_RATIO(x)		FIELD_PREP(ADAU7118_DEC_RATIO_MASK, x) +#define ADAU7118_CLK_MAP_MASK		GENMASK(7, 4) +#define ADAU7118_SLOT_WIDTH_MASK	GENMASK(5, 4) +#define ADAU7118_SLOT_WIDTH(x)		FIELD_PREP(ADAU7118_SLOT_WIDTH_MASK, x) +#define ADAU7118_TRISTATE_MASK		BIT(6) +#define ADAU7118_TRISTATE(x)		FIELD_PREP(ADAU7118_TRISTATE_MASK, x) +#define ADAU7118_DATA_FMT_MASK		GENMASK(3, 1) +#define ADAU7118_DATA_FMT(x)		FIELD_PREP(ADAU7118_DATA_FMT_MASK, x) +#define ADAU7118_SAI_MODE_MASK		BIT(0) +#define ADAU7118_SAI_MODE(x)		FIELD_PREP(ADAU7118_SAI_MODE_MASK, x) +#define ADAU7118_LRCLK_BCLK_POL_MASK	GENMASK(1, 0) +#define ADAU7118_LRCLK_BCLK_POL(x) \ +				FIELD_PREP(ADAU7118_LRCLK_BCLK_POL_MASK, x) +#define ADAU7118_SPT_SLOT_MASK		GENMASK(7, 4) +#define ADAU7118_SPT_SLOT(x)		FIELD_PREP(ADAU7118_SPT_SLOT_MASK, x) +#define ADAU7118_FULL_SOFT_R_MASK	BIT(1) +#define ADAU7118_FULL_SOFT_R(x)		FIELD_PREP(ADAU7118_FULL_SOFT_R_MASK, x) + +struct adau7118_data { +	struct regmap *map; +	struct device *dev; +	struct regulator *iovdd; +	struct regulator *dvdd; +	u32 slot_width; +	u32 slots; +	bool hw_mode; +	bool right_j; +}; + +/* Input Enable */ +static const struct snd_kcontrol_new adau7118_dapm_pdm_control[4] = { +	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 0, 1, 0), +	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 1, 1, 0), +	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 2, 1, 0), +	SOC_DAPM_SINGLE("Capture Switch", ADAU7118_REG_ENABLES, 3, 1, 0), +}; + +static const struct snd_soc_dapm_widget adau7118_widgets_sw[] = { +	/* Input Enable Switches */ +	SND_SOC_DAPM_SWITCH("PDM0", SND_SOC_NOPM, 0, 0, +			    &adau7118_dapm_pdm_control[0]), +	SND_SOC_DAPM_SWITCH("PDM1", SND_SOC_NOPM, 0, 0, +			    &adau7118_dapm_pdm_control[1]), +	SND_SOC_DAPM_SWITCH("PDM2", SND_SOC_NOPM, 0, 0, +			    &adau7118_dapm_pdm_control[2]), +	SND_SOC_DAPM_SWITCH("PDM3", SND_SOC_NOPM, 0, 0, +			    &adau7118_dapm_pdm_control[3]), + +	/* PDM Clocks */ +	SND_SOC_DAPM_SUPPLY("PDM_CLK0", ADAU7118_REG_ENABLES, 4, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("PDM_CLK1", ADAU7118_REG_ENABLES, 5, 0, NULL, 0), + +	/* Output channels */ +	SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, ADAU7118_REG_SPT_CX(0), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX2", "Capture", 0, ADAU7118_REG_SPT_CX(1), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX3", "Capture", 0, ADAU7118_REG_SPT_CX(2), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX4", "Capture", 0, ADAU7118_REG_SPT_CX(3), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX5", "Capture", 0, ADAU7118_REG_SPT_CX(4), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX6", "Capture", 0, ADAU7118_REG_SPT_CX(5), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX7", "Capture", 0, ADAU7118_REG_SPT_CX(6), +			     0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1TX8", "Capture", 0, ADAU7118_REG_SPT_CX(7), +			     0, 0), +}; + +static const struct snd_soc_dapm_route adau7118_routes_sw[] = { +	{ "PDM0", "Capture Switch", "PDM_DAT0" }, +	{ "PDM1", "Capture Switch", "PDM_DAT1" }, +	{ "PDM2", "Capture Switch", "PDM_DAT2" }, +	{ "PDM3", "Capture Switch", "PDM_DAT3" }, +	{ "AIF1TX1", NULL, "PDM0" }, +	{ "AIF1TX2", NULL, "PDM0" }, +	{ "AIF1TX3", NULL, "PDM1" }, +	{ "AIF1TX4", NULL, "PDM1" }, +	{ "AIF1TX5", NULL, "PDM2" }, +	{ "AIF1TX6", NULL, "PDM2" }, +	{ "AIF1TX7", NULL, "PDM3" }, +	{ "AIF1TX8", NULL, "PDM3" }, +	{ "Capture", NULL, "PDM_CLK0" }, +	{ "Capture", NULL, "PDM_CLK1" }, +}; + +static const struct snd_soc_dapm_widget adau7118_widgets_hw[] = { +	SND_SOC_DAPM_AIF_OUT("AIF1TX", "Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route adau7118_routes_hw[] = { +	{ "AIF1TX", NULL, "PDM_DAT0" }, +	{ "AIF1TX", NULL, "PDM_DAT1" }, +	{ "AIF1TX", NULL, "PDM_DAT2" }, +	{ "AIF1TX", NULL, "PDM_DAT3" }, +}; + +static const struct snd_soc_dapm_widget adau7118_widgets[] = { +	SND_SOC_DAPM_INPUT("PDM_DAT0"), +	SND_SOC_DAPM_INPUT("PDM_DAT1"), +	SND_SOC_DAPM_INPUT("PDM_DAT2"), +	SND_SOC_DAPM_INPUT("PDM_DAT3"), +}; + +static int adau7118_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 adau7118_data *st = +		snd_soc_component_get_drvdata(dai->component); +	int chan, ret; + +	dev_dbg(st->dev, "Set channel map, %d", tx_num); + +	for (chan = 0; chan < tx_num; chan++) { +		ret = snd_soc_component_update_bits(dai->component, +					ADAU7118_REG_SPT_CX(chan), +					ADAU7118_SPT_SLOT_MASK, +					ADAU7118_SPT_SLOT(tx_slot[chan])); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +static int adau7118_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	struct adau7118_data *st = +		snd_soc_component_get_drvdata(dai->component); +	int ret = 0; +	u32 regval; + +	dev_dbg(st->dev, "Set format, fmt:%d\n", fmt); + +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_I2S: +		ret = snd_soc_component_update_bits(dai->component, +						    ADAU7118_REG_SPT_CTRL1, +						    ADAU7118_DATA_FMT_MASK, +						    ADAU7118_DATA_FMT(0)); +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		ret = snd_soc_component_update_bits(dai->component, +						    ADAU7118_REG_SPT_CTRL1, +						    ADAU7118_DATA_FMT_MASK, +						    ADAU7118_DATA_FMT(1)); +		break; +	case SND_SOC_DAIFMT_RIGHT_J: +		st->right_j = true; +		break; +	default: +		dev_err(st->dev, "Invalid format %d", +			fmt & SND_SOC_DAIFMT_FORMAT_MASK); +		return -EINVAL; +	} + +	if (ret < 0) +		return ret; + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		regval = ADAU7118_LRCLK_BCLK_POL(0); +		break; +	case SND_SOC_DAIFMT_NB_IF: +		regval = ADAU7118_LRCLK_BCLK_POL(2); +		break; +	case SND_SOC_DAIFMT_IB_NF: +		regval = ADAU7118_LRCLK_BCLK_POL(1); +		break; +	case SND_SOC_DAIFMT_IB_IF: +		regval = ADAU7118_LRCLK_BCLK_POL(3); +		break; +	default: +		dev_err(st->dev, "Invalid Inv mask %d", +			fmt & SND_SOC_DAIFMT_INV_MASK); +		return -EINVAL; +	} + +	ret = snd_soc_component_update_bits(dai->component, +					    ADAU7118_REG_SPT_CTRL2, +					    ADAU7118_LRCLK_BCLK_POL_MASK, +					    regval); +	if (ret < 0) +		return ret; + +	return 0; +} + +static int adau7118_set_tristate(struct snd_soc_dai *dai, int tristate) +{ +	struct adau7118_data *st = +		snd_soc_component_get_drvdata(dai->component); +	int ret; + +	dev_dbg(st->dev, "Set tristate, %d\n", tristate); + +	ret = snd_soc_component_update_bits(dai->component, +					    ADAU7118_REG_SPT_CTRL1, +					    ADAU7118_TRISTATE_MASK, +					    ADAU7118_TRISTATE(tristate)); +	if (ret < 0) +		return ret; + +	return 0; +} + +static int adau7118_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, +				 unsigned int rx_mask, int slots, +				 int slot_width) +{ +	struct adau7118_data *st = +		snd_soc_component_get_drvdata(dai->component); +	int ret = 0; +	u32 regval; + +	dev_dbg(st->dev, "Set tdm, slots:%d width:%d\n", slots, slot_width); + +	switch (slot_width) { +	case 32: +		regval = ADAU7118_SLOT_WIDTH(0); +		break; +	case 24: +		regval = ADAU7118_SLOT_WIDTH(2); +		break; +	case 16: +		regval = ADAU7118_SLOT_WIDTH(1); +		break; +	default: +		dev_err(st->dev, "Invalid slot width:%d\n", slot_width); +		return -EINVAL; +	} + +	ret = snd_soc_component_update_bits(dai->component, +					    ADAU7118_REG_SPT_CTRL1, +					    ADAU7118_SLOT_WIDTH_MASK, regval); +	if (ret < 0) +		return ret; + +	st->slot_width = slot_width; +	st->slots = slots; + +	return 0; +} + +static int adau7118_hw_params(struct snd_pcm_substream *substream, +			      struct snd_pcm_hw_params *params, +			      struct snd_soc_dai *dai) +{ +	struct adau7118_data *st = +		snd_soc_component_get_drvdata(dai->component); +	u32 data_width = params_width(params), slots_width; +	int ret; +	u32 regval; + +	if (!st->slots) { +		/* set stereo mode */ +		ret = snd_soc_component_update_bits(dai->component, +						    ADAU7118_REG_SPT_CTRL1, +						    ADAU7118_SAI_MODE_MASK, +						    ADAU7118_SAI_MODE(0)); +		if (ret < 0) +			return ret; + +		slots_width = 32; +	} else { +		slots_width = st->slot_width; +	} + +	if (data_width > slots_width) { +		dev_err(st->dev, "Invalid data_width:%d, slots_width:%d", +			data_width, slots_width); +		return -EINVAL; +	} + +	if (st->right_j) { +		switch (slots_width - data_width) { +		case 8: +			/* delay bclck by 8 */ +			regval = ADAU7118_DATA_FMT(2); +			break; +		case 12: +			/* delay bclck by 12 */ +			regval = ADAU7118_DATA_FMT(3); +			break; +		case 16: +			/* delay bclck by 16 */ +			regval = ADAU7118_DATA_FMT(4); +			break; +		default: +			dev_err(st->dev, +				"Cannot set right_j setting, slot_w:%d, data_w:%d\n", +					slots_width, data_width); +			return -EINVAL; +		} + +		ret = snd_soc_component_update_bits(dai->component, +						    ADAU7118_REG_SPT_CTRL1, +						    ADAU7118_DATA_FMT_MASK, +						    regval); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +static int adau7118_set_bias_level(struct snd_soc_component *component, +				   enum snd_soc_bias_level level) +{ +	struct adau7118_data *st = snd_soc_component_get_drvdata(component); +	int ret = 0; + +	dev_dbg(st->dev, "Set bias level %d\n", level); + +	switch (level) { +	case SND_SOC_BIAS_ON: +	case SND_SOC_BIAS_PREPARE: +		break; + +	case SND_SOC_BIAS_STANDBY: +		if (snd_soc_component_get_bias_level(component) == +							SND_SOC_BIAS_OFF) { +			/* power on */ +			ret = regulator_enable(st->iovdd); +			if (ret) +				return ret; + +			/* there's no timing constraints before enabling dvdd */ +			ret = regulator_enable(st->dvdd); +			if (ret) { +				regulator_disable(st->iovdd); +				return ret; +			} + +			if (st->hw_mode) +				return 0; + +			regcache_cache_only(st->map, false); +			/* sync cache */ +			ret = snd_soc_component_cache_sync(component); +		} +		break; +	case SND_SOC_BIAS_OFF: +		/* power off */ +		ret = regulator_disable(st->dvdd); +		if (ret) +			return ret; + +		ret = regulator_disable(st->iovdd); +		if (ret) +			return ret; + +		if (st->hw_mode) +			return 0; + +		/* cache only */ +		regcache_mark_dirty(st->map); +		regcache_cache_only(st->map, true); + +		break; +	} + +	return ret; +} + +static int adau7118_component_probe(struct snd_soc_component *component) +{ +	struct adau7118_data *st = snd_soc_component_get_drvdata(component); +	struct snd_soc_dapm_context *dapm = +					snd_soc_component_get_dapm(component); +	int ret = 0; + +	if (st->hw_mode) { +		ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_hw, +					ARRAY_SIZE(adau7118_widgets_hw)); +		if (ret) +			return ret; + +		ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_hw, +					      ARRAY_SIZE(adau7118_routes_hw)); +	} else { +		snd_soc_component_init_regmap(component, st->map); +		ret = snd_soc_dapm_new_controls(dapm, adau7118_widgets_sw, +					ARRAY_SIZE(adau7118_widgets_sw)); +		if (ret) +			return ret; + +		ret = snd_soc_dapm_add_routes(dapm, adau7118_routes_sw, +					      ARRAY_SIZE(adau7118_routes_sw)); +	} + +	return ret; +} + +static const struct snd_soc_dai_ops adau7118_ops = { +	.hw_params = adau7118_hw_params, +	.set_channel_map = adau7118_set_channel_map, +	.set_fmt = adau7118_set_fmt, +	.set_tdm_slot = adau7118_set_tdm_slot, +	.set_tristate = adau7118_set_tristate, +}; + +static struct snd_soc_dai_driver adau7118_dai = { +	.name = "adau7118-hifi-capture", +	.capture = { +		.stream_name = "Capture", +		.channels_min = 1, +		.channels_max = 8, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | +			SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_S24_LE | +			SNDRV_PCM_FMTBIT_S24_3LE, +		.rates = SNDRV_PCM_RATE_CONTINUOUS, +		.rate_min = 4000, +		.rate_max = 192000, +		.sig_bits = 24, +	}, +}; + +static const struct snd_soc_component_driver adau7118_component_driver = { +	.probe			= adau7118_component_probe, +	.set_bias_level		= adau7118_set_bias_level, +	.dapm_widgets		= adau7118_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(adau7118_widgets), +	.use_pmdown_time	= 1, +	.endianness		= 1, +	.non_legacy_dai_naming	= 1, +}; + +static void adau7118_regulator_disable(void *data) +{ +	struct adau7118_data *st = data; +	int ret; +	/* +	 * If we fail to disable DVDD, don't bother in trying IOVDD. We +	 * actually don't want to be left in the situation where DVDD +	 * is enabled and IOVDD is disabled. +	 */ +	ret = regulator_disable(st->dvdd); +	if (ret) +		return; + +	regulator_disable(st->iovdd); +} + +static int adau7118_regulator_setup(struct adau7118_data *st) +{ +	st->iovdd = devm_regulator_get(st->dev, "iovdd"); +	if (IS_ERR(st->iovdd)) { +		dev_err(st->dev, "Could not get iovdd: %ld\n", +			PTR_ERR(st->iovdd)); +		return PTR_ERR(st->iovdd); +	} + +	st->dvdd = devm_regulator_get(st->dev, "dvdd"); +	if (IS_ERR(st->dvdd)) { +		dev_err(st->dev, "Could not get dvdd: %ld\n", +			PTR_ERR(st->dvdd)); +		return PTR_ERR(st->dvdd); +	} +	/* just assume the device is in reset */ +	if (!st->hw_mode) { +		regcache_mark_dirty(st->map); +		regcache_cache_only(st->map, true); +	} + +	return devm_add_action_or_reset(st->dev, adau7118_regulator_disable, +					st); +} + +static int adau7118_parset_dt(const struct adau7118_data *st) +{ +	int ret; +	u32 dec_ratio = 0; +	/* 4 inputs */ +	u32 clk_map[4], regval; + +	if (st->hw_mode) +		return 0; + +	ret = device_property_read_u32(st->dev, "adi,decimation-ratio", +				       &dec_ratio); +	if (!ret) { +		switch (dec_ratio) { +		case 64: +			regval = ADAU7118_DEC_RATIO(0); +			break; +		case 32: +			regval = ADAU7118_DEC_RATIO(1); +			break; +		case 16: +			regval = ADAU7118_DEC_RATIO(2); +			break; +		default: +			dev_err(st->dev, "Invalid dec ratio: %u", dec_ratio); +			return -EINVAL; +		} + +		ret = regmap_update_bits(st->map, +					 ADAU7118_REG_DEC_RATIO_CLK_MAP, +					 ADAU7118_DEC_RATIO_MASK, regval); +		if (ret) +			return ret; +	} + +	ret = device_property_read_u32_array(st->dev, "adi,pdm-clk-map", +					     clk_map, ARRAY_SIZE(clk_map)); +	if (!ret) { +		int pdm; +		u32 _clk_map = 0; + +		for (pdm = 0; pdm < ARRAY_SIZE(clk_map); pdm++) +			_clk_map |= (clk_map[pdm] << (pdm + 4)); + +		ret = regmap_update_bits(st->map, +					 ADAU7118_REG_DEC_RATIO_CLK_MAP, +					 ADAU7118_CLK_MAP_MASK, _clk_map); +		if (ret) +			return ret; +	} + +	return 0; +} + +int adau7118_probe(struct device *dev, struct regmap *map, bool hw_mode) +{ +	struct adau7118_data *st; +	int ret; + +	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); +	if (!st) +		return -ENOMEM; + +	st->dev = dev; +	st->hw_mode = hw_mode; +	dev_set_drvdata(dev, st); + +	if (!hw_mode) { +		st->map = map; +		adau7118_dai.ops = &adau7118_ops; +		/* +		 * Perform a full soft reset. This will set all register's +		 * with their reset values. +		 */ +		ret = regmap_update_bits(map, ADAU7118_REG_RESET, +					 ADAU7118_FULL_SOFT_R_MASK, +					 ADAU7118_FULL_SOFT_R(1)); +		if (ret) +			return ret; +	} + +	ret = adau7118_parset_dt(st); +	if (ret) +		return ret; + +	ret = adau7118_regulator_setup(st); +	if (ret) +		return ret; + +	return devm_snd_soc_register_component(dev, +					       &adau7118_component_driver, +					       &adau7118_dai, 1); +} +EXPORT_SYMBOL_GPL(adau7118_probe); + +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); +MODULE_DESCRIPTION("ADAU7118 8 channel PDM-to-I2S/TDM Converter driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau7118.h b/sound/soc/codecs/adau7118.h new file mode 100644 index 000000000000..c65679a4dff1 --- /dev/null +++ b/sound/soc/codecs/adau7118.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_ADAU7118_H +#define _LINUX_ADAU7118_H + +struct regmap; +struct device; + +/* register map */ +#define ADAU7118_REG_VENDOR_ID		0x00 +#define ADAU7118_REG_DEVICE_ID1		0x01 +#define ADAU7118_REG_DEVICE_ID2		0x02 +#define ADAU7118_REG_REVISION_ID	0x03 +#define ADAU7118_REG_ENABLES		0x04 +#define ADAU7118_REG_DEC_RATIO_CLK_MAP	0x05 +#define ADAU7118_REG_HPF_CONTROL	0x06 +#define ADAU7118_REG_SPT_CTRL1		0x07 +#define ADAU7118_REG_SPT_CTRL2		0x08 +#define ADAU7118_REG_SPT_CX(num)	(0x09 + (num)) +#define ADAU7118_REG_DRIVE_STRENGTH	0x11 +#define ADAU7118_REG_RESET		0x12 + +int adau7118_probe(struct device *dev, struct regmap *map, bool hw_mode); + +#endif diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index 3c1bd24a1057..7b17f39a6a10 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -1,15 +1,23 @@  // SPDX-License-Identifier: GPL-2.0  /* - * Driver for ChromeOS Embedded Controller codec. + * Copyright 2019 Google, Inc. + * + * ChromeOS Embedded Controller codec driver.   *   * This driver uses the cros-ec interface to communicate with the ChromeOS   * EC for audio function.   */ +#include <crypto/hash.h> +#include <crypto/sha.h>  #include <linux/delay.h>  #include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h>  #include <linux/kernel.h>  #include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h>  #include <linux/platform_data/cros_ec_commands.h>  #include <linux/platform_data/cros_ec_proto.h>  #include <linux/platform_device.h> @@ -18,92 +26,279 @@  #include <sound/soc.h>  #include <sound/tlv.h> -#define DRV_NAME "cros-ec-codec" - -/** - * struct cros_ec_codec_data - ChromeOS EC codec driver data. - * @dev:		Device structure used in sysfs. - * @ec_device:		cros_ec_device structure to talk to the physical device. - * @component:		Pointer to the component. - * @max_dmic_gain:	Maximum gain in dB supported by EC codec. - */ -struct cros_ec_codec_data { +struct cros_ec_codec_priv {  	struct device *dev;  	struct cros_ec_device *ec_device; -	struct snd_soc_component *component; -	unsigned int max_dmic_gain; + +	/* common */ +	uint32_t ec_capabilities; + +	uint64_t ec_shm_addr; +	uint32_t ec_shm_len; + +	uint64_t ap_shm_phys_addr; +	uint32_t ap_shm_len; +	uint64_t ap_shm_addr; +	uint64_t ap_shm_last_alloc; + +	/* DMIC */ +	atomic_t dmic_probed; + +	/* WoV */ +	bool wov_enabled; +	uint8_t *wov_audio_shm_p; +	uint32_t wov_audio_shm_len; +	uint8_t wov_audio_shm_type; +	uint8_t *wov_lang_shm_p; +	uint32_t wov_lang_shm_len; +	uint8_t wov_lang_shm_type; + +	struct mutex wov_dma_lock; +	uint8_t wov_buf[64000]; +	uint32_t wov_rp, wov_wp; +	size_t wov_dma_offset; +	bool wov_burst_read; +	struct snd_pcm_substream *wov_substream; +	struct delayed_work wov_copy_work; +	struct notifier_block wov_notifier;  }; -static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0); +static int ec_codec_capable(struct cros_ec_codec_priv *priv, uint8_t cap) +{ +	return priv->ec_capabilities & BIT(cap); +} -static int ec_command_get_gain(struct snd_soc_component *component, -			       struct ec_param_codec_i2s *param, -			       struct ec_codec_i2s_gain *resp) +static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, +				uint8_t *out, size_t outsize, +				uint8_t *in, size_t insize)  { -	struct cros_ec_codec_data *codec_data = -		snd_soc_component_get_drvdata(component); -	struct cros_ec_device *ec_device = codec_data->ec_device; -	u8 buffer[sizeof(struct cros_ec_command) + -		  max(sizeof(struct ec_param_codec_i2s), -		      sizeof(struct ec_codec_i2s_gain))]; -	struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;  	int ret; +	struct cros_ec_command *msg; + +	msg = kmalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); +	if (!msg) +		return -ENOMEM;  	msg->version = 0; -	msg->command = EC_CMD_CODEC_I2S; -	msg->outsize = sizeof(struct ec_param_codec_i2s); -	msg->insize = sizeof(struct ec_codec_i2s_gain); +	msg->command = cmd; +	msg->outsize = outsize; +	msg->insize = insize; -	memcpy(msg->data, param, msg->outsize); +	if (outsize) +		memcpy(msg->data, out, outsize); -	ret = cros_ec_cmd_xfer_status(ec_device, msg); -	if (ret > 0) -		memcpy(resp, msg->data, msg->insize); +	ret = cros_ec_cmd_xfer_status(ec_dev, msg); +	if (ret < 0) +		goto error; + +	if (insize) +		memcpy(in, msg->data, insize); +	ret = 0; +error: +	kfree(msg);  	return ret;  } -/* - * Wrapper for EC command without response. - */ -static int ec_command_no_resp(struct snd_soc_component *component, -			      struct ec_param_codec_i2s *param) +static int calculate_sha256(struct cros_ec_codec_priv *priv, +			    uint8_t *buf, uint32_t size, uint8_t *digest)  { -	struct cros_ec_codec_data *codec_data = +	struct crypto_shash *tfm; + +	tfm = crypto_alloc_shash("sha256", CRYPTO_ALG_TYPE_SHASH, 0); +	if (IS_ERR(tfm)) { +		dev_err(priv->dev, "can't alloc shash\n"); +		return PTR_ERR(tfm); +	} + +	{ +		SHASH_DESC_ON_STACK(desc, tfm); + +		desc->tfm = tfm; + +		crypto_shash_digest(desc, buf, size, digest); +		shash_desc_zero(desc); +	} + +	crypto_free_shash(tfm); + +#ifdef DEBUG +	{ +		char digest_str[65]; + +		bin2hex(digest_str, digest, 32); +		digest_str[64] = 0; +		dev_dbg(priv->dev, "hash=%s\n", digest_str); +	} +#endif + +	return 0; +} + +static int dmic_get_gain(struct snd_kcontrol *kcontrol, +			 struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *component = +		snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv =  		snd_soc_component_get_drvdata(component); -	struct cros_ec_device *ec_device = codec_data->ec_device; -	u8 buffer[sizeof(struct cros_ec_command) + -		  sizeof(struct ec_param_codec_i2s)]; -	struct cros_ec_command *msg = (struct cros_ec_command *)&buffer; +	struct ec_param_ec_codec_dmic p; +	struct ec_response_ec_codec_dmic_get_gain_idx r; +	int ret; -	msg->version = 0; -	msg->command = EC_CMD_CODEC_I2S; -	msg->outsize = sizeof(struct ec_param_codec_i2s); -	msg->insize = 0; +	p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; +	p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) +		return ret; +	ucontrol->value.integer.value[0] = r.gain; -	memcpy(msg->data, param, msg->outsize); +	p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; +	p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) +		return ret; +	ucontrol->value.integer.value[1] = r.gain; -	return cros_ec_cmd_xfer_status(ec_device, msg); +	return 0;  } -static int set_i2s_config(struct snd_soc_component *component, -			  enum ec_i2s_config i2s_config) +static int dmic_put_gain(struct snd_kcontrol *kcontrol, +			 struct snd_ctl_elem_value *ucontrol)  { -	struct ec_param_codec_i2s param; +	struct snd_soc_component *component = +		snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct soc_mixer_control *control = +		(struct soc_mixer_control *)kcontrol->private_value; +	int max_dmic_gain = control->max; +	int left = ucontrol->value.integer.value[0]; +	int right = ucontrol->value.integer.value[1]; +	struct ec_param_ec_codec_dmic p; +	int ret; + +	if (left > max_dmic_gain || right > max_dmic_gain) +		return -EINVAL; -	dev_dbg(component->dev, "%s set I2S format to %u\n", __func__, -		i2s_config); +	dev_dbg(component->dev, "set mic gain to %u, %u\n", left, right); -	param.cmd = EC_CODEC_I2S_SET_CONFIG; -	param.i2s_config = i2s_config; +	p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; +	p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; +	p.set_gain_idx_param.gain = left; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret < 0) +		return ret; + +	p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; +	p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; +	p.set_gain_idx_param.gain = right; +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static const DECLARE_TLV_DB_SCALE(dmic_gain_tlv, 0, 100, 0); + +enum { +	DMIC_CTL_GAIN = 0, +}; + +static struct snd_kcontrol_new dmic_controls[] = { +	[DMIC_CTL_GAIN] = +		SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, +				   0, 0, 0, dmic_get_gain, dmic_put_gain, +				   dmic_gain_tlv), +}; + +static int dmic_probe(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct device *dev = priv->dev; +	struct soc_mixer_control *control; +	struct ec_param_ec_codec_dmic p; +	struct ec_response_ec_codec_dmic_get_max_gain r; +	int ret; + +	if (!atomic_add_unless(&priv->dmic_probed, 1, 1)) +		return 0; + +	p.cmd = EC_CODEC_DMIC_GET_MAX_GAIN; + +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret < 0) { +		dev_warn(dev, "get_max_gain() unsupported\n"); +		return 0; +	} + +	dev_dbg(dev, "max gain = %d\n", r.max_gain); + +	control = (struct soc_mixer_control *) +		dmic_controls[DMIC_CTL_GAIN].private_value; +	control->max = r.max_gain; +	control->platform_max = r.max_gain; -	return ec_command_no_resp(component, ¶m); +	return snd_soc_add_component_controls(component, +			&dmic_controls[DMIC_CTL_GAIN], 1);  } -static int cros_ec_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +static int i2s_rx_hw_params(struct snd_pcm_substream *substream, +			    struct snd_pcm_hw_params *params, +			    struct snd_soc_dai *dai)  {  	struct snd_soc_component *component = dai->component; -	enum ec_i2s_config i2s_config; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; +	enum ec_codec_i2s_rx_sample_depth depth; +	int ret; + +	if (params_rate(params) != 48000) +		return -EINVAL; + +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24; +		break; +	default: +		return -EINVAL; +	} + +	dev_dbg(component->dev, "set depth to %u\n", depth); + +	p.cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH; +	p.set_sample_depth_param.depth = depth; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret < 0) +		return ret; + +	dev_dbg(component->dev, "set bclk to %u\n", +		snd_soc_params_to_bclk(params)); + +	p.cmd = EC_CODEC_I2S_RX_SET_BCLK; +	p.set_bclk_param.bclk = snd_soc_params_to_bclk(params); +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	struct snd_soc_component *component = dai->component; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; +	enum ec_codec_i2s_rx_daifmt daifmt;  	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {  	case SND_SOC_DAIFMT_CBS_CFS: @@ -121,300 +316,727 @@ static int cros_ec_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {  	case SND_SOC_DAIFMT_I2S: -		i2s_config = EC_DAI_FMT_I2S; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S;  		break; -  	case SND_SOC_DAIFMT_RIGHT_J: -		i2s_config = EC_DAI_FMT_RIGHT_J; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J;  		break; -  	case SND_SOC_DAIFMT_LEFT_J: -		i2s_config = EC_DAI_FMT_LEFT_J; +		daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J;  		break; +	default: +		return -EINVAL; +	} -	case SND_SOC_DAIFMT_DSP_A: -		i2s_config = EC_DAI_FMT_PCM_A; -		break; +	dev_dbg(component->dev, "set format to %u\n", daifmt); -	case SND_SOC_DAIFMT_DSP_B: -		i2s_config = EC_DAI_FMT_PCM_B; -		break; +	p.cmd = EC_CODEC_I2S_RX_SET_DAIFMT; +	p.set_daifmt_param.daifmt = daifmt; +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static const struct snd_soc_dai_ops i2s_rx_dai_ops = { +	.hw_params = i2s_rx_hw_params, +	.set_fmt = i2s_rx_set_fmt, +}; +static int i2s_rx_event(struct snd_soc_dapm_widget *w, +			struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_component *component = +		snd_soc_dapm_to_component(w->dapm); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_i2s_rx p; + +	switch (event) { +	case SND_SOC_DAPM_PRE_PMU: +		dev_dbg(component->dev, "enable I2S RX\n"); +		p.cmd = EC_CODEC_I2S_RX_ENABLE; +		break; +	case SND_SOC_DAPM_PRE_PMD: +		dev_dbg(component->dev, "disable I2S RX\n"); +		p.cmd = EC_CODEC_I2S_RX_DISABLE; +		break;  	default: -		return -EINVAL; +		return 0;  	} -	return set_i2s_config(component, i2s_config); +	return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, +				    (uint8_t *)&p, sizeof(p), NULL, 0); +} + +static struct snd_soc_dapm_widget i2s_rx_dapm_widgets[] = { +	SND_SOC_DAPM_INPUT("DMIC"), +	SND_SOC_DAPM_SUPPLY("I2S RX Enable", SND_SOC_NOPM, 0, 0, i2s_rx_event, +			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +	SND_SOC_DAPM_AIF_OUT("I2S RX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static struct snd_soc_dapm_route i2s_rx_dapm_routes[] = { +	{"I2S RX", NULL, "DMIC"}, +	{"I2S RX", NULL, "I2S RX Enable"}, +}; + +static struct snd_soc_dai_driver i2s_rx_dai_driver = { +	.name = "EC Codec I2S RX", +	.capture = { +		.stream_name = "I2S Capture", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			SNDRV_PCM_FMTBIT_S24_LE, +	}, +	.ops = &i2s_rx_dai_ops, +}; + +static int i2s_rx_probe(struct snd_soc_component *component) +{ +	return dmic_probe(component);  } -static int set_i2s_sample_depth(struct snd_soc_component *component, -				enum ec_sample_depth_value depth) +static const struct snd_soc_component_driver i2s_rx_component_driver = { +	.probe			= i2s_rx_probe, +	.dapm_widgets		= i2s_rx_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(i2s_rx_dapm_widgets), +	.dapm_routes		= i2s_rx_dapm_routes, +	.num_dapm_routes	= ARRAY_SIZE(i2s_rx_dapm_routes), +}; + +static void *wov_map_shm(struct cros_ec_codec_priv *priv, +			 uint8_t shm_id, uint32_t *len, uint8_t *type)  { -	struct ec_param_codec_i2s param; +	struct ec_param_ec_codec p; +	struct ec_response_ec_codec_get_shm_addr r; +	uint32_t req, offset; + +	p.cmd = EC_CODEC_GET_SHM_ADDR; +	p.get_shm_addr_param.shm_id = shm_id; +	if (send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +				 (uint8_t *)&p, sizeof(p), +				 (uint8_t *)&r, sizeof(r)) < 0) { +		dev_err(priv->dev, "failed to EC_CODEC_GET_SHM_ADDR\n"); +		return NULL; +	} -	dev_dbg(component->dev, "%s set depth to %u\n", __func__, depth); +	dev_dbg(priv->dev, "phys_addr=%#llx, len=%#x\n", r.phys_addr, r.len); + +	*len = r.len; +	*type = r.type; + +	switch (r.type) { +	case EC_CODEC_SHM_TYPE_EC_RAM: +		return (void __force *)devm_ioremap_wc(priv->dev, +				r.phys_addr + priv->ec_shm_addr, r.len); +	case EC_CODEC_SHM_TYPE_SYSTEM_RAM: +		if (r.phys_addr) { +			dev_err(priv->dev, "unknown status\n"); +			return NULL; +		} + +		req = round_up(r.len, PAGE_SIZE); +		dev_dbg(priv->dev, "round up from %u to %u\n", r.len, req); + +		if (priv->ap_shm_last_alloc + req > +		    priv->ap_shm_phys_addr + priv->ap_shm_len) { +			dev_err(priv->dev, "insufficient space for AP SHM\n"); +			return NULL; +		} + +		dev_dbg(priv->dev, "alloc AP SHM addr=%#llx, len=%#x\n", +			priv->ap_shm_last_alloc, req); + +		p.cmd = EC_CODEC_SET_SHM_ADDR; +		p.set_shm_addr_param.phys_addr = priv->ap_shm_last_alloc; +		p.set_shm_addr_param.len = req; +		p.set_shm_addr_param.shm_id = shm_id; +		if (send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +					 (uint8_t *)&p, sizeof(p), +					 NULL, 0) < 0) { +			dev_err(priv->dev, "failed to EC_CODEC_SET_SHM_ADDR\n"); +			return NULL; +		} + +		/* +		 * Note: EC codec only requests for `r.len' but we allocate +		 * round up PAGE_SIZE `req'. +		 */ +		offset = priv->ap_shm_last_alloc - priv->ap_shm_phys_addr; +		priv->ap_shm_last_alloc += req; + +		return (void *)(uintptr_t)(priv->ap_shm_addr + offset); +	default: +		return NULL; +	} +} -	param.cmd = EC_CODEC_SET_SAMPLE_DEPTH; -	param.depth = depth; +static bool wov_queue_full(struct cros_ec_codec_priv *priv) +{ +	return ((priv->wov_wp + 1) % sizeof(priv->wov_buf)) == priv->wov_rp; +} -	return ec_command_no_resp(component, ¶m); +static size_t wov_queue_size(struct cros_ec_codec_priv *priv) +{ +	if (priv->wov_wp >= priv->wov_rp) +		return priv->wov_wp - priv->wov_rp; +	else +		return sizeof(priv->wov_buf) - priv->wov_rp + priv->wov_wp;  } -static int set_i2s_bclk(struct snd_soc_component *component, uint32_t bclk) +static void wov_queue_dequeue(struct cros_ec_codec_priv *priv, size_t len)  { -	struct ec_param_codec_i2s param; +	struct snd_pcm_runtime *runtime = priv->wov_substream->runtime; +	size_t req; + +	while (len) { +		req = min(len, runtime->dma_bytes - priv->wov_dma_offset); +		if (priv->wov_wp >= priv->wov_rp) +			req = min(req, (size_t)priv->wov_wp - priv->wov_rp); +		else +			req = min(req, sizeof(priv->wov_buf) - priv->wov_rp); -	dev_dbg(component->dev, "%s set i2s bclk to %u\n", __func__, bclk); +		memcpy(runtime->dma_area + priv->wov_dma_offset, +		       priv->wov_buf + priv->wov_rp, req); -	param.cmd = EC_CODEC_I2S_SET_BCLK; -	param.bclk = bclk; +		priv->wov_dma_offset += req; +		if (priv->wov_dma_offset == runtime->dma_bytes) +			priv->wov_dma_offset = 0; -	return ec_command_no_resp(component, ¶m); +		priv->wov_rp += req; +		if (priv->wov_rp == sizeof(priv->wov_buf)) +			priv->wov_rp = 0; + +		len -= req; +	} + +	snd_pcm_period_elapsed(priv->wov_substream);  } -static int cros_ec_i2s_hw_params(struct snd_pcm_substream *substream, -				 struct snd_pcm_hw_params *params, -				 struct snd_soc_dai *dai) +static void wov_queue_try_dequeue(struct cros_ec_codec_priv *priv)  { -	struct snd_soc_component *component = dai->component; -	unsigned int rate, bclk; -	int ret; +	size_t period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); -	rate = params_rate(params); -	if (rate != 48000) -		return -EINVAL; +	while (period_bytes && wov_queue_size(priv) >= period_bytes) { +		wov_queue_dequeue(priv, period_bytes); +		period_bytes = snd_pcm_lib_period_bytes(priv->wov_substream); +	} +} -	switch (params_format(params)) { -	case SNDRV_PCM_FORMAT_S16_LE: -		ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_16); -		break; -	case SNDRV_PCM_FORMAT_S24_LE: -		ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_24); -		break; -	default: -		return -EINVAL; +static void wov_queue_enqueue(struct cros_ec_codec_priv *priv, +			      uint8_t *addr, size_t len, bool iomem) +{ +	size_t req; + +	while (len) { +		if (wov_queue_full(priv)) { +			wov_queue_try_dequeue(priv); + +			if (wov_queue_full(priv)) { +				dev_err(priv->dev, "overrun detected\n"); +				return; +			} +		} + +		if (priv->wov_wp >= priv->wov_rp) +			req = sizeof(priv->wov_buf) - priv->wov_wp; +		else +			/* Note: waste 1-byte to differentiate full and empty */ +			req = priv->wov_rp - priv->wov_wp - 1; +		req = min(req, len); + +		if (iomem) +			memcpy_fromio(priv->wov_buf + priv->wov_wp, +				      (void __force __iomem *)addr, req); +		else +			memcpy(priv->wov_buf + priv->wov_wp, addr, req); + +		priv->wov_wp += req; +		if (priv->wov_wp == sizeof(priv->wov_buf)) +			priv->wov_wp = 0; + +		addr += req; +		len -= req;  	} -	if (ret < 0) + +	wov_queue_try_dequeue(priv); +} + +static int wov_read_audio_shm(struct cros_ec_codec_priv *priv) +{ +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_read_audio_shm r; +	int ret; + +	p.cmd = EC_CODEC_WOV_READ_AUDIO_SHM; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) { +		dev_err(priv->dev, "failed to EC_CODEC_WOV_READ_AUDIO_SHM\n");  		return ret; +	} -	bclk = snd_soc_params_to_bclk(params); -	return set_i2s_bclk(component, bclk); +	if (!r.len) +		dev_dbg(priv->dev, "no data, sleep\n"); +	else +		wov_queue_enqueue(priv, priv->wov_audio_shm_p + r.offset, r.len, +			priv->wov_audio_shm_type == EC_CODEC_SHM_TYPE_EC_RAM); +	return -EAGAIN;  } -static const struct snd_soc_dai_ops cros_ec_i2s_dai_ops = { -	.hw_params = cros_ec_i2s_hw_params, -	.set_fmt = cros_ec_i2s_set_dai_fmt, -}; +static int wov_read_audio(struct cros_ec_codec_priv *priv) +{ +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_read_audio r; +	int remain = priv->wov_burst_read ? 16000 : 320; +	int ret; -static struct snd_soc_dai_driver cros_ec_dai[] = { -	{ -		.name = "cros_ec_codec I2S", -		.id = 0, -		.capture = { -			.stream_name = "I2S Capture", -			.channels_min = 2, -			.channels_max = 2, -			.rates = SNDRV_PCM_RATE_48000, -			.formats = SNDRV_PCM_FMTBIT_S16_LE | -				   SNDRV_PCM_FMTBIT_S24_LE, -		}, -		.ops = &cros_ec_i2s_dai_ops, +	while (remain >= 0) { +		p.cmd = EC_CODEC_WOV_READ_AUDIO; +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), +					   (uint8_t *)&r, sizeof(r)); +		if (ret) { +			dev_err(priv->dev, +				"failed to EC_CODEC_WOV_READ_AUDIO\n"); +			return ret; +		} + +		if (!r.len) { +			dev_dbg(priv->dev, "no data, sleep\n"); +			priv->wov_burst_read = false; +			break; +		} + +		wov_queue_enqueue(priv, r.buf, r.len, false); +		remain -= r.len;  	} -}; -static int get_ec_mic_gain(struct snd_soc_component *component, -			   u8 *left, u8 *right) +	return -EAGAIN; +} + +static void wov_copy_work(struct work_struct *w)  { -	struct ec_param_codec_i2s param; -	struct ec_codec_i2s_gain resp; +	struct cros_ec_codec_priv *priv = +		container_of(w, struct cros_ec_codec_priv, wov_copy_work.work);  	int ret; -	param.cmd = EC_CODEC_GET_GAIN; +	mutex_lock(&priv->wov_dma_lock); +	if (!priv->wov_substream) { +		dev_warn(priv->dev, "no pcm substream\n"); +		goto leave; +	} -	ret = ec_command_get_gain(component, ¶m, &resp); -	if (ret < 0) -		return ret; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +		ret = wov_read_audio_shm(priv); +	else +		ret = wov_read_audio(priv); + +	if (ret == -EAGAIN) +		schedule_delayed_work(&priv->wov_copy_work, +				      msecs_to_jiffies(10)); +	else if (ret) +		dev_err(priv->dev, "failed to read audio data\n"); +leave: +	mutex_unlock(&priv->wov_dma_lock); +} -	*left = resp.left; -	*right = resp.right; +static int wov_enable_get(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = snd_soc_component_get_drvdata(c); +	ucontrol->value.integer.value[0] = priv->wov_enabled;  	return 0;  } -static int mic_gain_get(struct snd_kcontrol *kcontrol, -			struct snd_ctl_elem_value *ucontrol) +static int wov_enable_put(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol)  { -	struct snd_soc_component *component = -		snd_soc_kcontrol_component(kcontrol); -	u8 left, right; +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct cros_ec_codec_priv *priv = snd_soc_component_get_drvdata(c); +	int enabled = ucontrol->value.integer.value[0]; +	struct ec_param_ec_codec_wov p;  	int ret; -	ret = get_ec_mic_gain(component, &left, &right); -	if (ret) -		return ret; - -	ucontrol->value.integer.value[0] = left; -	ucontrol->value.integer.value[1] = right; +	if (priv->wov_enabled != enabled) { +		if (enabled) +			p.cmd = EC_CODEC_WOV_ENABLE; +		else +			p.cmd = EC_CODEC_WOV_DISABLE; + +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), NULL, 0); +		if (ret) { +			dev_err(priv->dev, "failed to %s wov\n", +				enabled ? "enable" : "disable"); +			return ret; +		} + +		priv->wov_enabled = enabled; +	}  	return 0;  } -static int set_ec_mic_gain(struct snd_soc_component *component, -			   u8 left, u8 right) +static int wov_set_lang_shm(struct cros_ec_codec_priv *priv, +			    uint8_t *buf, size_t size, uint8_t *digest)  { -	struct ec_param_codec_i2s param; +	struct ec_param_ec_codec_wov p; +	struct ec_param_ec_codec_wov_set_lang_shm *pp = &p.set_lang_shm_param; +	int ret; -	dev_dbg(component->dev, "%s set mic gain to %u, %u\n", -		__func__, left, right); +	if (size > priv->wov_lang_shm_len) { +		dev_err(priv->dev, "no enough SHM size: %d\n", +			priv->wov_lang_shm_len); +		return -EIO; +	} -	param.cmd = EC_CODEC_SET_GAIN; -	param.gain.left = left; -	param.gain.right = right; +	switch (priv->wov_lang_shm_type) { +	case EC_CODEC_SHM_TYPE_EC_RAM: +		memcpy_toio((void __force __iomem *)priv->wov_lang_shm_p, +			    buf, size); +		memset_io((void __force __iomem *)priv->wov_lang_shm_p + size, +			  0, priv->wov_lang_shm_len - size); +		break; +	case EC_CODEC_SHM_TYPE_SYSTEM_RAM: +		memcpy(priv->wov_lang_shm_p, buf, size); +		memset(priv->wov_lang_shm_p + size, 0, +		       priv->wov_lang_shm_len - size); -	return ec_command_no_resp(component, ¶m); +		/* make sure write to memory before calling host command */ +		wmb(); +		break; +	} + +	p.cmd = EC_CODEC_WOV_SET_LANG_SHM; +	memcpy(pp->hash, digest, SHA256_DIGEST_SIZE); +	pp->total_len = size; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), NULL, 0); +	if (ret) { +		dev_err(priv->dev, "failed to EC_CODEC_WOV_SET_LANG_SHM\n"); +		return ret; +	} + +	return 0;  } -static int mic_gain_put(struct snd_kcontrol *kcontrol, -			struct snd_ctl_elem_value *ucontrol) +static int wov_set_lang(struct cros_ec_codec_priv *priv, +			uint8_t *buf, size_t size, uint8_t *digest)  { -	struct snd_soc_component *component = -		snd_soc_kcontrol_component(kcontrol); -	struct cros_ec_codec_data *codec_data = -		snd_soc_component_get_drvdata(component); -	int left = ucontrol->value.integer.value[0]; -	int right = ucontrol->value.integer.value[1]; -	unsigned int max_dmic_gain = codec_data->max_dmic_gain; +	struct ec_param_ec_codec_wov p; +	struct ec_param_ec_codec_wov_set_lang *pp = &p.set_lang_param; +	size_t i, req; +	int ret; -	if (left > max_dmic_gain || right > max_dmic_gain) -		return -EINVAL; +	for (i = 0; i < size; i += req) { +		req = min(size - i, ARRAY_SIZE(pp->buf)); + +		p.cmd = EC_CODEC_WOV_SET_LANG; +		memcpy(pp->hash, digest, SHA256_DIGEST_SIZE); +		pp->total_len = size; +		pp->offset = i; +		memcpy(pp->buf, buf + i, req); +		pp->len = req; +		ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +					   (uint8_t *)&p, sizeof(p), NULL, 0); +		if (ret) { +			dev_err(priv->dev, "failed to EC_CODEC_WOV_SET_LANG\n"); +			return ret; +		} +	} -	return set_ec_mic_gain(component, (u8)left, (u8)right); +	return 0;  } -static struct snd_kcontrol_new mic_gain_control = -	SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, 0, 0, 0, -			   mic_gain_get, mic_gain_put, ec_mic_gain_tlv); - -static int enable_i2s(struct snd_soc_component *component, int enable) +static int wov_hotword_model_put(struct snd_kcontrol *kcontrol, +				 const unsigned int __user *bytes, +				 unsigned int size)  { -	struct ec_param_codec_i2s param; +	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	struct ec_param_ec_codec_wov p; +	struct ec_response_ec_codec_wov_get_lang r; +	uint8_t digest[SHA256_DIGEST_SIZE]; +	uint8_t *buf; +	int ret; + +	/* Skips the TLV header. */ +	bytes += 2; +	size -= 8; + +	dev_dbg(priv->dev, "%s: size=%d\n", __func__, size); + +	buf = memdup_user(bytes, size); +	if (IS_ERR(buf)) +		return PTR_ERR(buf); + +	ret = calculate_sha256(priv, buf, size, digest); +	if (ret) +		goto leave; + +	p.cmd = EC_CODEC_WOV_GET_LANG; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_WOV, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) +		goto leave; -	dev_dbg(component->dev, "%s set i2s to %u\n", __func__, enable); +	if (memcmp(digest, r.hash, SHA256_DIGEST_SIZE) == 0) { +		dev_dbg(priv->dev, "not updated"); +		goto leave; +	} -	param.cmd = EC_CODEC_I2S_ENABLE; -	param.i2s_enable = enable; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_LANG_SHM)) +		ret = wov_set_lang_shm(priv, buf, size, digest); +	else +		ret = wov_set_lang(priv, buf, size, digest); -	return ec_command_no_resp(component, ¶m); +leave: +	kfree(buf); +	return ret;  } -static int cros_ec_i2s_enable_event(struct snd_soc_dapm_widget *w, -				    struct snd_kcontrol *kcontrol, int event) +static struct snd_kcontrol_new wov_controls[] = { +	SOC_SINGLE_BOOL_EXT("Wake-on-Voice Switch", 0, +			    wov_enable_get, wov_enable_put), +	SND_SOC_BYTES_TLV("Hotword Model", 0x11000, NULL, +			  wov_hotword_model_put), +}; + +static struct snd_soc_dai_driver wov_dai_driver = { +	.name = "Wake on Voice", +	.capture = { +		.stream_name = "WoV Capture", +		.channels_min = 1, +		.channels_max = 1, +		.rates = SNDRV_PCM_RATE_16000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +}; + +static int wov_host_event(struct notifier_block *nb, +			  unsigned long queued_during_suspend, void *notify)  { -	struct snd_soc_component *component = -		snd_soc_dapm_to_component(w->dapm); +	struct cros_ec_codec_priv *priv = +		container_of(nb, struct cros_ec_codec_priv, wov_notifier); +	u32 host_event; + +	dev_dbg(priv->dev, "%s\n", __func__); + +	host_event = cros_ec_get_host_event(priv->ec_device); +	if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_WOV)) { +		schedule_delayed_work(&priv->wov_copy_work, 0); +		return NOTIFY_OK; +	} else { +		return NOTIFY_DONE; +	} +} -	switch (event) { -	case SND_SOC_DAPM_PRE_PMU: -		dev_dbg(component->dev, -			"%s got SND_SOC_DAPM_PRE_PMU event\n", __func__); -		return enable_i2s(component, 1); +static int wov_probe(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); +	int ret; -	case SND_SOC_DAPM_PRE_PMD: -		dev_dbg(component->dev, -			"%s got SND_SOC_DAPM_PRE_PMD event\n", __func__); -		return enable_i2s(component, 0); +	mutex_init(&priv->wov_dma_lock); +	INIT_DELAYED_WORK(&priv->wov_copy_work, wov_copy_work); + +	priv->wov_notifier.notifier_call = wov_host_event; +	ret = blocking_notifier_chain_register( +			&priv->ec_device->event_notifier, &priv->wov_notifier); +	if (ret) +		return ret; + +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_LANG_SHM)) { +		priv->wov_lang_shm_p = wov_map_shm(priv, +				EC_CODEC_SHM_ID_WOV_LANG, +				&priv->wov_lang_shm_len, +				&priv->wov_lang_shm_type); +		if (!priv->wov_lang_shm_p) +			return -EFAULT;  	} -	return 0; +	if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) { +		priv->wov_audio_shm_p = wov_map_shm(priv, +				EC_CODEC_SHM_ID_WOV_AUDIO, +				&priv->wov_audio_shm_len, +				&priv->wov_audio_shm_type); +		if (!priv->wov_audio_shm_p) +			return -EFAULT; +	} + +	return dmic_probe(component);  } -/* - * The goal of this DAPM route is to turn on/off I2S using EC - * host command when capture stream is started/stopped. - */ -static const struct snd_soc_dapm_widget cros_ec_codec_dapm_widgets[] = { -	SND_SOC_DAPM_INPUT("DMIC"), +static void wov_remove(struct snd_soc_component *component) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -	/* -	 * Control EC to enable/disable I2S. -	 */ -	SND_SOC_DAPM_SUPPLY("I2S Enable", SND_SOC_NOPM, -			    0, 0, cros_ec_i2s_enable_event, -			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +	blocking_notifier_chain_unregister( +			&priv->ec_device->event_notifier, &priv->wov_notifier); +} -	SND_SOC_DAPM_AIF_OUT("I2STX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), -}; +static int wov_pcm_open(struct snd_soc_component *component, +			struct snd_pcm_substream *substream) +{ +	static const struct snd_pcm_hardware hw_param = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_MMAP_VALID, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +		.rates = SNDRV_PCM_RATE_16000, +		.channels_min = 1, +		.channels_max = 1, +		.period_bytes_min = PAGE_SIZE, +		.period_bytes_max = 0x20000 / 8, +		.periods_min = 8, +		.periods_max = 8, +		.buffer_bytes_max = 0x20000, +	}; + +	return snd_soc_set_runtime_hwparams(substream, &hw_param); +} -static const struct snd_soc_dapm_route cros_ec_codec_dapm_routes[] = { -	{ "I2STX", NULL, "DMIC" }, -	{ "I2STX", NULL, "I2S Enable" }, -}; +static int wov_pcm_hw_params(struct snd_soc_component *component, +			     struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *hw_params) +{ +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -/* - * Read maximum gain from device property and set it to mixer control. - */ -static int cros_ec_set_gain_range(struct device *dev) +	mutex_lock(&priv->wov_dma_lock); +	priv->wov_substream = substream; +	priv->wov_rp = priv->wov_wp = 0; +	priv->wov_dma_offset = 0; +	priv->wov_burst_read = true; +	mutex_unlock(&priv->wov_dma_lock); + +	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int wov_pcm_hw_free(struct snd_soc_component *component, +			   struct snd_pcm_substream *substream)  { -	struct soc_mixer_control *control; -	struct cros_ec_codec_data *codec_data = dev_get_drvdata(dev); -	int rc; +	struct cros_ec_codec_priv *priv = +		snd_soc_component_get_drvdata(component); -	rc = device_property_read_u32(dev, "max-dmic-gain", -				      &codec_data->max_dmic_gain); -	if (rc) -		return rc; +	mutex_lock(&priv->wov_dma_lock); +	wov_queue_dequeue(priv, wov_queue_size(priv)); +	priv->wov_substream = NULL; +	mutex_unlock(&priv->wov_dma_lock); -	control = (struct soc_mixer_control *) -				mic_gain_control.private_value; -	control->max = codec_data->max_dmic_gain; -	control->platform_max = codec_data->max_dmic_gain; +	cancel_delayed_work_sync(&priv->wov_copy_work); -	return 0; +	return snd_pcm_lib_free_pages(substream);  } -static int cros_ec_codec_probe(struct snd_soc_component *component) +static snd_pcm_uframes_t wov_pcm_pointer(struct snd_soc_component *component, +					 struct snd_pcm_substream *substream)  { -	int rc; - -	struct cros_ec_codec_data *codec_data = +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct cros_ec_codec_priv *priv =  		snd_soc_component_get_drvdata(component); -	rc = cros_ec_set_gain_range(codec_data->dev); -	if (rc) -		return rc; +	return bytes_to_frames(runtime, priv->wov_dma_offset); +} -	return snd_soc_add_component_controls(component, &mic_gain_control, 1); +static int wov_pcm_new(struct snd_soc_component *component, +		       struct snd_soc_pcm_runtime *rtd) +{ +	snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, +					      NULL, 0, 0); +	return 0;  } -static const struct snd_soc_component_driver cros_ec_component_driver = { -	.probe			= cros_ec_codec_probe, -	.dapm_widgets		= cros_ec_codec_dapm_widgets, -	.num_dapm_widgets	= ARRAY_SIZE(cros_ec_codec_dapm_widgets), -	.dapm_routes		= cros_ec_codec_dapm_routes, -	.num_dapm_routes	= ARRAY_SIZE(cros_ec_codec_dapm_routes), +static const struct snd_soc_component_driver wov_component_driver = { +	.probe		= wov_probe, +	.remove		= wov_remove, +	.controls	= wov_controls, +	.num_controls	= ARRAY_SIZE(wov_controls), +	.open		= wov_pcm_open, +	.hw_params	= wov_pcm_hw_params, +	.hw_free	= wov_pcm_hw_free, +	.pointer	= wov_pcm_pointer, +	.pcm_construct	= wov_pcm_new,  }; -/* - * Platform device and platform driver fro cros-ec-codec. - */ -static int cros_ec_codec_platform_probe(struct platform_device *pd) +static int cros_ec_codec_platform_probe(struct platform_device *pdev)  { -	struct device *dev = &pd->dev; -	struct cros_ec_device *ec_device = dev_get_drvdata(pd->dev.parent); -	struct cros_ec_codec_data *codec_data; +	struct device *dev = &pdev->dev; +	struct cros_ec_device *ec_device = dev_get_drvdata(pdev->dev.parent); +	struct cros_ec_codec_priv *priv; +	struct ec_param_ec_codec p; +	struct ec_response_ec_codec_get_capabilities r; +	int ret; +#ifdef CONFIG_OF +	struct device_node *node; +	struct resource res; +	u64 ec_shm_size; +	const __be32 *regaddr_p; +#endif -	codec_data = devm_kzalloc(dev, sizeof(struct cros_ec_codec_data), -				  GFP_KERNEL); -	if (!codec_data) +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +	if (!priv)  		return -ENOMEM; -	codec_data->dev = dev; -	codec_data->ec_device = ec_device; +#ifdef CONFIG_OF +	regaddr_p = of_get_address(dev->of_node, 0, &ec_shm_size, NULL); +	if (regaddr_p) { +		priv->ec_shm_addr = of_read_number(regaddr_p, 2); +		priv->ec_shm_len = ec_shm_size; -	platform_set_drvdata(pd, codec_data); +		dev_dbg(dev, "ec_shm_addr=%#llx len=%#x\n", +			priv->ec_shm_addr, priv->ec_shm_len); +	} + +	node = of_parse_phandle(dev->of_node, "memory-region", 0); +	if (node) { +		ret = of_address_to_resource(node, 0, &res); +		if (!ret) { +			priv->ap_shm_phys_addr = res.start; +			priv->ap_shm_len = resource_size(&res); +			priv->ap_shm_addr = +				(uint64_t)(uintptr_t)devm_ioremap_wc( +					dev, priv->ap_shm_phys_addr, +					priv->ap_shm_len); +			priv->ap_shm_last_alloc = priv->ap_shm_phys_addr; + +			dev_dbg(dev, "ap_shm_phys_addr=%#llx len=%#x\n", +				priv->ap_shm_phys_addr, priv->ap_shm_len); +		} +	} +#endif + +	priv->dev = dev; +	priv->ec_device = ec_device; +	atomic_set(&priv->dmic_probed, 0); + +	p.cmd = EC_CODEC_GET_CAPABILITIES; +	ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC, +				   (uint8_t *)&p, sizeof(p), +				   (uint8_t *)&r, sizeof(r)); +	if (ret) { +		dev_err(dev, "failed to EC_CODEC_GET_CAPABILITIES\n"); +		return ret; +	} +	priv->ec_capabilities = r.capabilities; + +	platform_set_drvdata(pdev, priv); + +	ret = devm_snd_soc_register_component(dev, &i2s_rx_component_driver, +					      &i2s_rx_dai_driver, 1); +	if (ret) +		return ret; -	return devm_snd_soc_register_component(dev, &cros_ec_component_driver, -					  cros_ec_dai, ARRAY_SIZE(cros_ec_dai)); +	return devm_snd_soc_register_component(dev, &wov_component_driver, +					       &wov_dai_driver, 1);  }  #ifdef CONFIG_OF @@ -427,7 +1049,7 @@ MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match);  static struct platform_driver cros_ec_codec_platform_driver = {  	.driver = { -		.name = DRV_NAME, +		.name = "cros-ec-codec",  		.of_match_table = of_match_ptr(cros_ec_codec_of_match),  	},  	.probe = cros_ec_codec_platform_probe, @@ -438,4 +1060,4 @@ module_platform_driver(cros_ec_codec_platform_driver);  MODULE_LICENSE("GPL v2");  MODULE_DESCRIPTION("ChromeOS EC codec driver");  MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>"); -MODULE_ALIAS("platform:" DRV_NAME); +MODULE_ALIAS("platform:cros-ec-codec"); diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c index 1c1ba7bea4d8..2ad00ed21bec 100644 --- a/sound/soc/codecs/cx2072x.c +++ b/sound/soc/codecs/cx2072x.c @@ -1507,7 +1507,7 @@ static int cx2072x_probe(struct snd_soc_component *codec)  	regmap_multi_reg_write(cx2072x->regmap, cx2072x_reg_init,  			       ARRAY_SIZE(cx2072x_reg_init)); -	/* configre PortC as input device */ +	/* configure PortC as input device */  	regmap_update_bits(cx2072x->regmap, CX2072X_PORTC_PIN_CTRL,  			   0x20, 0x20); diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index 4570f662fb48..6803d39e09a5 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -14,13 +14,11 @@  #include <sound/pcm_params.h>  #include <sound/soc.h>  #include <sound/hdaudio_ext.h> +#include <sound/hda_i915.h>  #include <sound/hda_codec.h>  #include <sound/hda_register.h> -#include "hdac_hda.h" -#define HDAC_ANALOG_DAI_ID		0 -#define HDAC_DIGITAL_DAI_ID		1 -#define HDAC_ALT_ANALOG_DAI_ID		2 +#include "hdac_hda.h"  #define STUB_FORMATS	(SNDRV_PCM_FMTBIT_S8 | \  			SNDRV_PCM_FMTBIT_U8 | \ @@ -32,6 +30,11 @@  			SNDRV_PCM_FMTBIT_U32_LE | \  			SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) +#define STUB_HDMI_RATES	(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ +				 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ +				 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ +				 SNDRV_PCM_RATE_192000) +  static int hdac_hda_dai_open(struct snd_pcm_substream *substream,  			     struct snd_soc_dai *dai);  static void hdac_hda_dai_close(struct snd_pcm_substream *substream, @@ -121,7 +124,46 @@ static struct snd_soc_dai_driver hdac_hda_dais[] = {  		.formats = STUB_FORMATS,  		.sig_bits = 24,  	}, -} +}, +{ +	.id = HDAC_HDMI_0_DAI_ID, +	.name = "intel-hdmi-hifi1", +	.ops = &hdac_hda_dai_ops, +	.playback = { +		.stream_name    = "hifi1", +		.channels_min   = 1, +		.channels_max   = 32, +		.rates          = STUB_HDMI_RATES, +		.formats        = STUB_FORMATS, +		.sig_bits = 24, +	}, +}, +{ +	.id = HDAC_HDMI_1_DAI_ID, +	.name = "intel-hdmi-hifi2", +	.ops = &hdac_hda_dai_ops, +	.playback = { +		.stream_name    = "hifi2", +		.channels_min   = 1, +		.channels_max   = 32, +		.rates          = STUB_HDMI_RATES, +		.formats        = STUB_FORMATS, +		.sig_bits = 24, +	}, +}, +{ +	.id = HDAC_HDMI_2_DAI_ID, +	.name = "intel-hdmi-hifi3", +	.ops = &hdac_hda_dai_ops, +	.playback = { +		.stream_name    = "hifi3", +		.channels_min   = 1, +		.channels_max   = 32, +		.rates          = STUB_HDMI_RATES, +		.formats        = STUB_FORMATS, +		.sig_bits = 24, +	}, +},  }; @@ -135,10 +177,11 @@ static int hdac_hda_dai_set_tdm_slot(struct snd_soc_dai *dai,  	hda_pvt = snd_soc_component_get_drvdata(component);  	pcm = &hda_pvt->pcm[dai->id]; +  	if (tx_mask) -		pcm[dai->id].stream_tag[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask; +		pcm->stream_tag[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask;  	else -		pcm[dai->id].stream_tag[SNDRV_PCM_STREAM_CAPTURE] = rx_mask; +		pcm->stream_tag[SNDRV_PCM_STREAM_CAPTURE] = rx_mask;  	return 0;  } @@ -278,6 +321,12 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,  	struct hda_pcm *cpcm;  	const char *pcm_name; +	/* +	 * map DAI ID to the closest matching PCM name, using the naming +	 * scheme used by hda-codec snd_hda_gen_build_pcms() and for +	 * HDMI in hda_codec patch_hdmi.c) +	 */ +  	switch (dai->id) {  	case HDAC_ANALOG_DAI_ID:  		pcm_name = "Analog"; @@ -288,13 +337,22 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,  	case HDAC_ALT_ANALOG_DAI_ID:  		pcm_name = "Alt Analog";  		break; +	case HDAC_HDMI_0_DAI_ID: +		pcm_name = "HDMI 0"; +		break; +	case HDAC_HDMI_1_DAI_ID: +		pcm_name = "HDMI 1"; +		break; +	case HDAC_HDMI_2_DAI_ID: +		pcm_name = "HDMI 2"; +		break;  	default:  		dev_err(&hcodec->core.dev, "invalid dai id %d\n", dai->id);  		return NULL;  	}  	list_for_each_entry(cpcm, &hcodec->pcm_list_head, list) { -		if (strpbrk(cpcm->name, pcm_name)) +		if (strstr(cpcm->name, pcm_name))  			return cpcm;  	} @@ -302,6 +360,18 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,  	return NULL;  } +static bool is_hdmi_codec(struct hda_codec *hcodec) +{ +	struct hda_pcm *cpcm; + +	list_for_each_entry(cpcm, &hcodec->pcm_list_head, list) { +		if (cpcm->pcm_type == HDA_PCM_TYPE_HDMI) +			return true; +	} + +	return false; +} +  static int hdac_hda_codec_probe(struct snd_soc_component *component)  {  	struct hdac_hda_priv *hda_pvt = @@ -322,6 +392,15 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component)  	snd_hdac_ext_bus_link_get(hdev->bus, hlink); +	/* +	 * Ensure any HDA display is powered at codec probe. +	 * After snd_hda_codec_device_new(), display power is +	 * managed by runtime PM. +	 */ +	if (hda_pvt->need_display_power) +		snd_hdac_display_power(hdev->bus, +				       HDA_CODEC_IDX_CONTROLLER, true); +  	ret = snd_hda_codec_device_new(hcodec->bus, component->card->snd_card,  				       hdev->addr, hcodec);  	if (ret < 0) { @@ -366,20 +445,31 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component)  		dev_dbg(&hdev->dev, "no patch file found\n");  	} +	/* configure codec for 1:1 PCM:DAI mapping */ +	hcodec->mst_no_extra_pcms = 1; +  	ret = snd_hda_codec_parse_pcms(hcodec);  	if (ret < 0) {  		dev_err(&hdev->dev, "unable to map pcms to dai %d\n", ret);  		goto error;  	} -	ret = snd_hda_codec_build_controls(hcodec); -	if (ret < 0) { -		dev_err(&hdev->dev, "unable to create controls %d\n", ret); -		goto error; +	/* HDMI controls need to be created in machine drivers */ +	if (!is_hdmi_codec(hcodec)) { +		ret = snd_hda_codec_build_controls(hcodec); +		if (ret < 0) { +			dev_err(&hdev->dev, "unable to create controls %d\n", +				ret); +			goto error; +		}  	}  	hcodec->core.lazy_cache = true; +	if (hda_pvt->need_display_power) +		snd_hdac_display_power(hdev->bus, +				       HDA_CODEC_IDX_CONTROLLER, false); +  	/*  	 * hdac_device core already sets the state to active and calls  	 * get_noresume. So enable runtime and set the device to suspend. diff --git a/sound/soc/codecs/hdac_hda.h b/sound/soc/codecs/hdac_hda.h index 6b1bd4f428e7..e145cec085b8 100644 --- a/sound/soc/codecs/hdac_hda.h +++ b/sound/soc/codecs/hdac_hda.h @@ -6,6 +6,16 @@  #ifndef __HDAC_HDA_H__  #define __HDAC_HDA_H__ +enum { +	HDAC_ANALOG_DAI_ID = 0, +	HDAC_DIGITAL_DAI_ID, +	HDAC_ALT_ANALOG_DAI_ID, +	HDAC_HDMI_0_DAI_ID, +	HDAC_HDMI_1_DAI_ID, +	HDAC_HDMI_2_DAI_ID, +	HDAC_LAST_DAI_ID = HDAC_HDMI_2_DAI_ID, +}; +  struct hdac_hda_pcm {  	int stream_tag[2];  	unsigned int format_val[2]; @@ -13,7 +23,8 @@ struct hdac_hda_pcm {  struct hdac_hda_priv {  	struct hda_codec codec; -	struct hdac_hda_pcm pcm[2]; +	struct hdac_hda_pcm pcm[HDAC_LAST_DAI_ID]; +	bool need_display_power;  };  #define hdac_to_hda_priv(_hdac) \ diff --git a/sound/soc/codecs/madera.h b/sound/soc/codecs/madera.h index 1f3e8e230cf2..6d8938a3fb64 100644 --- a/sound/soc/codecs/madera.h +++ b/sound/soc/codecs/madera.h @@ -27,6 +27,7 @@  #define MADERA_FLL_SRC_NONE		-1  #define MADERA_FLL_SRC_MCLK1		0  #define MADERA_FLL_SRC_MCLK2		1 +#define MADERA_FLL_SRC_MCLK3		2  #define MADERA_FLL_SRC_SLIMCLK		3  #define MADERA_FLL_SRC_FLL1		4  #define MADERA_FLL_SRC_FLL2		5 @@ -51,6 +52,7 @@  #define MADERA_CLK_SRC_MCLK1		0x0  #define MADERA_CLK_SRC_MCLK2		0x1 +#define MADERA_CLK_SRC_MCLK3		0x2  #define MADERA_CLK_SRC_FLL1		0x4  #define MADERA_CLK_SRC_FLL2		0x5  #define MADERA_CLK_SRC_FLL3		0x6 diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c index e3d311fb510e..f53235be77d9 100644 --- a/sound/soc/codecs/msm8916-wcd-analog.c +++ b/sound/soc/codecs/msm8916-wcd-analog.c @@ -228,6 +228,10 @@  #define CDC_A_RX_EAR_CTL			(0xf19E)  #define RX_EAR_CTL_SPK_VBAT_LDO_EN_MASK		BIT(0)  #define RX_EAR_CTL_SPK_VBAT_LDO_EN_ENABLE	BIT(0) +#define RX_EAR_CTL_PA_EAR_PA_EN_MASK		BIT(6) +#define RX_EAR_CTL_PA_EAR_PA_EN_ENABLE		BIT(6) +#define RX_EAR_CTL_PA_SEL_MASK			BIT(7) +#define RX_EAR_CTL_PA_SEL			BIT(7)  #define CDC_A_SPKR_DAC_CTL		(0xf1B0)  #define SPKR_DAC_CTL_DAC_RESET_MASK	BIT(4) @@ -312,6 +316,7 @@ static const char *const hph_text[] = { "ZERO", "Switch", };  static const struct soc_enum hph_enum = SOC_ENUM_SINGLE_VIRT(  					ARRAY_SIZE(hph_text), hph_text); +static const struct snd_kcontrol_new ear_mux = SOC_DAPM_ENUM("EAR_S", hph_enum);  static const struct snd_kcontrol_new hphl_mux = SOC_DAPM_ENUM("HPHL", hph_enum);  static const struct snd_kcontrol_new hphr_mux = SOC_DAPM_ENUM("HPHR", hph_enum); @@ -685,6 +690,34 @@ static int pm8916_wcd_analog_enable_spk_pa(struct snd_soc_dapm_widget *w,  	return 0;  } +static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, +					    struct snd_kcontrol *kcontrol, +					    int event) +{ +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +	switch (event) { +	case SND_SOC_DAPM_PRE_PMU: +		snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, +				    RX_EAR_CTL_PA_SEL_MASK, RX_EAR_CTL_PA_SEL); +		break; +	case SND_SOC_DAPM_POST_PMU: +		snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, +				    RX_EAR_CTL_PA_EAR_PA_EN_MASK, +				    RX_EAR_CTL_PA_EAR_PA_EN_ENABLE); +		break; +	case SND_SOC_DAPM_POST_PMD: +		snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, +				    RX_EAR_CTL_PA_EAR_PA_EN_MASK, 0); +		/* Delay to reduce ear turn off pop */ +		usleep_range(7000, 7100); +		snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, +				    RX_EAR_CTL_PA_SEL_MASK, 0); +		break; +	} +	return 0; +} +  static const struct reg_default wcd_reg_defaults_2_0[] = {  	{CDC_A_RX_COM_OCP_CTL, 0xD1},  	{CDC_A_RX_COM_OCP_COUNT, 0xFF}, @@ -801,12 +834,20 @@ static const struct snd_soc_dapm_route pm8916_wcd_analog_audio_map[] = {  	{"PDM_TX", NULL, "A_MCLK2"},  	{"A_MCLK2", NULL, "A_MCLK"}, +	/* Earpiece (RX MIX1) */ +	{"EAR", NULL, "EAR_S"}, +	{"EAR_S", "Switch", "EAR PA"}, +	{"EAR PA", NULL, "RX_BIAS"}, +	{"EAR PA", NULL, "HPHL DAC"}, +	{"EAR PA", NULL, "HPHR DAC"}, +	{"EAR PA", NULL, "EAR CP"}, +  	/* Headset (RX MIX1 and RX MIX2) */  	{"HEADPHONE", NULL, "HPHL PA"},  	{"HEADPHONE", NULL, "HPHR PA"}, -	{"HPHL PA", NULL, "EAR_HPHL_CLK"}, -	{"HPHR PA", NULL, "EAR_HPHR_CLK"}, +	{"HPHL DAC", NULL, "EAR_HPHL_CLK"}, +	{"HPHR DAC", NULL, "EAR_HPHR_CLK"},  	{"CP", NULL, "NCP_CLK"}, @@ -847,11 +888,20 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = {  	SND_SOC_DAPM_INPUT("AMIC1"),  	SND_SOC_DAPM_INPUT("AMIC3"),  	SND_SOC_DAPM_INPUT("AMIC2"), +	SND_SOC_DAPM_OUTPUT("EAR"),  	SND_SOC_DAPM_OUTPUT("HEADPHONE"),  	/* RX stuff */  	SND_SOC_DAPM_SUPPLY("INT_LDO_H", SND_SOC_NOPM, 1, 0, NULL, 0), +	SND_SOC_DAPM_PGA_E("EAR PA", SND_SOC_NOPM, +			   0, 0, NULL, 0, +			   pm8916_wcd_analog_enable_ear_pa, +			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | +			   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +	SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), +	SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), +  	SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0),  	SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux),  	SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, diff --git a/sound/soc/codecs/mt6358.c b/sound/soc/codecs/mt6358.c index bb737fd678cc..1b830ea4f6ed 100644 --- a/sound/soc/codecs/mt6358.c +++ b/sound/soc/codecs/mt6358.c @@ -93,6 +93,8 @@ struct mt6358_priv {  	int mtkaif_protocol;  	struct regulator *avdd_reg; + +	int wov_enabled;  };  int mt6358_set_mtkaif_protocol(struct snd_soc_component *cmpnt, @@ -464,6 +466,106 @@ static int mt6358_put_volsw(struct snd_kcontrol *kcontrol,  	return ret;  } +static void mt6358_restore_pga(struct mt6358_priv *priv); + +static int mt6358_enable_wov_phase2(struct mt6358_priv *priv) +{ +	/* analog */ +	regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, +			   0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, +			   0xffff, 0x0800); +	mt6358_restore_pga(priv); + +	regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9929); +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, +			   0xffff, 0x0025); +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, +			   0xffff, 0x0005); + +	/* digital */ +	regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, +			   0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x0120); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0xffff); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0200); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2424); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xdbac); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x029e); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, +			   0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, +			   0xffff, 0x0451); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0x68d1); + +	return 0; +} + +static int mt6358_disable_wov_phase2(struct mt6358_priv *priv) +{ +	/* digital */ +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_TOP, 0xffff, 0xc000); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_HPF_CFG0, +			   0xffff, 0x0450); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_POSDIV_CFG0, +			   0xffff, 0x0c00); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG5, 0xffff, 0x0100); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG4, 0xffff, 0x006c); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG3, 0xffff, 0xa879); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG2, 0xffff, 0x2323); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG1, 0xffff, 0x0400); +	regmap_update_bits(priv->regmap, MT6358_AFE_VOW_CFG0, 0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_GPIO_MODE3, 0xffff, 0x02d8); +	regmap_update_bits(priv->regmap, MT6358_AUD_TOP_CKPDN_CON0, +			   0xffff, 0x0000); + +	/* analog */ +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON8, +			   0xffff, 0x0004); +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON9, +			   0xffff, 0x0000); +	regmap_update_bits(priv->regmap, MT6358_DCXO_CW13, 0xffff, 0x9829); +	regmap_update_bits(priv->regmap, MT6358_AUDENC_ANA_CON1, +			   0xffff, 0x0000); +	mt6358_restore_pga(priv); +	regmap_update_bits(priv->regmap, MT6358_DCXO_CW14, 0xffff, 0xa2b5); +	regmap_update_bits(priv->regmap, MT6358_AUDDEC_ANA_CON13, +			   0xffff, 0x0010); + +	return 0; +} + +static int mt6358_get_wov(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); + +	ucontrol->value.integer.value[0] = priv->wov_enabled; +	return 0; +} + +static int mt6358_put_wov(struct snd_kcontrol *kcontrol, +			  struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); +	struct mt6358_priv *priv = snd_soc_component_get_drvdata(c); +	int enabled = ucontrol->value.integer.value[0]; + +	if (priv->wov_enabled != enabled) { +		if (enabled) +			mt6358_enable_wov_phase2(priv); +		else +			mt6358_disable_wov_phase2(priv); + +		priv->wov_enabled = enabled; +	} + +	return 0; +} +  static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0);  static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 600, 0); @@ -483,6 +585,9 @@ static const struct snd_kcontrol_new mt6358_snd_controls[] = {  			     MT6358_AUDENC_ANA_CON0, MT6358_AUDENC_ANA_CON1,  			     8, 4, 0,  			     snd_soc_get_volsw, mt6358_put_volsw, pga_tlv), + +	SOC_SINGLE_BOOL_EXT("Wake-on-Voice Phase2 Switch", 0, +			    mt6358_get_wov, mt6358_put_wov),  };  /* MUX */ diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index 88b75695fbf7..9711fab296eb 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -9,7 +9,9 @@  #include <linux/clk.h>  #include <linux/delay.h> +#include <linux/gpio/consumer.h>  #include <linux/module.h> +#include <linux/of_gpio.h>  #include <linux/pm_runtime.h>  #include <linux/regulator/consumer.h> @@ -59,9 +61,11 @@ struct pcm3168a_priv {  	struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES];  	struct regmap *regmap;  	struct clk *scki; +	struct gpio_desc *gpio_rst;  	unsigned long sysclk;  	struct pcm3168a_io_params io_params[2]; +	struct snd_soc_dai_driver dai_drv[2];  };  static const char *const pcm3168a_roll_off[] = { "Sharp", "Slow" }; @@ -314,6 +318,34 @@ static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai,  	return 0;  } +static void pcm3168a_update_fixup_pcm_stream(struct snd_soc_dai *dai) +{ +	struct snd_soc_component *component = dai->component; +	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component); +	u64 formats = SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE; +	unsigned int channel_max = dai->id == PCM3168A_DAI_DAC ? 8 : 6; + +	if (pcm3168a->io_params[dai->id].fmt == PCM3168A_FMT_RIGHT_J) { +		/* S16_LE is only supported in RIGHT_J mode */ +		formats |= SNDRV_PCM_FMTBIT_S16_LE; + +		/* +		 * If multi DIN/DOUT is not selected, RIGHT_J can only support +		 * two channels (no TDM support) +		 */ +		if (pcm3168a->io_params[dai->id].tdm_slots != 2) +			channel_max = 2; +	} + +	if (dai->id == PCM3168A_DAI_DAC) { +		dai->driver->playback.channels_max = channel_max; +		dai->driver->playback.formats = formats; +	} else { +		dai->driver->capture.channels_max = channel_max; +		dai->driver->capture.formats = formats; +	} +} +  static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format)  {  	struct snd_soc_component *component = dai->component; @@ -376,6 +408,8 @@ static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format)  	regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); +	pcm3168a_update_fixup_pcm_stream(dai); +  	return 0;  } @@ -409,6 +443,8 @@ static int pcm3168a_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,  	else  		io_params->tdm_mask = rx_mask; +	pcm3168a_update_fixup_pcm_stream(dai); +  	return 0;  } @@ -530,63 +566,7 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream,  	return 0;  } -static int pcm3168a_startup(struct snd_pcm_substream *substream, -			    struct snd_soc_dai *dai) -{ -	struct snd_soc_component *component = dai->component; -	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component); -	unsigned int sample_min; -	unsigned int channel_max; -	unsigned int channel_maxs[] = { -		8, /* DAC */ -		6  /* ADC */ -	}; - -	/* -	 * Available Data Bits -	 * -	 * RIGHT_J : 24 / 16 -	 * LEFT_J  : 24 -	 * I2S     : 24 -	 * -	 * TDM available -	 * -	 * I2S -	 * LEFT_J -	 */ -	switch (pcm3168a->io_params[dai->id].fmt) { -	case PCM3168A_FMT_RIGHT_J: -		sample_min  = 16; -		channel_max =  2; -		break; -	case PCM3168A_FMT_LEFT_J: -	case PCM3168A_FMT_I2S: -	case PCM3168A_FMT_DSP_A: -	case PCM3168A_FMT_DSP_B: -		sample_min  = 24; -		channel_max = channel_maxs[dai->id]; -		break; -	default: -		sample_min  = 24; -		channel_max =  2; -	} - -	snd_pcm_hw_constraint_minmax(substream->runtime, -				     SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -				     sample_min, 32); - -	/* Allow all channels in multi DIN/DOUT mode */ -	if (pcm3168a->io_params[dai->id].tdm_slots == 2) -		channel_max = channel_maxs[dai->id]; - -	snd_pcm_hw_constraint_minmax(substream->runtime, -				     SNDRV_PCM_HW_PARAM_CHANNELS, -				     2, channel_max); - -	return 0; -}  static const struct snd_soc_dai_ops pcm3168a_dai_ops = { -	.startup	= pcm3168a_startup,  	.set_fmt	= pcm3168a_set_dai_fmt,  	.set_sysclk	= pcm3168a_set_dai_sysclk,  	.hw_params	= pcm3168a_hw_params, @@ -666,6 +646,7 @@ static bool pcm3168a_readable_register(struct device *dev, unsigned int reg)  static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg)  {  	switch (reg) { +	case PCM3168A_RST_SMODE:  	case PCM3168A_DAC_ZERO:  	case PCM3168A_ADC_OV:  		return true; @@ -725,6 +706,25 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)  	dev_set_drvdata(dev, pcm3168a); +	/* +	 * Request the reset (connected to RST pin) gpio line as non exclusive +	 * as the same reset line might be connected to multiple pcm3168a codec +	 * +	 * The RST is low active, we want the GPIO line to be high initially, so +	 * request the initial level to LOW which in practice means DEASSERTED: +	 * The deasserted level of GPIO_ACTIVE_LOW is HIGH. +	 */ +	pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset", +						GPIOD_OUT_LOW | +						GPIOD_FLAGS_BIT_NONEXCLUSIVE); +	if (IS_ERR(pcm3168a->gpio_rst)) { +		ret = PTR_ERR(pcm3168a->gpio_rst); +		if (ret != -EPROBE_DEFER ) +			dev_err(dev, "failed to acquire RST gpio: %d\n", ret); + +		return ret; +	} +  	pcm3168a->scki = devm_clk_get(dev, "scki");  	if (IS_ERR(pcm3168a->scki)) {  		ret = PTR_ERR(pcm3168a->scki); @@ -766,18 +766,28 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)  		goto err_regulator;  	} -	ret = pcm3168a_reset(pcm3168a); -	if (ret) { -		dev_err(dev, "Failed to reset device: %d\n", ret); -		goto err_regulator; +	if (pcm3168a->gpio_rst) { +		/* +		 * The device is taken out from reset via GPIO line, wait for +		 * 3846 SCKI clock cycles for the internal reset de-assertion +		 */ +		msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk)); +	} else { +		ret = pcm3168a_reset(pcm3168a); +		if (ret) { +			dev_err(dev, "Failed to reset device: %d\n", ret); +			goto err_regulator; +		}  	}  	pm_runtime_set_active(dev);  	pm_runtime_enable(dev);  	pm_runtime_idle(dev); -	ret = devm_snd_soc_register_component(dev, &pcm3168a_driver, pcm3168a_dais, -			ARRAY_SIZE(pcm3168a_dais)); +	memcpy(pcm3168a->dai_drv, pcm3168a_dais, sizeof(pcm3168a->dai_drv)); +	ret = devm_snd_soc_register_component(dev, &pcm3168a_driver, +					      pcm3168a->dai_drv, +					      ARRAY_SIZE(pcm3168a->dai_drv));  	if (ret) {  		dev_err(dev, "failed to register component: %d\n", ret);  		goto err_regulator; @@ -806,6 +816,15 @@ static void pcm3168a_disable(struct device *dev)  void pcm3168a_remove(struct device *dev)  { +	struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + +	/* +	 * The RST is low active, we want the GPIO line to be low when the +	 * driver is removed, so set level to 1 which in practice means +	 * ASSERTED: +	 * The asserted level of GPIO_ACTIVE_LOW is LOW. +	 */ +	gpiod_set_value_cansleep(pcm3168a->gpio_rst, 1);  	pm_runtime_disable(dev);  #ifndef CONFIG_PM  	pcm3168a_disable(dev); diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index be1e276e3631..2552073e54ce 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -61,7 +61,6 @@ static const struct reg_sequence init_list[] = {  	{ RT1011_DAC_SET_1, 0xe702 },  	{ RT1011_DAC_SET_3, 0x2004 },  }; -#define RT1011_INIT_REG_LEN ARRAY_SIZE(init_list)  static const struct reg_default rt1011_reg[] = {  	{0x0000, 0x0000}, @@ -684,7 +683,8 @@ static int rt1011_reg_init(struct snd_soc_component *component)  {  	struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); -	regmap_multi_reg_write(rt1011->regmap, init_list, RT1011_INIT_REG_LEN); +	regmap_multi_reg_write(rt1011->regmap, +		init_list, ARRAY_SIZE(init_list));  	return 0;  } @@ -989,7 +989,7 @@ static SOC_ENUM_SINGLE_DECL(rt1011_din_source_enum, RT1011_CROSS_BQ_SET_1, 5,  static const char * const rt1011_tdm_data_out_select[] = {  	"TDM_O_LR", "BQ1", "DVOL", "BQ10", "ALC", "DMIX", "ADC_SRC_LR", -	"ADC_O_LR",	"ADC_MONO", "RSPK_BPF_LR", "DMIX_ADD", "ENVELOPE_FS", +	"ADC_O_LR", "ADC_MONO", "RSPK_BPF_LR", "DMIX_ADD", "ENVELOPE_FS",  	"SEP_O_GAIN", "ALC_BK_GAIN", "STP_V_C", "DMIX_ABST"  }; @@ -1002,7 +1002,7 @@ static SOC_ENUM_SINGLE_DECL(rt1011_tdm2_l_dac1_enum, RT1011_TDM2_SET_4, 12,  	rt1011_tdm_l_ch_data_select);  static SOC_ENUM_SINGLE_DECL(rt1011_tdm1_adc1_dat_enum, -	RT1011_ADCDAT_OUT_SOURCE, 0,	rt1011_tdm_data_out_select); +	RT1011_ADCDAT_OUT_SOURCE, 0, rt1011_tdm_data_out_select);  static SOC_ENUM_SINGLE_DECL(rt1011_tdm1_adc1_loc_enum, RT1011_TDM1_SET_2, 0,  	rt1011_tdm_l_ch_data_select); @@ -1024,9 +1024,9 @@ static const char * const rt1011_tdm_adc_swap_select[] = {  	"L/R", "R/L", "L/L", "R/R"  }; -static SOC_ENUM_SINGLE_DECL(rt1011_tdm_adc1_1_enum,	RT1011_TDM1_SET_3, 6, +static SOC_ENUM_SINGLE_DECL(rt1011_tdm_adc1_1_enum, RT1011_TDM1_SET_3, 6,  	rt1011_tdm_adc_swap_select); -static SOC_ENUM_SINGLE_DECL(rt1011_tdm_adc2_1_enum,	RT1011_TDM1_SET_3, 4, +static SOC_ENUM_SINGLE_DECL(rt1011_tdm_adc2_1_enum, RT1011_TDM1_SET_3, 4,  	rt1011_tdm_adc_swap_select);  static void rt1011_reset(struct regmap *regmap) @@ -1092,9 +1092,9 @@ static bool rt1011_validate_bq_drc_coeff(unsigned short reg)  {  	if ((reg == RT1011_DAC_SET_1) |  		(reg >= RT1011_ADC_SET && reg <= RT1011_ADC_SET_1) | -		(reg == RT1011_ADC_SET_4) |	(reg == RT1011_ADC_SET_5) | +		(reg == RT1011_ADC_SET_4) | (reg == RT1011_ADC_SET_5) |  		(reg == RT1011_MIXER_1) | -		(reg == RT1011_A_TIMING_1) |	(reg >= RT1011_POWER_7 && +		(reg == RT1011_A_TIMING_1) | (reg >= RT1011_POWER_7 &&  		reg <= RT1011_POWER_8) |  		(reg == RT1011_CLASS_D_POS) | (reg == RT1011_ANALOG_CTRL) |  		(reg >= RT1011_SPK_TEMP_PROTECT_0 && @@ -1163,9 +1163,6 @@ static int rt1011_bq_drc_coeff_put(struct snd_kcontrol *kcontrol,  		(struct rt1011_bq_drc_params *)ucontrol->value.integer.value;  	unsigned int i, mode_idx = 0; -	if (!component->card->instantiated) -		return 0; -  	if (strstr(ucontrol->id.name, "AdvanceMode Initial Set"))  		mode_idx = RT1011_ADVMODE_INITIAL_SET;  	else if (strstr(ucontrol->id.name, "AdvanceMode SEP BQ Coeff")) @@ -1236,9 +1233,6 @@ static int rt1011_r0_cali_put(struct snd_kcontrol *kcontrol,  	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);  	struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); -	if (!component->card->instantiated) -		return 0; -  	rt1011->cali_done = 0;  	if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF &&  		ucontrol->value.integer.value[0]) @@ -1284,9 +1278,6 @@ static int rt1011_r0_load_mode_put(struct snd_kcontrol *kcontrol,  	if (ucontrol->value.integer.value[0] == rt1011->r0_reg)  		return 0; -	if (!component->card->instantiated) -		return 0; -  	if (ucontrol->value.integer.value[0] == 0)  		return -EINVAL; @@ -1298,7 +1289,7 @@ static int rt1011_r0_load_mode_put(struct snd_kcontrol *kcontrol,  		r0_integer = format / rt1011->r0_reg / 128;  		r0_factor = ((format / rt1011->r0_reg * 100) / 128)  						- (r0_integer * 100); -		dev_info(dev,	"New r0 resistance about %d.%02d ohm, reg=0x%X\n", +		dev_info(dev, "New r0 resistance about %d.%02d ohm, reg=0x%X\n",  			r0_integer, r0_factor, rt1011->r0_reg);  		if (rt1011->r0_reg) @@ -1640,6 +1631,7 @@ static int rt1011_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  		break;  	default:  		ret = -EINVAL; +		goto _set_fmt_err_;  	}  	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { @@ -1650,6 +1642,7 @@ static int rt1011_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  		break;  	default:  		ret = -EINVAL; +		goto _set_fmt_err_;  	}  	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { @@ -1666,6 +1659,7 @@ static int rt1011_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  		break;  	default:  		ret = -EINVAL; +		goto _set_fmt_err_;  	}  	switch (dai->id) { @@ -1683,6 +1677,7 @@ static int rt1011_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)  		ret = -EINVAL;  	} +_set_fmt_err_:  	snd_soc_dapm_mutex_unlock(dapm);  	return ret;  } @@ -1778,7 +1773,8 @@ static int rt1011_set_component_pll(struct snd_soc_component *component,  	ret = rl6231_pll_calc(freq_in, freq_out, &pll_code);  	if (ret < 0) { -		dev_err(component->dev, "Unsupport input clock %d\n", freq_in); +		dev_err(component->dev, "Unsupported input clock %d\n", +			freq_in);  		return ret;  	} @@ -1805,8 +1801,8 @@ static int rt1011_set_tdm_slot(struct snd_soc_dai *dai,  	struct snd_soc_component *component = dai->component;  	struct snd_soc_dapm_context *dapm =  		snd_soc_component_get_dapm(component); -	unsigned int val = 0, tdm_en = 0; -	int ret = 0; +	unsigned int val = 0, tdm_en = 0, rx_slotnum, tx_slotnum; +	int ret = 0, first_bit, last_bit;  	snd_soc_dapm_mutex_lock(dapm);  	if (rx_mask || tx_mask) @@ -1829,6 +1825,7 @@ static int rt1011_set_tdm_slot(struct snd_soc_dai *dai,  		break;  	default:  		ret = -EINVAL; +		goto _set_tdm_err_;  	}  	switch (slot_width) { @@ -1848,22 +1845,153 @@ static int rt1011_set_tdm_slot(struct snd_soc_dai *dai,  		break;  	default:  		ret = -EINVAL; +		goto _set_tdm_err_; +	} + +	/* Rx slot configuration */ +	rx_slotnum = hweight_long(rx_mask); +	first_bit = find_next_bit((unsigned long *)&rx_mask, 32, 0); +	if (rx_slotnum > 1 || rx_slotnum == 0) { +		ret = -EINVAL; +		dev_dbg(component->dev, "too many rx slots or zero slot\n"); +		goto _set_tdm_err_; +	} + +	switch (first_bit) { +	case 0: +	case 2: +	case 4: +	case 6: +		snd_soc_component_update_bits(component, +			RT1011_CROSS_BQ_SET_1, RT1011_MONO_LR_SEL_MASK, +			RT1011_MONO_L_CHANNEL); +		snd_soc_component_update_bits(component, +			RT1011_TDM1_SET_4, +			RT1011_TDM_I2S_TX_L_DAC1_1_MASK | +			RT1011_TDM_I2S_TX_R_DAC1_1_MASK, +			(first_bit << RT1011_TDM_I2S_TX_L_DAC1_1_SFT) | +			((first_bit+1) << RT1011_TDM_I2S_TX_R_DAC1_1_SFT)); +		break; +	case 1: +	case 3: +	case 5: +	case 7: +		snd_soc_component_update_bits(component, +			RT1011_CROSS_BQ_SET_1, RT1011_MONO_LR_SEL_MASK, +			RT1011_MONO_R_CHANNEL); +		snd_soc_component_update_bits(component, +			RT1011_TDM1_SET_4, +			RT1011_TDM_I2S_TX_L_DAC1_1_MASK | +			RT1011_TDM_I2S_TX_R_DAC1_1_MASK, +			((first_bit-1) << RT1011_TDM_I2S_TX_L_DAC1_1_SFT) | +			(first_bit << RT1011_TDM_I2S_TX_R_DAC1_1_SFT)); +		break; +	default: +		ret = -EINVAL; +		goto _set_tdm_err_; +	} + +	/* Tx slot configuration */ +	tx_slotnum = hweight_long(tx_mask); +	first_bit = find_next_bit((unsigned long *)&tx_mask, 32, 0); +	last_bit = find_last_bit((unsigned long *)&tx_mask, 32); +	if (tx_slotnum > 2 || (last_bit-first_bit) > 1) { +		ret = -EINVAL; +		dev_dbg(component->dev, "too many tx slots or tx slot location error\n"); +		goto _set_tdm_err_; +	} + +	if (tx_slotnum == 1) { +		snd_soc_component_update_bits(component, RT1011_TDM1_SET_2, +			RT1011_TDM_I2S_DOCK_ADCDAT_LEN_1_MASK | +			RT1011_TDM_ADCDAT1_DATA_LOCATION, first_bit); +		switch (first_bit) { +		case 1: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC1_1_MASK, +				RT1011_TDM_I2S_RX_ADC1_1_LL); +			break; +		case 3: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC2_1_MASK, +				RT1011_TDM_I2S_RX_ADC2_1_LL); +			break; +		case 5: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC3_1_MASK, +				RT1011_TDM_I2S_RX_ADC3_1_LL); +			break; +		case 7: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC4_1_MASK, +				RT1011_TDM_I2S_RX_ADC4_1_LL); +			break; +		case 0: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC1_1_MASK, 0); +			break; +		case 2: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC2_1_MASK, 0); +			break; +		case 4: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC3_1_MASK, 0); +			break; +		case 6: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_3, +				RT1011_TDM_I2S_RX_ADC4_1_MASK, 0); +			break; +		default: +			ret = -EINVAL; +			dev_dbg(component->dev, +				"tx slot location error\n"); +			goto _set_tdm_err_; +		} +	} else if (tx_slotnum == 2) { +		switch (first_bit) { +		case 0: +		case 2: +		case 4: +		case 6: +			snd_soc_component_update_bits(component, +				RT1011_TDM1_SET_2, +				RT1011_TDM_I2S_DOCK_ADCDAT_LEN_1_MASK | +				RT1011_TDM_ADCDAT1_DATA_LOCATION, +				RT1011_TDM_I2S_DOCK_ADCDAT_2CH | first_bit); +			break; +		default: +			ret = -EINVAL; +			dev_dbg(component->dev, +				"tx slot location should be paired and start from slot0/2/4/6\n"); +			goto _set_tdm_err_; +		}  	}  	snd_soc_component_update_bits(component, RT1011_TDM1_SET_1,  		RT1011_I2S_CH_TX_MASK | RT1011_I2S_CH_RX_MASK | -		RT1011_I2S_CH_TX_LEN_MASK |	RT1011_I2S_CH_RX_LEN_MASK, val); +		RT1011_I2S_CH_TX_LEN_MASK | RT1011_I2S_CH_RX_LEN_MASK, val);  	snd_soc_component_update_bits(component, RT1011_TDM2_SET_1,  		RT1011_I2S_CH_TX_MASK | RT1011_I2S_CH_RX_MASK | -		RT1011_I2S_CH_TX_LEN_MASK |	RT1011_I2S_CH_RX_LEN_MASK, val); +		RT1011_I2S_CH_TX_LEN_MASK | RT1011_I2S_CH_RX_LEN_MASK, val);  	snd_soc_component_update_bits(component, RT1011_TDM1_SET_2, -		RT1011_TDM_I2S_DOCK_EN_1_MASK,	tdm_en); +		RT1011_TDM_I2S_DOCK_EN_1_MASK, tdm_en);  	snd_soc_component_update_bits(component, RT1011_TDM2_SET_2, -		RT1011_TDM_I2S_DOCK_EN_2_MASK,	tdm_en); -	snd_soc_component_update_bits(component, RT1011_TDM_TOTAL_SET, -		RT1011_ADCDAT1_PIN_CONFIG | RT1011_ADCDAT2_PIN_CONFIG, -		RT1011_ADCDAT1_OUTPUT | RT1011_ADCDAT2_OUTPUT); +		RT1011_TDM_I2S_DOCK_EN_2_MASK, tdm_en); +	if (tx_slotnum) +		snd_soc_component_update_bits(component, RT1011_TDM_TOTAL_SET, +			RT1011_ADCDAT1_PIN_CONFIG | RT1011_ADCDAT2_PIN_CONFIG, +			RT1011_ADCDAT1_OUTPUT | RT1011_ADCDAT2_OUTPUT); +_set_tdm_err_:  	snd_soc_dapm_mutex_unlock(dapm);  	return ret;  } @@ -1982,7 +2110,7 @@ static const struct snd_soc_component_driver soc_component_dev_rt1011 = {  	.remove = rt1011_remove,  	.suspend = rt1011_suspend,  	.resume = rt1011_resume, -	.set_bias_level		= rt1011_set_bias_level, +	.set_bias_level = rt1011_set_bias_level,  	.controls = rt1011_snd_controls,  	.num_controls = ARRAY_SIZE(rt1011_snd_controls),  	.dapm_widgets = rt1011_dapm_widgets, @@ -1991,9 +2119,9 @@ static const struct snd_soc_component_driver soc_component_dev_rt1011 = {  	.num_dapm_routes = ARRAY_SIZE(rt1011_dapm_routes),  	.set_sysclk = rt1011_set_component_sysclk,  	.set_pll = rt1011_set_component_pll, -	.use_pmdown_time	= 1, -	.endianness		= 1, -	.non_legacy_dai_naming	= 1, +	.use_pmdown_time = 1, +	.endianness = 1, +	.non_legacy_dai_naming = 1,  };  static const struct regmap_config rt1011_regmap = { @@ -2095,17 +2223,17 @@ static int rt1011_calibrate(struct rt1011_priv *rt1011, unsigned char cali_flag)  	dc_offset = value << 16;  	regmap_read(rt1011->regmap, RT1011_EFUSE_ADC_OFFSET_15_0, &value);  	dc_offset |= (value & 0xffff); -	dev_info(dev,	"ADC offset=0x%x\n", dc_offset); +	dev_info(dev, "ADC offset=0x%x\n", dc_offset);  	regmap_read(rt1011->regmap, RT1011_EFUSE_DAC_OFFSET_G0_20_16, &value);  	dc_offset = value << 16;  	regmap_read(rt1011->regmap, RT1011_EFUSE_DAC_OFFSET_G0_15_0, &value);  	dc_offset |= (value & 0xffff); -	dev_info(dev,	"Gain0 offset=0x%x\n", dc_offset); +	dev_info(dev, "Gain0 offset=0x%x\n", dc_offset);  	regmap_read(rt1011->regmap, RT1011_EFUSE_DAC_OFFSET_G1_20_16, &value);  	dc_offset = value << 16;  	regmap_read(rt1011->regmap, RT1011_EFUSE_DAC_OFFSET_G1_15_0, &value);  	dc_offset |= (value & 0xffff); -	dev_info(dev,	"Gain1 offset=0x%x\n", dc_offset); +	dev_info(dev, "Gain1 offset=0x%x\n", dc_offset);  	if (cali_flag) { @@ -2125,7 +2253,7 @@ static int rt1011_calibrate(struct rt1011_priv *rt1011, unsigned char cali_flag)  		while (count < chk_cnt) {  			msleep(100);  			regmap_read(rt1011->regmap, -				RT1011_INIT_RECIPROCAL_SYN_24_16,	&value); +				RT1011_INIT_RECIPROCAL_SYN_24_16, &value);  			r0[count%3] = value << 16;  			regmap_read(rt1011->regmap,  				RT1011_INIT_RECIPROCAL_SYN_15_0, &value); @@ -2140,7 +2268,7 @@ static int rt1011_calibrate(struct rt1011_priv *rt1011, unsigned char cali_flag)  				break;  		}  		if (count > chk_cnt) { -			dev_err(dev,	"Calibrate R0 Failure\n"); +			dev_err(dev, "Calibrate R0 Failure\n");  			ret = -EAGAIN;  		} else {  			format = 2147483648U; /* 2^24 * 128 */ @@ -2149,7 +2277,7 @@ static int rt1011_calibrate(struct rt1011_priv *rt1011, unsigned char cali_flag)  							- (r0_integer * 100);  			rt1011->r0_reg = r0[0];  			rt1011->cali_done = 1; -			dev_info(dev,	"r0 resistance about %d.%02d ohm, reg=0x%X\n", +			dev_info(dev, "r0 resistance about %d.%02d ohm, reg=0x%X\n",  				r0_integer, r0_factor, r0[0]);  		}  	} @@ -2196,8 +2324,12 @@ static void rt1011_calibration_work(struct work_struct *work)  	struct rt1011_priv *rt1011 =  		container_of(work, struct rt1011_priv, cali_work);  	struct snd_soc_component *component = rt1011->component; +	unsigned int r0_integer, r0_factor, format; -	rt1011_calibrate(rt1011, 1); +	if (rt1011->r0_calib) +		rt1011_calibrate(rt1011, 0); +	else +		rt1011_calibrate(rt1011, 1);  	/*  	 * This flag should reset after booting. @@ -2208,6 +2340,40 @@ static void rt1011_calibration_work(struct work_struct *work)  	/* initial */  	rt1011_reg_init(component); + +	/* Apply temperature and calibration data from device property */ +	if (rt1011->temperature_calib <= 0xff && +		rt1011->temperature_calib > 0) { +		snd_soc_component_update_bits(component, +			RT1011_STP_INITIAL_RESISTANCE_TEMP, 0x3ff, +			(rt1011->temperature_calib << 2)); +	} + +	if (rt1011->r0_calib) { +		rt1011->r0_reg = rt1011->r0_calib; + +		format = 2147483648U; /* 2^24 * 128 */ +		r0_integer = format / rt1011->r0_reg / 128; +		r0_factor = ((format / rt1011->r0_reg * 100) / 128) +						- (r0_integer * 100); +		dev_info(component->dev, "DP r0 resistance about %d.%02d ohm, reg=0x%X\n", +			r0_integer, r0_factor, rt1011->r0_reg); + +		rt1011_r0_load(rt1011); +	} +} + +static int rt1011_parse_dp(struct rt1011_priv *rt1011, struct device *dev) +{ +	device_property_read_u32(dev, "realtek,temperature_calib", +		&rt1011->temperature_calib); +	device_property_read_u32(dev, "realtek,r0_calib", +		&rt1011->r0_calib); + +	dev_dbg(dev, "%s: r0_calib: 0x%x, temperature_calib: 0x%x", +		__func__, rt1011->r0_calib, rt1011->temperature_calib); + +	return 0;  }  static int rt1011_i2c_probe(struct i2c_client *i2c, @@ -2219,11 +2385,13 @@ static int rt1011_i2c_probe(struct i2c_client *i2c,  	rt1011 = devm_kzalloc(&i2c->dev, sizeof(struct rt1011_priv),  				GFP_KERNEL); -	if (rt1011 == NULL) +	if (!rt1011)  		return -ENOMEM;  	i2c_set_clientdata(i2c, rt1011); +	rt1011_parse_dp(rt1011, &i2c->dev); +  	rt1011->regmap = devm_regmap_init_i2c(i2c, &rt1011_regmap);  	if (IS_ERR(rt1011->regmap)) {  		ret = PTR_ERR(rt1011->regmap); @@ -2254,7 +2422,6 @@ static void rt1011_i2c_shutdown(struct i2c_client *client)  	rt1011_reset(rt1011->regmap);  } -  static struct i2c_driver rt1011_i2c_driver = {  	.driver = {  		.name = "rt1011", diff --git a/sound/soc/codecs/rt1011.h b/sound/soc/codecs/rt1011.h index 2d65983f3d0f..68fadc15fa8c 100644 --- a/sound/soc/codecs/rt1011.h +++ b/sound/soc/codecs/rt1011.h @@ -460,6 +460,23 @@  #define RT1011_TDM_I2S_DOCK_EN_1_MASK			(0x1 << 3)  #define RT1011_TDM_I2S_DOCK_EN_1_SFT			3  #define RT1011_TDM_I2S_DOCK_EN_1		(0x1 << 3) +#define RT1011_TDM_ADCDAT1_DATA_LOCATION			(0x7 << 0) + +/* TDM1 Setting-3 (0x0118) */ +#define RT1011_TDM_I2S_RX_ADC1_1_MASK			(0x3 << 6) +#define RT1011_TDM_I2S_RX_ADC2_1_MASK			(0x3 << 4) +#define RT1011_TDM_I2S_RX_ADC3_1_MASK			(0x3 << 2) +#define RT1011_TDM_I2S_RX_ADC4_1_MASK			(0x3 << 0) +#define RT1011_TDM_I2S_RX_ADC1_1_LL			(0x2 << 6) +#define RT1011_TDM_I2S_RX_ADC2_1_LL			(0x2 << 4) +#define RT1011_TDM_I2S_RX_ADC3_1_LL			(0x2 << 2) +#define RT1011_TDM_I2S_RX_ADC4_1_LL			(0x2 << 0) + +/* TDM1 Setting-4 (0x011a) */ +#define RT1011_TDM_I2S_TX_L_DAC1_1_MASK			(0x7 << 12) +#define RT1011_TDM_I2S_TX_R_DAC1_1_MASK			(0x7 << 8) +#define RT1011_TDM_I2S_TX_L_DAC1_1_SFT 12 +#define RT1011_TDM_I2S_TX_R_DAC1_1_SFT 8  /* TDM2 Setting-2 (0x0120) */  #define RT1011_TDM_I2S_DOCK_ADCDAT_LEN_2_MASK			(0x7 << 13) @@ -585,6 +602,12 @@  #define RT1011_STP_T0_EN_BIT		6  #define RT1011_STP_T0_EN		(0x1 << 6) +/* Cross Biquad Setting-1 (0x0702) */ +#define RT1011_MONO_LR_SEL_MASK			(0x3 << 5) +#define RT1011_MONO_L_CHANNEL			(0x0 << 5) +#define RT1011_MONO_R_CHANNEL			(0x1 << 5) +#define RT1011_MONO_LR_MIX_CHANNEL			(0x2 << 5) +  /* ClassD Internal Setting-1 (0x1300) */  #define RT1011_DRIVER_READY_SPK			(0x1 << 12)  #define RT1011_DRIVER_READY_SPK_BIT		12 @@ -667,6 +690,7 @@ struct rt1011_priv {  	int bq_drc_set;  	unsigned int r0_reg, cali_done; +	unsigned int r0_calib, temperature_calib;  	int recv_spk_mode;  }; diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c index 892ea406a69b..f1b7b947ecbd 100644 --- a/sound/soc/codecs/rt5514-spi.c +++ b/sound/soc/codecs/rt5514-spi.c @@ -201,26 +201,25 @@ static irqreturn_t rt5514_spi_irq(int irq, void *data)  }  /* PCM for streaming audio from the DSP buffer */ -static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream) +static int rt5514_spi_pcm_open(struct snd_soc_component *component, +			       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) +static int rt5514_spi_hw_params(struct snd_soc_component *component, +				struct snd_pcm_substream *substream, +				struct snd_pcm_hw_params *hw_params)  { -	struct snd_soc_pcm_runtime *rtd = substream->private_data; -	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);  	struct rt5514_dsp *rt5514_dsp =  		snd_soc_component_get_drvdata(component);  	int ret;  	u8 buf[8];  	mutex_lock(&rt5514_dsp->dma_lock); -	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, -			params_buffer_bytes(hw_params)); +	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));  	rt5514_dsp->substream = substream;  	rt5514_dsp->dma_offset = 0; @@ -234,10 +233,9 @@ static int rt5514_spi_hw_params(struct snd_pcm_substream *substream,  	return ret;  } -static int rt5514_spi_hw_free(struct snd_pcm_substream *substream) +static int rt5514_spi_hw_free(struct snd_soc_component *component, +			      struct snd_pcm_substream *substream)  { -	struct snd_soc_pcm_runtime *rtd = substream->private_data; -	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);  	struct rt5514_dsp *rt5514_dsp =  		snd_soc_component_get_drvdata(component); @@ -247,28 +245,20 @@ static int rt5514_spi_hw_free(struct snd_pcm_substream *substream)  	cancel_delayed_work_sync(&rt5514_dsp->copy_work); -	return snd_pcm_lib_free_vmalloc_buffer(substream); +	return snd_pcm_lib_free_pages(substream);  }  static snd_pcm_uframes_t rt5514_spi_pcm_pointer( +		struct snd_soc_component *component,  		struct snd_pcm_substream *substream)  {  	struct snd_pcm_runtime *runtime = substream->runtime; -	struct snd_soc_pcm_runtime *rtd = substream->private_data; -	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);  	struct rt5514_dsp *rt5514_dsp =  		snd_soc_component_get_drvdata(component);  	return bytes_to_frames(runtime, rt5514_dsp->dma_offset);  } -static const 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, -	.pointer	= rt5514_spi_pcm_pointer, -	.page		= snd_pcm_lib_get_vmalloc_page, -};  static int rt5514_spi_pcm_probe(struct snd_soc_component *component)  { @@ -301,10 +291,22 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component)  	return 0;  } +static int rt5514_spi_pcm_new(struct snd_soc_component *component, +			      struct snd_soc_pcm_runtime *rtd) +{ +	snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, +					      NULL, 0, 0); +	return 0; +} +  static const struct snd_soc_component_driver rt5514_spi_component = { -	.name  = DRV_NAME, -	.probe = rt5514_spi_pcm_probe, -	.ops = &rt5514_spi_pcm_ops, +	.name		= DRV_NAME, +	.probe		= rt5514_spi_pcm_probe, +	.open		= rt5514_spi_pcm_open, +	.hw_params	= rt5514_spi_hw_params, +	.hw_free	= rt5514_spi_hw_free, +	.pointer	= rt5514_spi_pcm_pointer, +	.pcm_construct	= rt5514_spi_pcm_new,  };  /** diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 1c06b3b9218c..92d67010aeed 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3270,6 +3270,9 @@ static void rt5645_jack_detect_work(struct work_struct *work)  		snd_soc_jack_report(rt5645->mic_jack,  				    report, SND_JACK_MICROPHONE);  		return; +	case 4: +		val = snd_soc_component_read32(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +		break;  	default: /* read rt5645 jd1_1 status */  		val = snd_soc_component_read32(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000;  		break; @@ -3603,7 +3606,7 @@ static const struct rt5645_platform_data intel_braswell_platform_data = {  static const struct rt5645_platform_data buddy_platform_data = {  	.dmic1_data_pin = RT5645_DMIC_DATA_GPIO5,  	.dmic2_data_pin = RT5645_DMIC_DATA_IN2P, -	.jd_mode = 3, +	.jd_mode = 4,  	.level_trigger_irq = true,  }; @@ -3636,6 +3639,12 @@ static const struct rt5645_platform_data lattepanda_board_platform_data = {  	.inv_jd1_1 = true  }; +static const struct rt5645_platform_data kahlee_platform_data = { +	.dmic1_data_pin = RT5645_DMIC_DATA_GPIO5, +	.dmic2_data_pin = RT5645_DMIC_DATA_IN2P, +	.jd_mode = 3, +}; +  static const struct dmi_system_id dmi_platform_data[] = {  	{  		.ident = "Chrome Buddy", @@ -3742,6 +3751,13 @@ static const struct dmi_system_id dmi_platform_data[] = {  		},  		.driver_data = (void *)&lattepanda_board_platform_data,  	}, +	{ +		.ident = "Chrome Kahlee", +		.matches = { +			DMI_MATCH(DMI_PRODUCT_NAME, "Kahlee"), +		}, +		.driver_data = (void *)&kahlee_platform_data, +	},  	{ }  }; @@ -3999,6 +4015,7 @@ static int rt5645_i2c_probe(struct i2c_client *i2c,  					   RT5645_JD1_MODE_1);  			break;  		case 3: +		case 4:  			regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1,  					   RT5645_JD1_MODE_MASK,  					   RT5645_JD1_MODE_2); diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c index 2943692f66ed..e6c1ec6c426e 100644 --- a/sound/soc/codecs/rt5663.c +++ b/sound/soc/codecs/rt5663.c @@ -3644,7 +3644,7 @@ static int rt5663_i2c_probe(struct i2c_client *i2c,  		regmap_update_bits(rt5663->regmap, RT5663_PWR_ANLG_1,  			RT5663_LDO1_DVO_MASK | RT5663_AMP_HP_MASK,  			RT5663_LDO1_DVO_0_9V | RT5663_AMP_HP_3X); -			break; +		break;  	case CODEC_VER_0:  		regmap_update_bits(rt5663->regmap, RT5663_DIG_MISC,  			RT5663_DIG_GATE_CTRL_MASK, RT5663_DIG_GATE_CTRL_EN); @@ -3663,7 +3663,7 @@ static int rt5663_i2c_probe(struct i2c_client *i2c,  		regmap_update_bits(rt5663->regmap, RT5663_TDM_2,  			RT5663_DATA_SWAP_ADCDAT1_MASK,  			RT5663_DATA_SWAP_ADCDAT1_LL); -			break; +		break;  	default:  		dev_err(&i2c->dev, "%s:Unknown codec type\n", __func__);  	} diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index d681488f5312..7810b1d7de32 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -24,6 +24,9 @@  #include <linux/firmware.h>  #include <linux/acpi.h> +#include <sound/soc.h> + +#include "rt5677.h"  #include "rt5677-spi.h"  #define DRV_NAME "rt5677spi" @@ -45,9 +48,367 @@  #define RT5677_SPI_WRITE_16	0x1  #define RT5677_SPI_READ_16	0x0 +#define RT5677_BUF_BYTES_TOTAL		0x20000 +#define RT5677_MIC_BUF_ADDR		0x60030000 +#define RT5677_MODEL_ADDR		0x5FFC9800 +#define RT5677_MIC_BUF_BYTES		((u32)(RT5677_BUF_BYTES_TOTAL - \ +					sizeof(u32))) +#define RT5677_MIC_BUF_FIRST_READ_SIZE	0x10000 +  static struct spi_device *g_spi;  static DEFINE_MUTEX(spi_mutex); +struct rt5677_dsp { +	struct device *dev; +	struct delayed_work copy_work; +	struct mutex dma_lock; +	struct snd_pcm_substream *substream; +	size_t dma_offset;	/* zero-based offset into runtime->dma_area */ +	size_t avail_bytes;	/* number of new bytes since last period */ +	u32 mic_read_offset;	/* zero-based offset into DSP's mic buffer */ +	bool new_hotword;	/* a new hotword is fired */ +}; + +static const struct snd_pcm_hardware rt5677_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	= RT5677_BUF_BYTES_TOTAL / 8, +	.periods_min		= 8, +	.periods_max		= 8, +	.channels_min		= 1, +	.channels_max		= 1, +	.buffer_bytes_max	= RT5677_BUF_BYTES_TOTAL, +}; + +static struct snd_soc_dai_driver rt5677_spi_dai = { +	/* The DAI name "rt5677-dsp-cpu-dai" is not used. The actual DAI name +	 * registered with ASoC is the name of the device "spi-RT5677AA:00", +	 * because we only have one DAI. See snd_soc_register_dais(). +	 */ +	.name = "rt5677-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, +	}, +}; + +/* PCM for streaming audio from the DSP buffer */ +static int rt5677_spi_pcm_open( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream) +{ +	snd_soc_set_runtime_hwparams(substream, &rt5677_spi_pcm_hardware); +	return 0; +} + +static int rt5677_spi_pcm_close( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct snd_soc_component *codec_component = +			snd_soc_rtdcom_lookup(rtd, "rt5677"); +	struct rt5677_priv *rt5677 = +			snd_soc_component_get_drvdata(codec_component); +	struct rt5677_dsp *rt5677_dsp = +			snd_soc_component_get_drvdata(component); + +	cancel_delayed_work_sync(&rt5677_dsp->copy_work); +	rt5677->set_dsp_vad(codec_component, false); +	return 0; +} + +static int rt5677_spi_hw_params( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream, +		struct snd_pcm_hw_params *hw_params) +{ +	struct rt5677_dsp *rt5677_dsp = +			snd_soc_component_get_drvdata(component); +	int ret; + +	mutex_lock(&rt5677_dsp->dma_lock); +	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +	rt5677_dsp->substream = substream; +	mutex_unlock(&rt5677_dsp->dma_lock); + +	return ret; +} + +static int rt5677_spi_hw_free( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream) +{ +	struct rt5677_dsp *rt5677_dsp = +			snd_soc_component_get_drvdata(component); + +	mutex_lock(&rt5677_dsp->dma_lock); +	rt5677_dsp->substream = NULL; +	mutex_unlock(&rt5677_dsp->dma_lock); + +	return snd_pcm_lib_free_pages(substream); +} + +static int rt5677_spi_prepare( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct snd_soc_component *rt5677_component = +			snd_soc_rtdcom_lookup(rtd, "rt5677"); +	struct rt5677_priv *rt5677 = +			snd_soc_component_get_drvdata(rt5677_component); +	struct rt5677_dsp *rt5677_dsp = +			snd_soc_component_get_drvdata(component); + +	rt5677->set_dsp_vad(rt5677_component, true); +	rt5677_dsp->dma_offset = 0; +	rt5677_dsp->avail_bytes = 0; +	return 0; +} + +static snd_pcm_uframes_t rt5677_spi_pcm_pointer( +		struct snd_soc_component *component, +		struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct rt5677_dsp *rt5677_dsp = +			snd_soc_component_get_drvdata(component); + +	return bytes_to_frames(runtime, rt5677_dsp->dma_offset); +} + +static int rt5677_spi_mic_write_offset(u32 *mic_write_offset) +{ +	int ret; +	/* Grab the first 4 bytes that hold the write pointer on the +	 * dsp, and check to make sure that it points somewhere inside the +	 * buffer. +	 */ +	ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR, mic_write_offset, +			sizeof(u32)); +	if (ret) +		return ret; +	/* Adjust the offset so that it's zero-based */ +	*mic_write_offset = *mic_write_offset - sizeof(u32); +	return *mic_write_offset < RT5677_MIC_BUF_BYTES ? 0 : -EFAULT; +} + +/* + * Copy one contiguous block of audio samples from the DSP mic buffer to the + * dma_area of the pcm runtime. The receiving buffer may wrap around. + * @begin: start offset of the block to copy, in bytes. + * @end:   offset of the first byte after the block to copy, must be greater + *         than or equal to begin. + * + * Return: Zero if successful, or a negative error code on failure. + */ +static int rt5677_spi_copy_block(struct rt5677_dsp *rt5677_dsp, +		u32 begin, u32 end) +{ +	struct snd_pcm_runtime *runtime = rt5677_dsp->substream->runtime; +	size_t bytes_per_frame = frames_to_bytes(runtime, 1); +	size_t first_chunk_len, second_chunk_len; +	int ret; + +	if (begin > end || runtime->dma_bytes < 2 * bytes_per_frame) { +		dev_err(rt5677_dsp->dev, +			"Invalid copy from (%u, %u), dma_area size %zu\n", +			begin, end, runtime->dma_bytes); +		return -EINVAL; +	} + +	/* The block to copy is empty */ +	if (begin == end) +		return 0; + +	/* If the incoming chunk is too big for the receiving buffer, only the +	 * last "receiving buffer size - one frame" bytes are copied. +	 */ +	if (end - begin > runtime->dma_bytes - bytes_per_frame) +		begin = end - (runtime->dma_bytes - bytes_per_frame); + +	/* May need to split to two chunks, calculate the size of each */ +	first_chunk_len = end - begin; +	second_chunk_len = 0; +	if (rt5677_dsp->dma_offset + first_chunk_len > runtime->dma_bytes) { +		/* Receiving buffer wrapped around */ +		second_chunk_len = first_chunk_len; +		first_chunk_len = runtime->dma_bytes - rt5677_dsp->dma_offset; +		second_chunk_len -= first_chunk_len; +	} + +	/* Copy first chunk */ +	ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR + sizeof(u32) + begin, +			runtime->dma_area + rt5677_dsp->dma_offset, +			first_chunk_len); +	if (ret) +		return ret; +	rt5677_dsp->dma_offset += first_chunk_len; +	if (rt5677_dsp->dma_offset == runtime->dma_bytes) +		rt5677_dsp->dma_offset = 0; + +	/* Copy second chunk */ +	if (second_chunk_len) { +		ret = rt5677_spi_read(RT5677_MIC_BUF_ADDR + sizeof(u32) + +				begin + first_chunk_len, runtime->dma_area, +				second_chunk_len); +		if (!ret) +			rt5677_dsp->dma_offset = second_chunk_len; +	} +	return ret; +} + +/* + * Copy a given amount of audio samples from the DSP mic buffer starting at + * mic_read_offset, to the dma_area of the pcm runtime. The source buffer may + * wrap around. mic_read_offset is updated after successful copy. + * @amount: amount of samples to copy, in bytes. + * + * Return: Zero if successful, or a negative error code on failure. + */ +static int rt5677_spi_copy(struct rt5677_dsp *rt5677_dsp, u32 amount) +{ +	int ret = 0; +	u32 target; + +	if (amount == 0) +		return ret; + +	target = rt5677_dsp->mic_read_offset + amount; +	/* Copy the first chunk in DSP's mic buffer */ +	ret |= rt5677_spi_copy_block(rt5677_dsp, rt5677_dsp->mic_read_offset, +			min(target, RT5677_MIC_BUF_BYTES)); + +	if (target >= RT5677_MIC_BUF_BYTES) { +		/* Wrap around, copy the second chunk */ +		target -= RT5677_MIC_BUF_BYTES; +		ret |= rt5677_spi_copy_block(rt5677_dsp, 0, target); +	} + +	if (!ret) +		rt5677_dsp->mic_read_offset = target; +	return ret; +} + +/* + * A delayed work that streams audio samples from the DSP mic buffer to the + * dma_area of the pcm runtime via SPI. + */ +static void rt5677_spi_copy_work(struct work_struct *work) +{ +	struct rt5677_dsp *rt5677_dsp = +		container_of(work, struct rt5677_dsp, copy_work.work); +	struct snd_pcm_runtime *runtime; +	u32 mic_write_offset; +	size_t new_bytes, copy_bytes, period_bytes; +	unsigned int delay; +	int ret = 0; + +	/* Ensure runtime->dma_area buffer does not go away while copying. */ +	mutex_lock(&rt5677_dsp->dma_lock); +	if (!rt5677_dsp->substream) { +		dev_err(rt5677_dsp->dev, "No pcm substream\n"); +		goto done; +	} + +	runtime = rt5677_dsp->substream->runtime; + +	if (rt5677_spi_mic_write_offset(&mic_write_offset)) { +		dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +		goto done; +	} + +	/* If this is the first time that we've asked for streaming data after +	 * a hotword is fired, we should start reading from the previous 2 +	 * seconds of audio from wherever the mic_write_offset is currently. +	 */ +	if (rt5677_dsp->new_hotword) { +		rt5677_dsp->new_hotword = false; +		/* See if buffer wraparound happens */ +		if (mic_write_offset < RT5677_MIC_BUF_FIRST_READ_SIZE) +			rt5677_dsp->mic_read_offset = RT5677_MIC_BUF_BYTES - +					(RT5677_MIC_BUF_FIRST_READ_SIZE - +					mic_write_offset); +		else +			rt5677_dsp->mic_read_offset = mic_write_offset - +					RT5677_MIC_BUF_FIRST_READ_SIZE; +	} + +	/* Calculate the amount of new samples in bytes */ +	if (rt5677_dsp->mic_read_offset <= mic_write_offset) +		new_bytes = mic_write_offset - rt5677_dsp->mic_read_offset; +	else +		new_bytes = RT5677_MIC_BUF_BYTES + mic_write_offset +				- rt5677_dsp->mic_read_offset; + +	/* Copy all new samples from DSP mic buffer, one period at a time */ +	period_bytes = snd_pcm_lib_period_bytes(rt5677_dsp->substream); +	while (new_bytes) { +		copy_bytes = min(new_bytes, period_bytes +				- rt5677_dsp->avail_bytes); +		ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); +		if (ret) { +			dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +			goto done; +		} +		rt5677_dsp->avail_bytes += copy_bytes; +		if (rt5677_dsp->avail_bytes >= period_bytes) { +			snd_pcm_period_elapsed(rt5677_dsp->substream); +			rt5677_dsp->avail_bytes = 0; +		} +		new_bytes -= copy_bytes; +	} + +	delay = bytes_to_frames(runtime, period_bytes) / (runtime->rate / 1000); +	schedule_delayed_work(&rt5677_dsp->copy_work, msecs_to_jiffies(delay)); +done: +	mutex_unlock(&rt5677_dsp->dma_lock); +} + +static int rt5677_spi_pcm_new(struct snd_soc_component *component, +			      struct snd_soc_pcm_runtime *rtd) +{ +	snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, +					      NULL, 0, 0); +	return 0; +} + +static int rt5677_spi_pcm_probe(struct snd_soc_component *component) +{ +	struct rt5677_dsp *rt5677_dsp; + +	rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp), +			GFP_KERNEL); +	if (!rt5677_dsp) +		return -ENOMEM; +	rt5677_dsp->dev = &g_spi->dev; +	mutex_init(&rt5677_dsp->dma_lock); +	INIT_DELAYED_WORK(&rt5677_dsp->copy_work, rt5677_spi_copy_work); + +	snd_soc_component_set_drvdata(component, rt5677_dsp); +	return 0; +} + +static const struct snd_soc_component_driver rt5677_spi_dai_component = { +	.name		= DRV_NAME, +	.probe		= rt5677_spi_pcm_probe, +	.open		= rt5677_spi_pcm_open, +	.close		= rt5677_spi_pcm_close, +	.hw_params	= rt5677_spi_hw_params, +	.hw_free	= rt5677_spi_hw_free, +	.prepare	= rt5677_spi_prepare, +	.pointer	= rt5677_spi_pcm_pointer, +	.pcm_construct	= rt5677_spi_pcm_new, +}; +  /* Select a suitable transfer command for the next transfer to ensure   * the transfer address is always naturally aligned while minimizing   * the total number of transfers required. @@ -218,9 +579,45 @@ int rt5677_spi_write_firmware(u32 addr, const struct firmware *fw)  }  EXPORT_SYMBOL_GPL(rt5677_spi_write_firmware); +void rt5677_spi_hotword_detected(void) +{ +	struct rt5677_dsp *rt5677_dsp; + +	if (!g_spi) +		return; + +	rt5677_dsp = dev_get_drvdata(&g_spi->dev); +	if (!rt5677_dsp) { +		dev_err(&g_spi->dev, "Can't get rt5677_dsp\n"); +		return; +	} + +	mutex_lock(&rt5677_dsp->dma_lock); +	dev_info(rt5677_dsp->dev, "Hotword detected\n"); +	rt5677_dsp->new_hotword = true; +	mutex_unlock(&rt5677_dsp->dma_lock); + +	schedule_delayed_work(&rt5677_dsp->copy_work, 0); +} +EXPORT_SYMBOL_GPL(rt5677_spi_hotword_detected); +  static int rt5677_spi_probe(struct spi_device *spi)  { +	int ret; +  	g_spi = spi; + +	ret = snd_soc_register_component(&spi->dev, &rt5677_spi_dai_component, +					 &rt5677_spi_dai, 1); +	if (ret < 0) +		dev_err(&spi->dev, "Failed to register component.\n"); + +	return ret; +} + +static int rt5677_spi_remove(struct spi_device *spi) +{ +	snd_soc_unregister_component(&spi->dev);  	return 0;  } @@ -236,6 +633,7 @@ static struct spi_driver rt5677_spi_driver = {  		.acpi_match_table = ACPI_PTR(rt5677_spi_acpi_id),  	},  	.probe = rt5677_spi_probe, +	.remove = rt5677_spi_remove,  };  module_spi_driver(rt5677_spi_driver); diff --git a/sound/soc/codecs/rt5677-spi.h b/sound/soc/codecs/rt5677-spi.h index 6ba3369dc235..3af36ec928e9 100644 --- a/sound/soc/codecs/rt5677-spi.h +++ b/sound/soc/codecs/rt5677-spi.h @@ -12,5 +12,6 @@  int rt5677_spi_read(u32 addr, void *rxbuf, size_t len);  int rt5677_spi_write(u32 addr, const void *txbuf, size_t len);  int rt5677_spi_write_firmware(u32 addr, const struct firmware *fw); +void rt5677_spi_hotword_detected(void);  #endif /* __RT5677_SPI_H__ */ diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 315a3d39bc09..e9a051a50ab2 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -38,6 +38,10 @@  #define RT5677_DEVICE_ID 0x6327 +/* Register controlling boot vector */ +#define RT5677_DSP_BOOT_VECTOR		0x1801f090 +#define RT5677_MODEL_ADDR		0x5FFC9800 +  #define RT5677_PR_RANGE_BASE (0xff + 1)  #define RT5677_PR_SPACING 0x100 @@ -298,6 +302,7 @@ static bool rt5677_volatile_register(struct device *dev, unsigned int reg)  	case RT5677_I2C_MASTER_CTRL7:  	case RT5677_I2C_MASTER_CTRL8:  	case RT5677_HAP_GENE_CTRL2: +	case RT5677_PWR_ANLG2: /* Modified by DSP firmware */  	case RT5677_PWR_DSP_ST:  	case RT5677_PRIV_DATA:  	case RT5677_ASRC_22: @@ -308,6 +313,8 @@ static bool rt5677_volatile_register(struct device *dev, unsigned int reg)  	case RT5677_IRQ_CTRL1:  	case RT5677_IRQ_CTRL2:  	case RT5677_GPIO_ST: +	case RT5677_GPIO_CTRL1: /* Modified by DSP firmware */ +	case RT5677_GPIO_CTRL2: /* Modified by DSP firmware */  	case RT5677_DSP_INB1_SRC_CTRL4:  	case RT5677_DSP_INB2_SRC_CTRL4:  	case RT5677_DSP_INB3_SRC_CTRL4: @@ -686,10 +693,8 @@ static int rt5677_dsp_mode_i2c_read(  	return ret;  } -static void rt5677_set_dsp_mode(struct snd_soc_component *component, bool on) +static void rt5677_set_dsp_mode(struct rt5677_priv *rt5677, bool on)  { -	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); -  	if (on) {  		regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1,  			RT5677_PWR_DSP, RT5677_PWR_DSP); @@ -701,86 +706,259 @@ static void rt5677_set_dsp_mode(struct snd_soc_component *component, bool on)  	}  } +static unsigned int rt5677_set_vad_source(struct rt5677_priv *rt5677) +{ +	struct snd_soc_dapm_context *dapm = +			snd_soc_component_get_dapm(rt5677->component); +	/* Force dapm to sync before we enable the +	 * DSP to prevent write corruption +	 */ +	snd_soc_dapm_sync(dapm); + +	/* DMIC1 power = enabled +	 * DMIC CLK = 256 * fs / 12 +	 */ +	regmap_update_bits(rt5677->regmap, RT5677_DMIC_CTRL1, +		RT5677_DMIC_CLK_MASK, 5 << RT5677_DMIC_CLK_SFT); + +	/* I2S pre divide 2 = /6 (clk_sys2) */ +	regmap_update_bits(rt5677->regmap, RT5677_CLK_TREE_CTRL1, +		RT5677_I2S_PD2_MASK, RT5677_I2S_PD2_6); + +	/* DSP Clock = MCLK1 (bypassed PLL2) */ +	regmap_write(rt5677->regmap, RT5677_GLB_CLK2, +		RT5677_DSP_CLK_SRC_BYPASS); + +	/* SAD Threshold1 */ +	regmap_write(rt5677->regmap, RT5677_VAD_CTRL2, 0x013f); +	/* SAD Threshold2 */ +	regmap_write(rt5677->regmap, RT5677_VAD_CTRL3, 0x0ae5); +	/* SAD Sample Rate Converter = Up 6 (8K to 48K) +	 * SAD Output Sample Rate = Same as I2S +	 * SAD Threshold3 +	 */ +	regmap_update_bits(rt5677->regmap, RT5677_VAD_CTRL4, +		RT5677_VAD_OUT_SRC_RATE_MASK | RT5677_VAD_OUT_SRC_MASK | +		RT5677_VAD_LV_DIFF_MASK, 0x7f << RT5677_VAD_LV_DIFF_SFT); +	/* Minimum frame level within a pre-determined duration = 32 frames +	 * Bypass ADPCM Encoder/Decoder = Bypass ADPCM +	 * Automatic Push Data to SAD Buffer Once SAD Flag is triggered = enable +	 * SAD Buffer Over-Writing = enable +	 * SAD Buffer Pop Mode Control = disable +	 * SAD Buffer Push Mode Control = enable +	 * SAD Detector Control = enable +	 * SAD Function Control = enable +	 * SAD Function Reset = normal +	 */ +	regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, +		RT5677_VAD_FUNC_RESET | RT5677_VAD_FUNC_ENABLE | +		RT5677_VAD_DET_ENABLE | RT5677_VAD_BUF_PUSH | +		RT5677_VAD_BUF_OW | RT5677_VAD_FG2ENC | +		RT5677_VAD_ADPCM_BYPASS | 1 << RT5677_VAD_MIN_DUR_SFT); + +	/* VAD/SAD is not routed to the IRQ output (i.e. MX-BE[14] = 0), but it +	 * is routed to DSP_IRQ_0, so DSP firmware may use it to sleep and save +	 * power. See ALC5677 datasheet section 9.17 "GPIO, Interrupt and Jack +	 * Detection" for more info. +	 */ + +	/* Private register, no doc */ +	regmap_update_bits(rt5677->regmap, RT5677_PR_BASE + RT5677_BIAS_CUR4, +		0x0f00, 0x0100); + +	/* LDO2 output = 1.2V +	 * LDO1 output = 1.2V (LDO_IN = 1.8V) +	 */ +	regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, +		RT5677_LDO1_SEL_MASK | RT5677_LDO2_SEL_MASK, +		5 << RT5677_LDO1_SEL_SFT | 5 << RT5677_LDO2_SEL_SFT); + +	/* Codec core power =  power on +	 * LDO1 power = power on +	 */ +	regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, +		RT5677_PWR_CORE | RT5677_PWR_LDO1, +		RT5677_PWR_CORE | RT5677_PWR_LDO1); + +	/* Isolation for DCVDD4 = normal (set during probe) +	 * Isolation for DCVDD2 = normal (set during probe) +	 * Isolation for DSP = normal +	 * Isolation for Band 0~7 = disable +	 * Isolation for InBound 4~10 and OutBound 4~10 = disable +	 */ +	regmap_write(rt5677->regmap, RT5677_PWR_DSP2, +		RT5677_PWR_CORE_ISO | RT5677_PWR_DSP_ISO | +		RT5677_PWR_SR7_ISO | RT5677_PWR_SR6_ISO | +		RT5677_PWR_SR5_ISO | RT5677_PWR_SR4_ISO | +		RT5677_PWR_SR3_ISO | RT5677_PWR_SR2_ISO | +		RT5677_PWR_SR1_ISO | RT5677_PWR_SR0_ISO | +		RT5677_PWR_MLT_ISO); + +	/* System Band 0~7 = power on +	 * InBound 4~10 and OutBound 4~10 = power on +	 * DSP = power on +	 * DSP CPU = stop (will be set to "run" after firmware loaded) +	 */ +	regmap_write(rt5677->regmap, RT5677_PWR_DSP1, +		RT5677_PWR_SR7 | RT5677_PWR_SR6 | +		RT5677_PWR_SR5 | RT5677_PWR_SR4 | +		RT5677_PWR_SR3 | RT5677_PWR_SR2 | +		RT5677_PWR_SR1 | RT5677_PWR_SR0 | +		RT5677_PWR_MLT | RT5677_PWR_DSP | +		RT5677_PWR_DSP_CPU); + +	return 0; +} + +static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, +		unsigned int len) +{ +	struct snd_soc_component *component = rt5677->component; +	Elf32_Ehdr *elf_hdr; +	Elf32_Phdr *pr_hdr; +	Elf32_Half i; +	int ret = 0; + +	if (!buf || (len < sizeof(Elf32_Ehdr))) +		return -ENOMEM; + +	elf_hdr = (Elf32_Ehdr *)buf; +#ifndef EM_XTENSA +#define EM_XTENSA	94 +#endif +	if (strncmp(elf_hdr->e_ident, ELFMAG, sizeof(ELFMAG) - 1)) +		dev_err(component->dev, "Wrong ELF header prefix\n"); +	if (elf_hdr->e_ehsize != sizeof(Elf32_Ehdr)) +		dev_err(component->dev, "Wrong Elf header size\n"); +	if (elf_hdr->e_machine != EM_XTENSA) +		dev_err(component->dev, "Wrong DSP code file\n"); + +	if (len < elf_hdr->e_phoff) +		return -ENOMEM; +	pr_hdr = (Elf32_Phdr *)(buf + elf_hdr->e_phoff); +	for (i = 0; i < elf_hdr->e_phnum; i++) { +		/* TODO: handle p_memsz != p_filesz */ +		if (pr_hdr->p_paddr && pr_hdr->p_filesz) { +			dev_info(component->dev, "Load 0x%x bytes to 0x%x\n", +					pr_hdr->p_filesz, pr_hdr->p_paddr); + +			ret = rt5677_spi_write(pr_hdr->p_paddr, +					buf + pr_hdr->p_offset, +					pr_hdr->p_filesz); +			if (ret) +				dev_err(component->dev, "Load firmware failed %d\n", +						ret); +		} +		pr_hdr++; +	} +	return ret; +} + +static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) +{ +	const struct firmware *fwp; +	struct device *dev = rt5677->component->dev; +	int ret = 0; + +	/* Load dsp firmware from rt5677_elf_vad file */ +	ret = request_firmware(&fwp, "rt5677_elf_vad", dev); +	if (ret) { +		dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +		return ret; +	} +	dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +	ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +	release_firmware(fwp); +	return ret; +} +  static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on)  {  	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); -	static bool activity; -	int ret; +	rt5677->dsp_vad_en_request = on; +	rt5677->dsp_vad_en = on;  	if (!IS_ENABLED(CONFIG_SND_SOC_RT5677_SPI))  		return -ENXIO; -	if (on && !activity) { +	schedule_delayed_work(&rt5677->dsp_work, 0); +	return 0; +} + +static void rt5677_dsp_work(struct work_struct *work) +{ +	struct rt5677_priv *rt5677 = +		container_of(work, struct rt5677_priv, dsp_work.work); +	static bool activity; +	bool enable = rt5677->dsp_vad_en; +	int i, val; + + +	dev_info(rt5677->component->dev, "DSP VAD: enable=%d, activity=%d\n", +			enable, activity); + +	if (enable && !activity) {  		activity = true; -		regcache_cache_only(rt5677->regmap, false); -		regcache_cache_bypass(rt5677->regmap, true); +		/* Before a hotword is detected, GPIO1 pin is configured as IRQ +		 * output so that jack detect works. When a hotword is detected, +		 * the DSP firmware configures the GPIO1 pin as GPIO1 and +		 * drives a 1. rt5677_irq() is called after a rising edge on +		 * the GPIO1 pin, due to either jack detect event or hotword +		 * event, or both. All possible events are checked and handled +		 * in rt5677_irq() where GPIO1 pin is configured back to IRQ +		 * output if a hotword is detected. +		 */ -		regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x1, 0x1); -		regmap_update_bits(rt5677->regmap, -			RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0f00); -		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, -			RT5677_LDO1_SEL_MASK, 0x0); -		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, -			RT5677_PWR_LDO1, RT5677_PWR_LDO1); -		switch (rt5677->type) { -		case RT5677: -			regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, -				RT5677_MCLK_SRC_MASK, RT5677_MCLK2_SRC); -			regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK2, -				RT5677_PLL2_PR_SRC_MASK | -				RT5677_DSP_CLK_SRC_MASK, -				RT5677_PLL2_PR_SRC_MCLK2 | -				RT5677_DSP_CLK_SRC_BYPASS); -			break; -		case RT5676: -			regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK2, -				RT5677_DSP_CLK_SRC_MASK, -				RT5677_DSP_CLK_SRC_BYPASS); -			break; -		default: -			break; +		rt5677_set_vad_source(rt5677); +		rt5677_set_dsp_mode(rt5677, true); + +#define RT5677_BOOT_RETRY 20 +		for (i = 0; i < RT5677_BOOT_RETRY; i++) { +			regmap_read(rt5677->regmap, RT5677_PWR_DSP_ST, &val); +			if (val == 0x3ff) +				break; +			udelay(500);  		} -		regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x07ff); -		regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x07fd); -		rt5677_set_dsp_mode(component, true); - -		ret = request_firmware(&rt5677->fw1, RT5677_FIRMWARE1, -			component->dev); -		if (ret == 0) { -			rt5677_spi_write_firmware(0x50000000, rt5677->fw1); -			release_firmware(rt5677->fw1); +		if (i == RT5677_BOOT_RETRY && val != 0x3ff) { +			dev_err(rt5677->component->dev, "DSP Boot Timed Out!"); +			return;  		} -		ret = request_firmware(&rt5677->fw2, RT5677_FIRMWARE2, -			component->dev); -		if (ret == 0) { -			rt5677_spi_write_firmware(0x60000000, rt5677->fw2); -			release_firmware(rt5677->fw2); -		} +		/* Boot the firmware from IRAM instead of SRAM0. */ +		rt5677_dsp_mode_i2c_write_addr(rt5677, RT5677_DSP_BOOT_VECTOR, +			0x0009, 0x0003); +		rt5677_dsp_mode_i2c_write_addr(rt5677, RT5677_DSP_BOOT_VECTOR, +			0x0019, 0x0003); +		rt5677_dsp_mode_i2c_write_addr(rt5677, RT5677_DSP_BOOT_VECTOR, +			0x0009, 0x0003); -		regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x0); +		rt5677_load_dsp_from_file(rt5677); -		regcache_cache_bypass(rt5677->regmap, false); -		regcache_cache_only(rt5677->regmap, true); -	} else if (!on && activity) { +		/* Set DSP CPU to Run */ +		regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +			RT5677_PWR_DSP_CPU, 0x0); +	} else if (!enable && activity) {  		activity = false; -		regcache_cache_only(rt5677->regmap, false); -		regcache_cache_bypass(rt5677->regmap, true); +		/* Don't turn off the DSP while handling irqs */ +		mutex_lock(&rt5677->irq_lock); +		/* Set DSP CPU to Stop */ +		regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +			RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); -		regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x1); -		rt5677_set_dsp_mode(component, false); -		regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x0001); +		rt5677_set_dsp_mode(rt5677, false); -		regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec); +		/* Disable and clear VAD interrupt */ +		regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); -		regcache_cache_bypass(rt5677->regmap, false); -		regcache_mark_dirty(rt5677->regmap); -		regcache_sync(rt5677->regmap); -	} +		/* Set GPIO1 pin back to be IRQ output for jack detect */ +		regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +			RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); -	return 0; +		mutex_unlock(&rt5677->irq_lock); +	}  }  static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); @@ -805,7 +983,7 @@ static int rt5677_dsp_vad_get(struct snd_kcontrol *kcontrol,  	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);  	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); -	ucontrol->value.integer.value[0] = rt5677->dsp_vad_en; +	ucontrol->value.integer.value[0] = rt5677->dsp_vad_en_request;  	return 0;  } @@ -814,12 +992,8 @@ static int rt5677_dsp_vad_put(struct snd_kcontrol *kcontrol,  		struct snd_ctl_elem_value *ucontrol)  {  	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); -	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); - -	rt5677->dsp_vad_en = !!ucontrol->value.integer.value[0]; -	if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) -		rt5677_set_dsp_vad(component, rt5677->dsp_vad_en); +	rt5677_set_dsp_vad(component, !!ucontrol->value.integer.value[0]);  	return 0;  } @@ -3010,6 +3184,7 @@ static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = {  	SND_SOC_DAPM_AIF_OUT("AIF4TX", "AIF4 Capture", 0, SND_SOC_NOPM, 0, 0),  	SND_SOC_DAPM_AIF_IN("SLBRX", "SLIMBus Playback", 0, SND_SOC_NOPM, 0, 0),  	SND_SOC_DAPM_AIF_OUT("SLBTX", "SLIMBus Capture", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_OUT("DSPTX", "DSP Buffer", 0, SND_SOC_NOPM, 0, 0),  	/* Sidetone Mux */  	SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, @@ -3544,11 +3719,24 @@ static const struct snd_soc_dapm_route rt5677_dapm_routes[] = {  	{ "SLBTX", NULL, "SLB ADC3 Mux" },  	{ "SLBTX", NULL, "SLB ADC4 Mux" }, +	{ "DSPTX", NULL, "IB01 Bypass Mux" }, +  	{ "IB01 Mux", "IF1 DAC 01", "IF1 DAC01" },  	{ "IB01 Mux", "IF2 DAC 01", "IF2 DAC01" },  	{ "IB01 Mux", "SLB DAC 01", "SLB DAC01" },  	{ "IB01 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, -	{ "IB01 Mux", "VAD ADC/DAC1 FS", "DAC1 FS" }, +	/* The IB01 Mux controls the source for InBound0 and InBound1. +	 * When the mux option "VAD ADC/DAC1 FS" is selected, "VAD ADC" goes to +	 * InBound0 and "DAC1 FS" goes to InBound1. "VAD ADC" is used for +	 * hotwording. "DAC1 FS" is not used currently. +	 * +	 * Creating a common widget node for "VAD ADC" + "DAC1 FS" and +	 * connecting the common widget to IB01 Mux causes the issue where +	 * there is an active path going from system playback -> "DAC1 FS" -> +	 * IB01 Mux -> DSP Buffer -> hotword stream. This wrong path confuses +	 * DAPM. Therefore "DAC1 FS" is ignored for now. +	 */ +	{ "IB01 Mux", "VAD ADC/DAC1 FS", "VAD ADC Mux" },  	{ "IB01 Bypass Mux", "Bypass", "IB01 Mux" },  	{ "IB01 Bypass Mux", "Pass SRC", "IB01 Mux" }, @@ -4457,14 +4645,15 @@ static int rt5677_set_bias_level(struct snd_soc_component *component,  			enum snd_soc_bias_level level)  {  	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); +	enum snd_soc_bias_level prev_bias = +		snd_soc_component_get_bias_level(component);  	switch (level) {  	case SND_SOC_BIAS_ON:  		break;  	case SND_SOC_BIAS_PREPARE: -		if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { -			rt5677_set_dsp_vad(component, false); +		if (prev_bias == SND_SOC_BIAS_STANDBY) {  			regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1,  				RT5677_LDO1_SEL_MASK | RT5677_LDO2_SEL_MASK, @@ -4488,9 +4677,25 @@ static int rt5677_set_bias_level(struct snd_soc_component *component,  		break;  	case SND_SOC_BIAS_STANDBY: +		if (prev_bias == SND_SOC_BIAS_OFF && +				rt5677->dsp_vad_en_request) { +			/* Re-enable the DSP if it was turned off at suspend */ +			rt5677->dsp_vad_en = true; +			/* The delay is to wait for MCLK */ +			schedule_delayed_work(&rt5677->dsp_work, +					msecs_to_jiffies(1000)); +		}  		break;  	case SND_SOC_BIAS_OFF: +		flush_delayed_work(&rt5677->dsp_work); +		if (rt5677->is_dsp_mode) { +			/* Turn off the DSP before suspend */ +			rt5677->dsp_vad_en = false; +			schedule_delayed_work(&rt5677->dsp_work, 0); +			flush_delayed_work(&rt5677->dsp_work); +		} +  		regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x1, 0x0);  		regmap_write(rt5677->regmap, RT5677_PWR_DIG1, 0x0000);  		regmap_write(rt5677->regmap, RT5677_PWR_ANLG1, @@ -4740,6 +4945,8 @@ static void rt5677_remove(struct snd_soc_component *component)  {  	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); +	cancel_delayed_work_sync(&rt5677->dsp_work); +  	regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec);  	gpiod_set_value_cansleep(rt5677->pow_ldo2, 0);  	gpiod_set_value_cansleep(rt5677->reset_pin, 1); @@ -4750,6 +4957,11 @@ static int rt5677_suspend(struct snd_soc_component *component)  {  	struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); +	if (rt5677->irq) { +		cancel_delayed_work_sync(&rt5677->resume_irq_check); +		disable_irq(rt5677->irq); +	} +  	if (!rt5677->dsp_vad_en) {  		regcache_cache_only(rt5677->regmap, true);  		regcache_mark_dirty(rt5677->regmap); @@ -4778,6 +4990,11 @@ static int rt5677_resume(struct snd_soc_component *component)  		regcache_sync(rt5677->regmap);  	} +	if (rt5677->irq) { +		enable_irq(rt5677->irq); +		schedule_delayed_work(&rt5677->resume_irq_check, 0); +	} +  	return 0;  }  #else @@ -4842,6 +5059,11 @@ static const struct snd_soc_dai_ops rt5677_aif_dai_ops = {  	.set_tdm_slot = rt5677_set_tdm_slot,  }; +static const struct snd_soc_dai_ops rt5677_dsp_dai_ops = { +	.set_sysclk = rt5677_set_dai_sysclk, +	.set_pll = rt5677_set_dai_pll, +}; +  static struct snd_soc_dai_driver rt5677_dai[] = {  	{  		.name = "rt5677-aif1", @@ -4938,6 +5160,18 @@ static struct snd_soc_dai_driver rt5677_dai[] = {  		},  		.ops = &rt5677_aif_dai_ops,  	}, +	{ +		.name = "rt5677-dspbuffer", +		.id = RT5677_DSPBUFF, +		.capture = { +			.stream_name = "DSP Buffer", +			.channels_min = 1, +			.channels_max = 1, +			.rates = SNDRV_PCM_RATE_16000, +			.formats = SNDRV_PCM_FMTBIT_S16_LE, +		}, +		.ops = &rt5677_dsp_dai_ops, +	},  };  static const struct snd_soc_component_driver soc_component_dev_rt5677 = { @@ -5073,6 +5307,28 @@ static const struct rt5677_irq_desc rt5677_irq_descs[] = {  	},  }; +static bool rt5677_check_hotword(struct rt5677_priv *rt5677) +{ +	int reg_gpio; + +	if (!rt5677->is_dsp_mode) +		return false; + +	if (regmap_read(rt5677->regmap, RT5677_GPIO_CTRL1, ®_gpio)) +		return false; + +	/* Firmware sets GPIO1 pin to be GPIO1 after hotword is detected */ +	if ((reg_gpio & RT5677_GPIO1_PIN_MASK) == RT5677_GPIO1_PIN_IRQ) +		return false; + +	/* Set GPIO1 pin back to be IRQ output for jack detect */ +	regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +			RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); + +	rt5677_spi_hotword_detected(); +	return true; +} +  static irqreturn_t rt5677_irq(int unused, void *data)  {  	struct rt5677_priv *rt5677 = data; @@ -5118,7 +5374,13 @@ static irqreturn_t rt5677_irq(int unused, void *data)  				reg_irq ^= rt5677_irq_descs[i].polarity_mask;  			}  		} -		if (!irq_fired) + +		/* Exit the loop only when we know for sure that GPIO1 pin +		 * was low at some point since irq_lock was acquired. Any event +		 * after that point creates a rising edge that triggers another +		 * call to rt5677_irq(). +		 */ +		if (!irq_fired && !rt5677_check_hotword(rt5677))  			goto exit;  		ret = regmap_write(rt5677->regmap, RT5677_IRQ_CTRL1, reg_irq); @@ -5129,6 +5391,7 @@ static irqreturn_t rt5677_irq(int unused, void *data)  		}  	}  exit: +	WARN_ON_ONCE(loop == 20);  	mutex_unlock(&rt5677->irq_lock);  	if (irq_fired)  		return IRQ_HANDLED; @@ -5136,6 +5399,39 @@ exit:  		return IRQ_NONE;  } +static void rt5677_resume_irq_check(struct work_struct *work) +{ +	int i, virq; +	struct rt5677_priv *rt5677 = +		container_of(work, struct rt5677_priv, resume_irq_check.work); + +	/* This is needed to check and clear the interrupt status register +	 * at resume. If the headset is plugged/unplugged when the device is +	 * fully suspended, there won't be a rising edge at resume to trigger +	 * the interrupt. Without this, we miss the next unplug/plug event. +	 */ +	rt5677_irq(0, rt5677); + +	/* Call all enabled jack detect irq handlers again. This is needed in +	 * addition to the above check for a corner case caused by jack gpio +	 * debounce. After codec irq is disabled at suspend, the delayed work +	 * scheduled by soc-jack may run and read wrong jack gpio values, since +	 * the regmap is in cache only mode. At resume, there is no irq because +	 * rt5677_irq has already ran and cleared the irq status at suspend. +	 * Without this explicit check, unplug the headset right after suspend +	 * starts, then after resume the headset is still shown as plugged in. +	 */ +	mutex_lock(&rt5677->irq_lock); +	for (i = 0; i < RT5677_IRQ_NUM; i++) { +		if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { +			virq = irq_find_mapping(rt5677->domain, i); +			if (virq) +				handle_nested_irq(virq); +		} +	} +	mutex_unlock(&rt5677->irq_lock); +} +  static void rt5677_irq_bus_lock(struct irq_data *data)  {  	struct rt5677_priv *rt5677 = irq_data_get_irq_chip_data(data); @@ -5211,6 +5507,7 @@ static int rt5677_init_irq(struct i2c_client *i2c)  	}  	mutex_init(&rt5677->irq_lock); +	INIT_DELAYED_WORK(&rt5677->resume_irq_check, rt5677_resume_irq_check);  	/*  	 * Select RC as the debounce clock so that GPIO works even when @@ -5256,6 +5553,8 @@ static int rt5677_init_irq(struct i2c_client *i2c)  	if (ret)  		dev_err(&i2c->dev, "Failed to request IRQ: %d\n", ret); +	rt5677->irq = i2c->irq; +  	return ret;  } @@ -5271,6 +5570,8 @@ static int rt5677_i2c_probe(struct i2c_client *i2c)  		return -ENOMEM;  	rt5677->dev = &i2c->dev; +	rt5677->set_dsp_vad = rt5677_set_dsp_vad; +	INIT_DELAYED_WORK(&rt5677->dsp_work, rt5677_dsp_work);  	i2c_set_clientdata(i2c, rt5677);  	if (i2c->dev.of_node) { diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h index 213f4b8ca269..944ae02aafc2 100644 --- a/sound/soc/codecs/rt5677.h +++ b/sound/soc/codecs/rt5677.h @@ -1336,6 +1336,8 @@  #define RT5677_PLL_M_SFT			12  #define RT5677_PLL_M_BP				(0x1 << 11)  #define RT5677_PLL_M_BP_SFT			11 +#define RT5677_PLL_UPDATE_PLL1			(0x1 << 1) +#define RT5677_PLL_UPDATE_PLL1_SFT		1  /* Global Clock Control 1 (0x80) */  #define RT5677_SCLK_SRC_MASK			(0x3 << 14) @@ -1730,6 +1732,7 @@ enum {  	RT5677_AIF4,  	RT5677_AIF5,  	RT5677_AIFS, +	RT5677_DSPBUFF,  };  enum { @@ -1845,14 +1848,20 @@ struct rt5677_priv {  #ifdef CONFIG_GPIOLIB  	struct gpio_chip gpio_chip;  #endif -	bool dsp_vad_en; +	bool dsp_vad_en_request; /* DSP VAD enable/disable request */ +	bool dsp_vad_en; /* dsp_work parameter */  	bool is_dsp_mode;  	bool is_vref_slow; +	struct delayed_work dsp_work;  	/* Interrupt handling */  	struct irq_domain *domain;  	struct mutex irq_lock;  	unsigned int irq_en; +	struct delayed_work resume_irq_check; +	int irq; + +	int (*set_dsp_vad)(struct snd_soc_component *component, bool on);  };  int rt5677_sel_asrc_clk_src(struct snd_soc_component *component, diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index c50b75ce82e0..b1713fffa3eb 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -44,6 +44,7 @@ static const struct rt5682_platform_data i2s_default_platform_data = {  	.dmic1_data_pin = RT5682_DMIC1_DATA_GPIO2,  	.dmic1_clk_pin = RT5682_DMIC1_CLK_GPIO3,  	.jd_src = RT5682_JD1, +	.btndet_delay = 16,  };  struct rt5682_priv { @@ -1002,6 +1003,7 @@ static int rt5682_set_jack_detect(struct snd_soc_component *component,  				   RT5682_JD1_EN_MASK, RT5682_JD1_DIS);  		regmap_update_bits(rt5682->regmap, RT5682_RC_CLK_CTRL,  				   RT5682_POW_JDH | RT5682_POW_JDL, 0); +		cancel_delayed_work_sync(&rt5682->jack_detect_work);  		return 0;  	} @@ -1026,6 +1028,18 @@ static int rt5682_set_jack_detect(struct snd_soc_component *component,  		regmap_update_bits(rt5682->regmap, RT5682_IRQ_CTRL_2,  			RT5682_JD1_EN_MASK | RT5682_JD1_POL_MASK,  			RT5682_JD1_EN | RT5682_JD1_POL_NOR); +		regmap_update_bits(rt5682->regmap, RT5682_4BTN_IL_CMD_4, +			0x7f7f, (rt5682->pdata.btndet_delay << 8 | +			rt5682->pdata.btndet_delay)); +		regmap_update_bits(rt5682->regmap, RT5682_4BTN_IL_CMD_5, +			0x7f7f, (rt5682->pdata.btndet_delay << 8 | +			rt5682->pdata.btndet_delay)); +		regmap_update_bits(rt5682->regmap, RT5682_4BTN_IL_CMD_6, +			0x7f7f, (rt5682->pdata.btndet_delay << 8 | +			rt5682->pdata.btndet_delay)); +		regmap_update_bits(rt5682->regmap, RT5682_4BTN_IL_CMD_7, +			0x7f7f, (rt5682->pdata.btndet_delay << 8 | +			rt5682->pdata.btndet_delay));  		mod_delayed_work(system_power_efficient_wq,  			   &rt5682->jack_detect_work, msecs_to_jiffies(250));  		break; @@ -1450,28 +1464,6 @@ static const struct snd_kcontrol_new hpor_switch =  	SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5682_HP_CTRL_1,  					RT5682_R_MUTE_SFT, 1, 1); -static int rt5682_charge_pump_event(struct snd_soc_dapm_widget *w, -	struct snd_kcontrol *kcontrol, int event) -{ -	struct snd_soc_component *component = -		snd_soc_dapm_to_component(w->dapm); - -	switch (event) { -	case SND_SOC_DAPM_PRE_PMU: -		snd_soc_component_update_bits(component, -			RT5682_HP_CHARGE_PUMP_1, RT5682_PM_HP_MASK, RT5682_PM_HP_HV); -		break; -	case SND_SOC_DAPM_POST_PMD: -		snd_soc_component_update_bits(component, -			RT5682_HP_CHARGE_PUMP_1, RT5682_PM_HP_MASK, RT5682_PM_HP_LV); -		break; -	default: -		return 0; -	} - -	return 0; -} -  static int rt5682_hp_event(struct snd_soc_dapm_widget *w,  	struct snd_kcontrol *kcontrol, int event)  { @@ -1755,8 +1747,7 @@ static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = {  	SND_SOC_DAPM_SUPPLY("HP Amp R", RT5682_PWR_ANLG_1,  		RT5682_PWR_HA_R_BIT, 0, NULL, 0),  	SND_SOC_DAPM_SUPPLY_S("Charge Pump", 1, RT5682_DEPOP_1, -		RT5682_PUMP_EN_SFT, 0, rt5682_charge_pump_event, -		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +		RT5682_PUMP_EN_SFT, 0, NULL, 0),  	SND_SOC_DAPM_SUPPLY_S("Capless", 2, RT5682_DEPOP_1,  		RT5682_CAPLESS_EN_SFT, 0, NULL, 0), @@ -2467,6 +2458,8 @@ static int rt5682_parse_dt(struct rt5682_priv *rt5682, struct device *dev)  		&rt5682->pdata.dmic1_clk_pin);  	device_property_read_u32(dev, "realtek,jd-src",  		&rt5682->pdata.jd_src); +	device_property_read_u32(dev, "realtek,btndet-delay", +		&rt5682->pdata.btndet_delay);  	rt5682->pdata.ldo1_en = of_get_named_gpio(dev->of_node,  		"realtek,ldo1-en-gpios", 0); @@ -2654,6 +2647,8 @@ static int rt5682_i2c_probe(struct i2c_client *i2c,  			RT5682_HPA_CP_BIAS_CTRL_MASK, RT5682_HPA_CP_BIAS_3UA);  	regmap_update_bits(rt5682->regmap, RT5682_CHARGE_PUMP_1,  			RT5682_CP_CLK_HP_MASK, RT5682_CP_CLK_HP_300KHZ); +	regmap_update_bits(rt5682->regmap, RT5682_HP_CHARGE_PUMP_1, +			RT5682_PM_HP_MASK, RT5682_PM_HP_HV);  	INIT_DELAYED_WORK(&rt5682->jack_detect_work,  				rt5682_jack_detect_handler); diff --git a/sound/soc/codecs/tas2562.c b/sound/soc/codecs/tas2562.c new file mode 100644 index 000000000000..729acd874c48 --- /dev/null +++ b/sound/soc/codecs/tas2562.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for the Texas Instruments TAS2562 CODEC +// Copyright (C) 2019 Texas Instruments Inc. + + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "tas2562.h" + +#define TAS2562_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ +			 SNDRV_PCM_FORMAT_S32_LE) + +struct tas2562_data { +	struct snd_soc_component *component; +	struct gpio_desc *sdz_gpio; +	struct regmap *regmap; +	struct device *dev; +	struct i2c_client *client; +	int v_sense_slot; +	int i_sense_slot; +}; + +static int tas2562_set_bias_level(struct snd_soc_component *component, +				 enum snd_soc_bias_level level) +{ +	struct tas2562_data *tas2562 = +			snd_soc_component_get_drvdata(component); + +	switch (level) { +	case SND_SOC_BIAS_ON: +		snd_soc_component_update_bits(component, +			TAS2562_PWR_CTRL, +			TAS2562_MODE_MASK, TAS2562_ACTIVE); +		break; +	case SND_SOC_BIAS_STANDBY: +	case SND_SOC_BIAS_PREPARE: +		snd_soc_component_update_bits(component, +			TAS2562_PWR_CTRL, +			TAS2562_MODE_MASK, TAS2562_MUTE); +		break; +	case SND_SOC_BIAS_OFF: +		snd_soc_component_update_bits(component, +			TAS2562_PWR_CTRL, +			TAS2562_MODE_MASK, TAS2562_SHUTDOWN); +		break; + +	default: +		dev_err(tas2562->dev, +				"wrong power level setting %d\n", level); +		return -EINVAL; +	} + +	return 0; +} + +static int tas2562_set_samplerate(struct tas2562_data *tas2562, int samplerate) +{ +	int samp_rate; +	int ramp_rate; + +	switch (samplerate) { +	case 7350: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_7305_8KHZ; +		break; +	case 8000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_7305_8KHZ; +		break; +	case 14700: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_14_7_16KHZ; +		break; +	case 16000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_14_7_16KHZ; +		break; +	case 22050: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_22_05_24KHZ; +		break; +	case 24000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_22_05_24KHZ; +		break; +	case 29400: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_29_4_32KHZ; +		break; +	case 32000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_29_4_32KHZ; +		break; +	case 44100: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_44_1_48KHZ; +		break; +	case 48000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_44_1_48KHZ; +		break; +	case 88200: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_88_2_96KHZ; +		break; +	case 96000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_88_2_96KHZ; +		break; +	case 176400: +		ramp_rate = TAS2562_TDM_CFG0_RAMPRATE_44_1; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_176_4_192KHZ; +		break; +	case 192000: +		ramp_rate = 0; +		samp_rate = TAS2562_TDM_CFG0_SAMPRATE_176_4_192KHZ; +		break; +	default: +		dev_info(tas2562->dev, "%s, unsupported sample rate, %d\n", +			__func__, samplerate); +		return -EINVAL; +	} + +	snd_soc_component_update_bits(tas2562->component, TAS2562_TDM_CFG0, +		TAS2562_TDM_CFG0_RAMPRATE_MASK,	ramp_rate); +	snd_soc_component_update_bits(tas2562->component, TAS2562_TDM_CFG0, +		TAS2562_TDM_CFG0_SAMPRATE_MASK,	samp_rate); + +	return 0; +} + +static int tas2562_set_dai_tdm_slot(struct snd_soc_dai *dai, +		unsigned int tx_mask, unsigned int rx_mask, +		int slots, int slot_width) +{ +	struct snd_soc_component *component = dai->component; +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); +	int ret = 0; + +	switch (slot_width) { +	case 16: +		ret = snd_soc_component_update_bits(component, +						    TAS2562_TDM_CFG2, +						    TAS2562_TDM_CFG2_RXLEN_MASK, +						    TAS2562_TDM_CFG2_RXLEN_16B); +		break; +	case 24: +		ret = snd_soc_component_update_bits(component, +						    TAS2562_TDM_CFG2, +						    TAS2562_TDM_CFG2_RXLEN_MASK, +						    TAS2562_TDM_CFG2_RXLEN_24B); +		break; +	case 32: +		ret = snd_soc_component_update_bits(component, +						    TAS2562_TDM_CFG2, +						    TAS2562_TDM_CFG2_RXLEN_MASK, +						    TAS2562_TDM_CFG2_RXLEN_32B); +		break; + +	case 0: +		/* Do not change slot width */ +		break; +	default: +		dev_err(tas2562->dev, "slot width not supported"); +		ret = -EINVAL; +	} + +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2562_set_bitwidth(struct tas2562_data *tas2562, int bitwidth) +{ +	int ret; + +	switch (bitwidth) { +	case SNDRV_PCM_FORMAT_S16_LE: +		snd_soc_component_update_bits(tas2562->component, +					      TAS2562_TDM_CFG2, +					      TAS2562_TDM_CFG2_RXWLEN_MASK, +					      TAS2562_TDM_CFG2_RXWLEN_16B); +		tas2562->v_sense_slot = tas2562->i_sense_slot + 2; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		snd_soc_component_update_bits(tas2562->component, +					      TAS2562_TDM_CFG2, +					      TAS2562_TDM_CFG2_RXWLEN_MASK, +					      TAS2562_TDM_CFG2_RXWLEN_24B); +		tas2562->v_sense_slot = tas2562->i_sense_slot + 4; +		break; +	case SNDRV_PCM_FORMAT_S32_LE: +		snd_soc_component_update_bits(tas2562->component, +					      TAS2562_TDM_CFG2, +					      TAS2562_TDM_CFG2_RXWLEN_MASK, +					      TAS2562_TDM_CFG2_RXWLEN_32B); +		tas2562->v_sense_slot = tas2562->i_sense_slot + 4; +		break; + +	default: +		dev_info(tas2562->dev, "Not supported params format\n"); +	} + +	ret = snd_soc_component_update_bits(tas2562->component, +		TAS2562_TDM_CFG5, +		TAS2562_TDM_CFG5_VSNS_EN | TAS2562_TDM_CFG5_VSNS_SLOT_MASK, +		TAS2562_TDM_CFG5_VSNS_EN | tas2562->v_sense_slot); +	if (ret < 0) +		return ret; + +	ret = snd_soc_component_update_bits(tas2562->component, +		TAS2562_TDM_CFG6, +		TAS2562_TDM_CFG6_ISNS_EN | TAS2562_TDM_CFG6_ISNS_SLOT_MASK, +		TAS2562_TDM_CFG6_ISNS_EN | tas2562->i_sense_slot); +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2562_hw_params(struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *params, +			     struct snd_soc_dai *dai) +{ +	struct snd_soc_component *component = dai->component; +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); +	int ret; + +	ret = tas2562_set_bitwidth(tas2562, params_format(params)); +	if (ret) { +		dev_err(tas2562->dev, "set bitwidth failed, %d\n", ret); +		return ret; +	} + +	ret = tas2562_set_samplerate(tas2562, params_rate(params)); +	if (ret) +		dev_err(tas2562->dev, "set bitwidth failed, %d\n", ret); + +	return ret; +} + +static int tas2562_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	struct snd_soc_component *component = dai->component; +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); +	u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; +	int ret; + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		asi_cfg_1 = 0; +		break; +	case SND_SOC_DAIFMT_IB_NF: +		asi_cfg_1 |= TAS2562_TDM_CFG1_RX_FALLING; +		break; +	default: +		dev_err(tas2562->dev, "ASI format Inverse is not found\n"); +		return -EINVAL; +	} + +	ret = snd_soc_component_update_bits(component, TAS2562_TDM_CFG1, +					    TAS2562_TDM_CFG1_RX_EDGE_MASK, +					    asi_cfg_1); +	if (ret < 0) { +		dev_err(tas2562->dev, "Failed to set RX edge\n"); +		return ret; +	} + +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case (SND_SOC_DAIFMT_I2S): +	case (SND_SOC_DAIFMT_DSP_A): +	case (SND_SOC_DAIFMT_DSP_B): +		tdm_rx_start_slot = BIT(1); +		break; +	case (SND_SOC_DAIFMT_LEFT_J): +		tdm_rx_start_slot = 0; +		break; +	default: +		dev_err(tas2562->dev, "DAI Format is not found, fmt=0x%x\n", +			fmt); +		ret = -EINVAL; +		break; +	} + +	ret = snd_soc_component_update_bits(component, TAS2562_TDM_CFG1, +					    TAS2562_TDM_CFG1_RX_OFFSET_MASK, +					    tdm_rx_start_slot); + +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2562_mute(struct snd_soc_dai *dai, int mute) +{ +	struct snd_soc_component *component = dai->component; + +	return snd_soc_component_update_bits(component, TAS2562_PWR_CTRL, +					     TAS2562_MODE_MASK, +					     mute ? TAS2562_MUTE : 0); +} + +static int tas2562_codec_probe(struct snd_soc_component *component) +{ +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); +	int ret; + +	tas2562->component = component; + +	if (tas2562->sdz_gpio) +		gpiod_set_value_cansleep(tas2562->sdz_gpio, 1); + +	ret = snd_soc_component_update_bits(component, TAS2562_PWR_CTRL, +					    TAS2562_MODE_MASK, TAS2562_MUTE); +	if (ret < 0) +		return ret; + +	return 0; +} + +#ifdef CONFIG_PM +static int tas2562_suspend(struct snd_soc_component *component) +{ +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); + +	regcache_cache_only(tas2562->regmap, true); +	regcache_mark_dirty(tas2562->regmap); + +	if (tas2562->sdz_gpio) +		gpiod_set_value_cansleep(tas2562->sdz_gpio, 0); + +	return 0; +} + +static int tas2562_resume(struct snd_soc_component *component) +{ +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); + +	if (tas2562->sdz_gpio) +		gpiod_set_value_cansleep(tas2562->sdz_gpio, 1); + +	regcache_cache_only(tas2562->regmap, false); + +	return regcache_sync(tas2562->regmap); +} +#else +#define tas2562_suspend NULL +#define tas2562_resume NULL +#endif + +static const char * const tas2562_ASI1_src[] = { +	"I2C offset", "Left", "Right", "LeftRightDiv2", +}; + +static SOC_ENUM_SINGLE_DECL(tas2562_ASI1_src_enum, TAS2562_TDM_CFG2, 4, +			    tas2562_ASI1_src); + +static const struct snd_kcontrol_new tas2562_asi1_mux = +	SOC_DAPM_ENUM("ASI1 Source", tas2562_ASI1_src_enum); + +static int tas2562_dac_event(struct snd_soc_dapm_widget *w, +			     struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_component *component = +					snd_soc_dapm_to_component(w->dapm); +	struct tas2562_data *tas2562 = snd_soc_component_get_drvdata(component); + +	switch (event) { +	case SND_SOC_DAPM_POST_PMU: +		dev_info(tas2562->dev, "SND_SOC_DAPM_POST_PMU\n"); +		break; +	case SND_SOC_DAPM_PRE_PMD: +		dev_info(tas2562->dev, "SND_SOC_DAPM_PRE_PMD\n"); +		break; +	default: +		break; +	} + +	return 0; +} + +static DECLARE_TLV_DB_SCALE(tas2562_dac_tlv, 850, 50, 0); + +static const struct snd_kcontrol_new isense_switch = +	SOC_DAPM_SINGLE("Switch", TAS2562_PWR_CTRL, TAS2562_ISENSE_POWER_EN, +			1, 1); + +static const struct snd_kcontrol_new vsense_switch = +	SOC_DAPM_SINGLE("Switch", TAS2562_PWR_CTRL, TAS2562_VSENSE_POWER_EN, +			1, 1); + +static const struct snd_kcontrol_new tas2562_snd_controls[] = { +	SOC_SINGLE_TLV("Amp Gain Volume", TAS2562_PB_CFG1, 0, 0x1c, 0, +		       tas2562_dac_tlv), +}; + +static const struct snd_soc_dapm_widget tas2562_dapm_widgets[] = { +	SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, &tas2562_asi1_mux), +	SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2562_dac_event, +			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +	SND_SOC_DAPM_SWITCH("ISENSE", TAS2562_PWR_CTRL, 3, 1, &isense_switch), +	SND_SOC_DAPM_SWITCH("VSENSE", TAS2562_PWR_CTRL, 2, 1, &vsense_switch), +	SND_SOC_DAPM_SIGGEN("VMON"), +	SND_SOC_DAPM_SIGGEN("IMON"), +	SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static const struct snd_soc_dapm_route tas2562_audio_map[] = { +	{"ASI1 Sel", "I2C offset", "ASI1"}, +	{"ASI1 Sel", "Left", "ASI1"}, +	{"ASI1 Sel", "Right", "ASI1"}, +	{"ASI1 Sel", "LeftRightDiv2", "ASI1"}, +	{ "DAC", NULL, "DAC IN" }, +	{ "OUT", NULL, "DAC" }, +	{"ISENSE", "Switch", "IMON"}, +	{"VSENSE", "Switch", "VMON"}, +}; + +static const struct snd_soc_component_driver soc_component_dev_tas2562 = { +	.probe			= tas2562_codec_probe, +	.suspend		= tas2562_suspend, +	.resume			= tas2562_resume, +	.set_bias_level		= tas2562_set_bias_level, +	.controls		= tas2562_snd_controls, +	.num_controls		= ARRAY_SIZE(tas2562_snd_controls), +	.dapm_widgets		= tas2562_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(tas2562_dapm_widgets), +	.dapm_routes		= tas2562_audio_map, +	.num_dapm_routes	= ARRAY_SIZE(tas2562_audio_map), +	.idle_bias_on		= 1, +	.use_pmdown_time	= 1, +	.endianness		= 1, +	.non_legacy_dai_naming	= 1, +}; + +static const struct snd_soc_dai_ops tas2562_speaker_dai_ops = { +	.hw_params	= tas2562_hw_params, +	.set_fmt	= tas2562_set_dai_fmt, +	.set_tdm_slot	= tas2562_set_dai_tdm_slot, +	.digital_mute	= tas2562_mute, +}; + +static struct snd_soc_dai_driver tas2562_dai[] = { +	{ +		.name = "tas2562-amplifier", +		.id = 0, +		.playback = { +			.stream_name    = "ASI1 Playback", +			.channels_min   = 2, +			.channels_max   = 2, +			.rates      = SNDRV_PCM_RATE_8000_192000, +			.formats    = TAS2562_FORMATS, +		}, +		.ops = &tas2562_speaker_dai_ops, +	}, +}; + +static const struct regmap_range_cfg tas2562_ranges[] = { +	{ +		.range_min = 0, +		.range_max = 5 * 128, +		.selector_reg = TAS2562_PAGE_CTRL, +		.selector_mask = 0xff, +		.selector_shift = 0, +		.window_start = 0, +		.window_len = 128, +	}, +}; + +static const struct reg_default tas2562_reg_defaults[] = { +	{ TAS2562_PAGE_CTRL, 0x00 }, +	{ TAS2562_SW_RESET, 0x00 }, +	{ TAS2562_PWR_CTRL, 0x0e }, +	{ TAS2562_PB_CFG1, 0x20 }, +	{ TAS2562_TDM_CFG0, 0x09 }, +	{ TAS2562_TDM_CFG1, 0x02 }, +}; + +static const struct regmap_config tas2562_regmap_config = { +	.reg_bits = 8, +	.val_bits = 8, + +	.max_register = 5 * 128, +	.cache_type = REGCACHE_RBTREE, +	.reg_defaults = tas2562_reg_defaults, +	.num_reg_defaults = ARRAY_SIZE(tas2562_reg_defaults), +	.ranges = tas2562_ranges, +	.num_ranges = ARRAY_SIZE(tas2562_ranges), +}; + +static int tas2562_parse_dt(struct tas2562_data *tas2562) +{ +	struct device *dev = tas2562->dev; +	int ret = 0; + +	tas2562->sdz_gpio = devm_gpiod_get_optional(dev, "shut-down-gpio", +						      GPIOD_OUT_HIGH); +	if (IS_ERR(tas2562->sdz_gpio)) { +		if (PTR_ERR(tas2562->sdz_gpio) == -EPROBE_DEFER) { +			tas2562->sdz_gpio = NULL; +			return -EPROBE_DEFER; +		} +	} + +	ret = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no", +			&tas2562->i_sense_slot); +	if (ret) +		dev_err(dev, "Looking up %s property failed %d\n", +			"ti,imon-slot-no", ret); + +	return ret; +} + +static int tas2562_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	struct device *dev = &client->dev; +	struct tas2562_data *data; +	int ret; + +	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	data->client = client; +	data->dev = &client->dev; + +	tas2562_parse_dt(data); + +	data->regmap = devm_regmap_init_i2c(client, &tas2562_regmap_config); +	if (IS_ERR(data->regmap)) { +		ret = PTR_ERR(data->regmap); +		dev_err(dev, "failed to allocate register map: %d\n", ret); +		return ret; +	} + +	dev_set_drvdata(&client->dev, data); + +	return devm_snd_soc_register_component(dev, &soc_component_dev_tas2562, +					       tas2562_dai, +					       ARRAY_SIZE(tas2562_dai)); + +} + +static const struct i2c_device_id tas2562_id[] = { +	{ "tas2562", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tas2562_id); + +static const struct of_device_id tas2562_of_match[] = { +	{ .compatible = "ti,tas2562", }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, tas2562_of_match); + +static struct i2c_driver tas2562_i2c_driver = { +	.driver = { +		.name = "tas2562", +		.of_match_table = of_match_ptr(tas2562_of_match), +	}, +	.probe = tas2562_probe, +	.id_table = tas2562_id, +}; + +module_i2c_driver(tas2562_i2c_driver); + +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_DESCRIPTION("TAS2562 Audio amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas2562.h b/sound/soc/codecs/tas2562.h new file mode 100644 index 000000000000..62e659ab786d --- /dev/null +++ b/sound/soc/codecs/tas2562.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tas2562.h - ALSA SoC Texas Instruments TAS2562 Mono Audio Amplifier + * + * Copyright (C) 2019 Texas Instruments Incorporated -  http://www.ti.com + * + * Author: Dan Murphy <dmurphy@ti.com> + */ + +#ifndef __TAS2562_H__ +#define __TAS2562_H__ + +#define TAS2562_PAGE_CTRL	0x00 + +#define TAS2562_REG(page, reg)	((page * 128) + reg) + +#define TAS2562_SW_RESET	TAS2562_REG(0, 0x01) +#define TAS2562_PWR_CTRL	TAS2562_REG(0, 0x02) +#define TAS2562_PB_CFG1		TAS2562_REG(0, 0x03) +#define TAS2562_MISC_CFG1	TAS2562_REG(0, 0x04) +#define TAS2562_MISC_CFG2	TAS2562_REG(0, 0x05) + +#define TAS2562_TDM_CFG0	TAS2562_REG(0, 0x06) +#define TAS2562_TDM_CFG1	TAS2562_REG(0, 0x07) +#define TAS2562_TDM_CFG2	TAS2562_REG(0, 0x08) +#define TAS2562_TDM_CFG3	TAS2562_REG(0, 0x09) +#define TAS2562_TDM_CFG4	TAS2562_REG(0, 0x0a) +#define TAS2562_TDM_CFG5	TAS2562_REG(0, 0x0b) +#define TAS2562_TDM_CFG6	TAS2562_REG(0, 0x0c) +#define TAS2562_TDM_CFG7	TAS2562_REG(0, 0x0d) +#define TAS2562_TDM_CFG8	TAS2562_REG(0, 0x0e) +#define TAS2562_TDM_CFG9	TAS2562_REG(0, 0x0f) +#define TAS2562_TDM_CFG10	TAS2562_REG(0, 0x10) +#define TAS2562_TDM_DET		TAS2562_REG(0, 0x11) +#define TAS2562_REV_ID		TAS2562_REG(0, 0x7d) + +/* Page 2 */ +#define TAS2562_DVC_CFG1	TAS2562_REG(2, 0x01) +#define TAS2562_DVC_CFG2	TAS2562_REG(2, 0x02) + +#define TAS2562_RESET	BIT(0) + +#define TAS2562_MODE_MASK	0x3 +#define TAS2562_ACTIVE		0x0 +#define TAS2562_MUTE		0x1 +#define TAS2562_SHUTDOWN	0x2 + +#define TAS2562_TDM_CFG1_RX_EDGE_MASK	BIT(0) +#define TAS2562_TDM_CFG1_RX_FALLING	1 +#define TAS2562_TDM_CFG1_RX_OFFSET_MASK	GENMASK(4, 0) + +#define TAS2562_TDM_CFG0_RAMPRATE_MASK		BIT(5) +#define TAS2562_TDM_CFG0_RAMPRATE_44_1		BIT(5) +#define TAS2562_TDM_CFG0_SAMPRATE_MASK		GENMASK(3, 1) +#define TAS2562_TDM_CFG0_SAMPRATE_7305_8KHZ	0x0 +#define TAS2562_TDM_CFG0_SAMPRATE_14_7_16KHZ	0x1 +#define TAS2562_TDM_CFG0_SAMPRATE_22_05_24KHZ	0x2 +#define TAS2562_TDM_CFG0_SAMPRATE_29_4_32KHZ	0x3 +#define TAS2562_TDM_CFG0_SAMPRATE_44_1_48KHZ	0x4 +#define TAS2562_TDM_CFG0_SAMPRATE_88_2_96KHZ	0x5 +#define TAS2562_TDM_CFG0_SAMPRATE_176_4_192KHZ	0x6 + +#define TAS2562_TDM_CFG2_RIGHT_JUSTIFY	BIT(6) + +#define TAS2562_TDM_CFG2_RXLEN_MASK	GENMASK(1, 0) +#define TAS2562_TDM_CFG2_RXLEN_16B	0x0 +#define TAS2562_TDM_CFG2_RXLEN_24B	BIT(0) +#define TAS2562_TDM_CFG2_RXLEN_32B	BIT(1) + +#define TAS2562_TDM_CFG2_RXWLEN_MASK	GENMASK(3, 2) +#define TAS2562_TDM_CFG2_RXWLEN_16B	0x0 +#define TAS2562_TDM_CFG2_RXWLEN_20B	BIT(2) +#define TAS2562_TDM_CFG2_RXWLEN_24B	BIT(3) +#define TAS2562_TDM_CFG2_RXWLEN_32B	(BIT(2) | BIT(3)) + +#define TAS2562_VSENSE_POWER_EN		BIT(2) +#define TAS2562_ISENSE_POWER_EN		BIT(3) + +#define TAS2562_TDM_CFG5_VSNS_EN	BIT(6) +#define TAS2562_TDM_CFG5_VSNS_SLOT_MASK	GENMASK(5, 0) + +#define TAS2562_TDM_CFG6_ISNS_EN	BIT(6) +#define TAS2562_TDM_CFG6_ISNS_SLOT_MASK	GENMASK(5, 0) + +#endif /* __TAS2562_H__ */ diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c new file mode 100644 index 000000000000..54c8135fe43c --- /dev/null +++ b/sound/soc/codecs/tas2770.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ALSA SoC Texas Instruments TAS2770 20-W Digital Input Mono Class-D +// Audio Amplifier with Speaker I/V Sense +// +// Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ +//	Author: Tracy Yi <tracy-yi@ti.com> +//	Frank Shi <shifu0704@thundersoft.com> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/firmware.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "tas2770.h" + +#define TAS2770_MDELAY 0xFFFFFFFE + +static void tas2770_reset(struct tas2770_priv *tas2770) +{ +	if (tas2770->reset_gpio) { +		gpiod_set_value_cansleep(tas2770->reset_gpio, 0); +		msleep(20); +		gpiod_set_value_cansleep(tas2770->reset_gpio, 1); +	} +	snd_soc_component_write(tas2770->component, TAS2770_SW_RST, +		TAS2770_RST); +} + +static int tas2770_set_bias_level(struct snd_soc_component *component, +				 enum snd_soc_bias_level level) +{ +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); + +	switch (level) { +	case SND_SOC_BIAS_ON: +		snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_ACTIVE); +		break; + +	case SND_SOC_BIAS_OFF: +		snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_SHUTDOWN); +		break; + +	default: +		dev_err(tas2770->dev, +				"wrong power level setting %d\n", level); +		return -EINVAL; +	} + +	return 0; +} + +#ifdef CONFIG_PM +static int tas2770_codec_suspend(struct snd_soc_component *component) +{ +	int ret; + +	ret = snd_soc_component_update_bits(component, +		TAS2770_PWR_CTRL, +		TAS2770_PWR_CTRL_MASK, +		TAS2770_PWR_CTRL_SHUTDOWN); + +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2770_codec_resume(struct snd_soc_component *component) +{ +	int ret; + +	ret = snd_soc_component_update_bits(component, +		TAS2770_PWR_CTRL, +		TAS2770_PWR_CTRL_MASK, +		TAS2770_PWR_CTRL_ACTIVE); + +	if (ret < 0) +		return ret; + +	return 0; +} +#else +#define tas2770_codec_suspend NULL +#define tas2770_codec_resume NULL +#endif + +static const char * const tas2770_ASI1_src[] = { +	"I2C offset", "Left", "Right", "LeftRightDiv2", +}; + +static SOC_ENUM_SINGLE_DECL( +	tas2770_ASI1_src_enum, TAS2770_TDM_CFG_REG2, +	4, tas2770_ASI1_src); + +static const struct snd_kcontrol_new tas2770_asi1_mux = +	SOC_DAPM_ENUM("ASI1 Source", tas2770_ASI1_src_enum); + +static int tas2770_dac_event(struct snd_soc_dapm_widget *w, +			     struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_component *component = +			snd_soc_dapm_to_component(w->dapm); +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); +	int ret; + +	switch (event) { +	case SND_SOC_DAPM_POST_PMU: +		ret = snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_MUTE); +		if (ret) +			goto end; +		break; +	case SND_SOC_DAPM_PRE_PMD: +		ret = snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_SHUTDOWN); +		if (ret) +			goto end; +		break; +	default: +		dev_err(tas2770->dev, "Not supported evevt\n"); +		return -EINVAL; +	} + +end: +	if (ret < 0) +		return ret; + +	return 0; +} + +static const struct snd_kcontrol_new isense_switch = +	SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 3, 1, 1); +static const struct snd_kcontrol_new vsense_switch = +	SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 2, 1, 1); + +static const struct snd_soc_dapm_widget tas2770_dapm_widgets[] = { +	SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, +				&tas2770_asi1_mux), +	SND_SOC_DAPM_SWITCH("ISENSE", TAS2770_PWR_CTRL, 3, 1, +			&isense_switch), +	SND_SOC_DAPM_SWITCH("VSENSE", TAS2770_PWR_CTRL, 2, 1, +			&vsense_switch), +	SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2770_dac_event, +	SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +	SND_SOC_DAPM_OUTPUT("OUT"), +	SND_SOC_DAPM_SIGGEN("VMON"), +	SND_SOC_DAPM_SIGGEN("IMON") +}; + +static const struct snd_soc_dapm_route tas2770_audio_map[] = { +	{"ASI1 Sel", "I2C offset", "ASI1"}, +	{"ASI1 Sel", "Left", "ASI1"}, +	{"ASI1 Sel", "Right", "ASI1"}, +	{"ASI1 Sel", "LeftRightDiv2", "ASI1"}, +	{"DAC", NULL, "ASI1 Sel"}, +	{"OUT", NULL, "DAC"}, +	{"ISENSE", "Switch", "IMON"}, +	{"VSENSE", "Switch", "VMON"}, +}; + +static int tas2770_mute(struct snd_soc_dai *dai, int mute) +{ +	struct snd_soc_component *component = dai->component; +	int ret; + +	if (mute) +		ret = snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_MUTE); +	else +		ret = snd_soc_component_update_bits(component, +			TAS2770_PWR_CTRL, +			TAS2770_PWR_CTRL_MASK, +			TAS2770_PWR_CTRL_ACTIVE); + +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth) +{ +	int ret; +	struct snd_soc_component *component = tas2770->component; + +	switch (bitwidth) { +	case SNDRV_PCM_FORMAT_S16_LE: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXW_MASK, +			TAS2770_TDM_CFG_REG2_RXW_16BITS); +		tas2770->v_sense_slot = tas2770->i_sense_slot + 2; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXW_MASK, +			TAS2770_TDM_CFG_REG2_RXW_24BITS); +		tas2770->v_sense_slot = tas2770->i_sense_slot + 4; +		break; +	case SNDRV_PCM_FORMAT_S32_LE: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXW_MASK, +			TAS2770_TDM_CFG_REG2_RXW_32BITS); +		tas2770->v_sense_slot = tas2770->i_sense_slot + 4; +		break; + +	default: +		return -EINVAL; +	} + +	tas2770->channel_size = bitwidth; + +	ret = snd_soc_component_update_bits(component, +		TAS2770_TDM_CFG_REG5, +		TAS2770_TDM_CFG_REG5_VSNS_MASK | +		TAS2770_TDM_CFG_REG5_50_MASK, +		TAS2770_TDM_CFG_REG5_VSNS_ENABLE | +		tas2770->v_sense_slot); +	if (ret) +		goto end; +	ret = snd_soc_component_update_bits(component, +		TAS2770_TDM_CFG_REG6, +		TAS2770_TDM_CFG_REG6_ISNS_MASK | +		TAS2770_TDM_CFG_REG6_50_MASK, +		TAS2770_TDM_CFG_REG6_ISNS_ENABLE | +		tas2770->i_sense_slot); + +end: +	if (ret < 0) +		return ret; + +	return 0; +} + +static int tas2770_set_samplerate(struct tas2770_priv *tas2770, int samplerate) +{ +	int ret; +	struct snd_soc_component *component = tas2770->component; + +	switch (samplerate) { +	case 48000: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_48KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_44_1_48KHZ); +		if (ret) +			goto end; +		break; +	case 44100: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_44_1_48KHZ); +		if (ret) +			goto end; +		break; +	case 96000: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_48KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_88_2_96KHZ); +		break; +	case 88200: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_88_2_96KHZ); +		break; +	case 19200: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_48KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_176_4_192KHZ); +		if (ret) +			goto end; +		break; +	case 17640: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_SMP_MASK, +			TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); +		if (ret) +			goto end; +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG0, +			TAS2770_TDM_CFG_REG0_31_MASK, +			TAS2770_TDM_CFG_REG0_31_176_4_192KHZ); +		break; +	default: +		ret = -EINVAL; +	} + +end: +	if (ret < 0) +		return ret; + +	tas2770->sampling_rate = samplerate; +	return 0; +} + +static int tas2770_hw_params(struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *params, +			     struct snd_soc_dai *dai) +{ +	struct snd_soc_component *component = dai->component; +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); +	int ret; + +	ret = tas2770_set_bitwidth(tas2770, params_format(params)); +	if (ret < 0) +		goto end; + + +	ret = tas2770_set_samplerate(tas2770, params_rate(params)); + +end: +	return ret; +} + +static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; +	int ret; +	struct snd_soc_component *component = dai->component; +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); + +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBS_CFS: +		break; +	default: +		dev_err(tas2770->dev, "ASI format master is not found\n"); +		return -EINVAL; +	} + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_RSING; +		break; +	case SND_SOC_DAIFMT_IB_NF: +		asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_FALING; +		break; +	default: +		dev_err(tas2770->dev, "ASI format Inverse is not found\n"); +		return -EINVAL; +	} + +	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1, +		TAS2770_TDM_CFG_REG1_RX_MASK, +		asi_cfg_1); +	if (ret < 0) +		return ret; + +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_I2S: +		tdm_rx_start_slot = 1; +		break; +	case SND_SOC_DAIFMT_DSP_A: +		tdm_rx_start_slot = 0; +		break; +	case SND_SOC_DAIFMT_DSP_B: +		tdm_rx_start_slot = 1; +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		tdm_rx_start_slot = 0; +		break; +	default: +		dev_err(tas2770->dev, +			"DAI Format is not found, fmt=0x%x\n", fmt); +		return -EINVAL; +	} + +	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1, +		TAS2770_TDM_CFG_REG1_MASK, +	(tdm_rx_start_slot << TAS2770_TDM_CFG_REG1_51_SHIFT)); +	if (ret < 0) +		return ret; + +	tas2770->asi_format = fmt; + +	return 0; +} + +static int tas2770_set_dai_tdm_slot(struct snd_soc_dai *dai, +				unsigned int tx_mask, +				unsigned int rx_mask, +				int slots, int slot_width) +{ +	struct snd_soc_component *component = dai->component; +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); +	int left_slot, right_slot; +	int ret; + +	if (tx_mask == 0 || rx_mask != 0) +		return -EINVAL; + +	if (slots == 1) { +		if (tx_mask != 1) +			return -EINVAL; +		left_slot = 0; +		right_slot = 0; +	} else { +		left_slot = __ffs(tx_mask); +		tx_mask &= ~(1 << left_slot); +		if (tx_mask == 0) { +			right_slot = left_slot; +		} else { +			right_slot = __ffs(tx_mask); +			tx_mask &= ~(1 << right_slot); +		} +	} + +	if (tx_mask != 0 || left_slot >= slots || right_slot >= slots) +		return -EINVAL; + +	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3, +		TAS2770_TDM_CFG_REG3_30_MASK, +		(left_slot << TAS2770_TDM_CFG_REG3_30_SHIFT)); +	if (ret < 0) +		return ret; +	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3, +		TAS2770_TDM_CFG_REG3_RXS_MASK, +	(right_slot << TAS2770_TDM_CFG_REG3_RXS_SHIFT)); +	if (ret < 0) +		return ret; + +	switch (slot_width) { +	case 16: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXS_MASK, +			TAS2770_TDM_CFG_REG2_RXS_16BITS); +		break; + +	case 24: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXS_MASK, +			TAS2770_TDM_CFG_REG2_RXS_24BITS); +		break; + +	case 32: +		ret = snd_soc_component_update_bits(component, +			TAS2770_TDM_CFG_REG2, +			TAS2770_TDM_CFG_REG2_RXS_MASK, +			TAS2770_TDM_CFG_REG2_RXS_32BITS); +		break; + +	case 0: +		/* Do not change slot width */ +		ret = 0; +		break; + +	default: +		ret = -EINVAL; +	} + +	if (ret < 0) +		return ret; + +	tas2770->slot_width = slot_width; +	return 0; +} + +static struct snd_soc_dai_ops tas2770_dai_ops = { +	.digital_mute = tas2770_mute, +	.hw_params  = tas2770_hw_params, +	.set_fmt    = tas2770_set_fmt, +	.set_tdm_slot = tas2770_set_dai_tdm_slot, +}; + +#define TAS2770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ +		SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define TAS2770_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ +					   SNDRV_PCM_RATE_96000 |\ +					    SNDRV_PCM_RATE_192000\ +					  ) + +static struct snd_soc_dai_driver tas2770_dai_driver[] = { +	{ +		.name = "tas2770 ASI1", +		.id = 0, +		.playback = { +			.stream_name    = "ASI1 Playback", +			.channels_min   = 2, +			.channels_max   = 2, +			.rates      = TAS2770_RATES, +			.formats    = TAS2770_FORMATS, +		}, +		.capture = { +			.stream_name    = "ASI1 Capture", +			.channels_min   = 0, +			.channels_max   = 2, +			.rates          = TAS2770_RATES, +			.formats    = TAS2770_FORMATS, +		}, +		.ops = &tas2770_dai_ops, +		.symmetric_rates = 1, +	}, +}; + +static int tas2770_codec_probe(struct snd_soc_component *component) +{ +	struct tas2770_priv *tas2770 = +			snd_soc_component_get_drvdata(component); + +	tas2770->component = component; + +	return 0; +} + +static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0); +static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0); + +static const struct snd_kcontrol_new tas2770_snd_controls[] = { +	SOC_SINGLE_TLV("Speaker Playback Volume", TAS2770_PLAY_CFG_REG2, +		0, TAS2770_PLAY_CFG_REG2_VMAX, 1, +		tas2770_playback_volume), +	SOC_SINGLE_TLV("Amp Gain Volume", TAS2770_PLAY_CFG_REG0, +		0, 0x14, 0, +		tas2770_digital_tlv), +}; + +static const struct snd_soc_component_driver soc_component_driver_tas2770 = { +	.probe			= tas2770_codec_probe, +	.suspend		= tas2770_codec_suspend, +	.resume			= tas2770_codec_resume, +	.set_bias_level = tas2770_set_bias_level, +	.controls		= tas2770_snd_controls, +	.num_controls		= ARRAY_SIZE(tas2770_snd_controls), +	.dapm_widgets		= tas2770_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(tas2770_dapm_widgets), +	.dapm_routes		= tas2770_audio_map, +	.num_dapm_routes	= ARRAY_SIZE(tas2770_audio_map), +	.idle_bias_on		= 1, +	.endianness		= 1, +	.non_legacy_dai_naming	= 1, +}; + +static int tas2770_register_codec(struct tas2770_priv *tas2770) +{ +	return devm_snd_soc_register_component(tas2770->dev, +		&soc_component_driver_tas2770, +		tas2770_dai_driver, ARRAY_SIZE(tas2770_dai_driver)); +} + +static const struct reg_default tas2770_reg_defaults[] = { +	{ TAS2770_PAGE, 0x00 }, +	{ TAS2770_SW_RST, 0x00 }, +	{ TAS2770_PWR_CTRL, 0x0e }, +	{ TAS2770_PLAY_CFG_REG0, 0x10 }, +	{ TAS2770_PLAY_CFG_REG1, 0x01 }, +	{ TAS2770_PLAY_CFG_REG2, 0x00 }, +	{ TAS2770_MSC_CFG_REG0, 0x07 }, +	{ TAS2770_TDM_CFG_REG1, 0x02 }, +	{ TAS2770_TDM_CFG_REG2, 0x0a }, +	{ TAS2770_TDM_CFG_REG3, 0x10 }, +	{ TAS2770_INT_MASK_REG0, 0xfc }, +	{ TAS2770_INT_MASK_REG1, 0xb1 }, +	{ TAS2770_INT_CFG, 0x05 }, +	{ TAS2770_MISC_IRQ, 0x81 }, +	{ TAS2770_CLK_CGF, 0x0c }, + +}; + +static bool tas2770_volatile(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TAS2770_PAGE: /* regmap implementation requires this */ +	case TAS2770_SW_RST: /* always clears after write */ +	case TAS2770_BO_PRV_REG0:/* has a self clearing bit */ +	case TAS2770_LVE_INT_REG0: +	case TAS2770_LVE_INT_REG1: +	case TAS2770_LAT_INT_REG0:/* Sticky interrupt flags */ +	case TAS2770_LAT_INT_REG1:/* Sticky interrupt flags */ +	case TAS2770_VBAT_MSB: +	case TAS2770_VBAT_LSB: +	case TAS2770_TEMP_MSB: +	case TAS2770_TEMP_LSB: +		return true; +	} +	return false; +} + +static bool tas2770_writeable(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TAS2770_LVE_INT_REG0: +	case TAS2770_LVE_INT_REG1: +	case TAS2770_LAT_INT_REG0: +	case TAS2770_LAT_INT_REG1: +	case TAS2770_VBAT_MSB: +	case TAS2770_VBAT_LSB: +	case TAS2770_TEMP_MSB: +	case TAS2770_TEMP_LSB: +	case TAS2770_TDM_CLK_DETC: +	case TAS2770_REV_AND_GPID: +		return false; +	} +	return true; +} + +static const struct regmap_range_cfg tas2770_regmap_ranges[] = { +	{ +		.range_min = 0, +		.range_max = 1 * 128, +		.selector_reg = TAS2770_PAGE, +		.selector_mask = 0xff, +		.selector_shift = 0, +		.window_start = 0, +		.window_len = 128, +	}, +}; + +static const struct regmap_config tas2770_i2c_regmap = { +	.reg_bits = 8, +	.val_bits = 8, +	.writeable_reg = tas2770_writeable, +	.volatile_reg = tas2770_volatile, +	.reg_defaults = tas2770_reg_defaults, +	.num_reg_defaults = ARRAY_SIZE(tas2770_reg_defaults), +	.cache_type = REGCACHE_RBTREE, +	.ranges = tas2770_regmap_ranges, +	.num_ranges = ARRAY_SIZE(tas2770_regmap_ranges), +	.max_register = 1 * 128, +}; + +static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770) +{ +	int rc = 0; + +	rc = fwnode_property_read_u32(dev->fwnode, "ti,asi-format", +					&tas2770->asi_format); +	if (rc) { +		dev_err(tas2770->dev, "Looking up %s property failed %d\n", +			"ti,asi-format", rc); +		goto end; +	} + +	rc = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no", +			&tas2770->i_sense_slot); +	if (rc) { +		dev_err(tas2770->dev, "Looking up %s property failed %d\n", +			"ti,imon-slot-no", rc); +		goto end; +	} + +	rc = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no", +				&tas2770->v_sense_slot); +	if (rc) { +		dev_err(tas2770->dev, "Looking up %s property failed %d\n", +			"ti,vmon-slot-no", rc); +		goto end; +	} + +end: +	return rc; +} + +static int tas2770_i2c_probe(struct i2c_client *client, +			const struct i2c_device_id *id) +{ +	struct tas2770_priv *tas2770; +	int result; + +	tas2770 = devm_kzalloc(&client->dev, +		sizeof(struct tas2770_priv), GFP_KERNEL); +	if (!tas2770) +		return -ENOMEM; +	tas2770->dev = &client->dev; + +	i2c_set_clientdata(client, tas2770); +	dev_set_drvdata(&client->dev, tas2770); +	tas2770->power_state = TAS2770_POWER_SHUTDOWN; + +	tas2770->regmap = devm_regmap_init_i2c(client, &tas2770_i2c_regmap); +	if (IS_ERR(tas2770->regmap)) { +		result = PTR_ERR(tas2770->regmap); +		dev_err(&client->dev, "Failed to allocate register map: %d\n", +					result); +		goto end; +	} + +	if (client->dev.of_node) { +		result = tas2770_parse_dt(&client->dev, tas2770); +		if (result) { +			dev_err(tas2770->dev, "%s: Failed to parse devicetree\n", +				__func__); +			goto end; +		} +	} + +	tas2770->reset_gpio = devm_gpiod_get_optional(tas2770->dev, +							  "reset-gpio", +						      GPIOD_OUT_HIGH); +	if (IS_ERR(tas2770->reset_gpio)) { +		if (PTR_ERR(tas2770->reset_gpio) == -EPROBE_DEFER) { +			tas2770->reset_gpio = NULL; +			return -EPROBE_DEFER; +		} +	} + +	tas2770->channel_size = 0; +	tas2770->slot_width = 0; + +	tas2770_reset(tas2770); + +	result = tas2770_register_codec(tas2770); +	if (result) +		dev_err(tas2770->dev, "Register codec failed.\n"); + +end: +	return result; +} + +static int tas2770_i2c_remove(struct i2c_client *client) +{ +	pm_runtime_disable(&client->dev); +	return 0; +} + + +static const struct i2c_device_id tas2770_i2c_id[] = { +	{ "tas2770", 0}, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tas2770_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id tas2770_of_match[] = { +	{ .compatible = "ti,tas2770" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, tas2770_of_match); +#endif + +static struct i2c_driver tas2770_i2c_driver = { +	.driver = { +		.name   = "tas2770", +		.of_match_table = of_match_ptr(tas2770_of_match), +	}, +	.probe      = tas2770_i2c_probe, +	.remove     = tas2770_i2c_remove, +	.id_table   = tas2770_i2c_id, +}; + +module_i2c_driver(tas2770_i2c_driver); + +MODULE_AUTHOR("Shi Fu <shifu0704@thundersoft.com>"); +MODULE_DESCRIPTION("TAS2770 I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h new file mode 100644 index 000000000000..cbb858369fe6 --- /dev/null +++ b/sound/soc/codecs/tas2770.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * ALSA SoC TAS2770 codec driver + * + *  Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + */ +#ifndef __TAS2770__ +#define __TAS2770__ + +/* Book Control Register (available in page0 of each book) */ +#define TAS2770_BOOKCTL_PAGE            0 +#define TAS2770_BOOKCTL_REG         127 +#define TAS2770_REG(page, reg)        ((page * 128) + reg) +    /* Page */ +#define TAS2770_PAGE  TAS2770_REG(0X0, 0x00) +#define TAS2770_PAGE_PAGE_MASK  255 +    /* Software Reset */ +#define TAS2770_SW_RST  TAS2770_REG(0X0, 0x01) +#define TAS2770_RST  BIT(0) +    /* Power Control */ +#define TAS2770_PWR_CTRL  TAS2770_REG(0X0, 0x02) +#define TAS2770_PWR_CTRL_MASK  0x3 +#define TAS2770_PWR_CTRL_ACTIVE  0x0 +#define TAS2770_PWR_CTRL_MUTE  BIT(0) +#define TAS2770_PWR_CTRL_SHUTDOWN  0x2 +    /* Playback Configuration Reg0 */ +#define TAS2770_PLAY_CFG_REG0  TAS2770_REG(0X0, 0x03) +    /* Playback Configuration Reg1 */ +#define TAS2770_PLAY_CFG_REG1  TAS2770_REG(0X0, 0x04) +    /* Playback Configuration Reg2 */ +#define TAS2770_PLAY_CFG_REG2  TAS2770_REG(0X0, 0x05) +#define TAS2770_PLAY_CFG_REG2_VMAX 0xc9 +    /* Misc Configuration Reg0 */ +#define TAS2770_MSC_CFG_REG0  TAS2770_REG(0X0, 0x07) +    /* TDM Configuration Reg0 */ +#define TAS2770_TDM_CFG_REG0  TAS2770_REG(0X0, 0x0A) +#define TAS2770_TDM_CFG_REG0_SMP_MASK  BIT(5) +#define TAS2770_TDM_CFG_REG0_SMP_48KHZ  0x0 +#define TAS2770_TDM_CFG_REG0_SMP_44_1KHZ  BIT(5) +#define TAS2770_TDM_CFG_REG0_31_MASK  0xe +#define TAS2770_TDM_CFG_REG0_31_44_1_48KHZ  0x6 +#define TAS2770_TDM_CFG_REG0_31_88_2_96KHZ  0x8 +#define TAS2770_TDM_CFG_REG0_31_176_4_192KHZ  0xa +    /* TDM Configuration Reg1 */ +#define TAS2770_TDM_CFG_REG1  TAS2770_REG(0X0, 0x0B) +#define TAS2770_TDM_CFG_REG1_MASK 0x3e +#define TAS2770_TDM_CFG_REG1_51_SHIFT  1 +#define TAS2770_TDM_CFG_REG1_RX_MASK  BIT(0) +#define TAS2770_TDM_CFG_REG1_RX_RSING  0x0 +#define TAS2770_TDM_CFG_REG1_RX_FALING  BIT(0) +    /* TDM Configuration Reg2 */ +#define TAS2770_TDM_CFG_REG2  TAS2770_REG(0X0, 0x0C) +#define TAS2770_TDM_CFG_REG2_RXW_MASK  0xc +#define TAS2770_TDM_CFG_REG2_RXW_16BITS  0x0 +#define TAS2770_TDM_CFG_REG2_RXW_24BITS  0x8 +#define TAS2770_TDM_CFG_REG2_RXW_32BITS  0xc +#define TAS2770_TDM_CFG_REG2_RXS_MASK    0x3 +#define TAS2770_TDM_CFG_REG2_RXS_16BITS  0x0 +#define TAS2770_TDM_CFG_REG2_RXS_24BITS  BIT(0) +#define TAS2770_TDM_CFG_REG2_RXS_32BITS  0x2 +    /* TDM Configuration Reg3 */ +#define TAS2770_TDM_CFG_REG3  TAS2770_REG(0X0, 0x0D) +#define TAS2770_TDM_CFG_REG3_RXS_MASK  0xf0 +#define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4 +#define TAS2770_TDM_CFG_REG3_30_MASK  0xf +#define TAS2770_TDM_CFG_REG3_30_SHIFT 0 +    /* TDM Configuration Reg5 */ +#define TAS2770_TDM_CFG_REG5  TAS2770_REG(0X0, 0x0F) +#define TAS2770_TDM_CFG_REG5_VSNS_MASK  BIT(6) +#define TAS2770_TDM_CFG_REG5_VSNS_ENABLE  BIT(6) +#define TAS2770_TDM_CFG_REG5_50_MASK  0x3f +    /* TDM Configuration Reg6 */ +#define TAS2770_TDM_CFG_REG6  TAS2770_REG(0X0, 0x10) +#define TAS2770_TDM_CFG_REG6_ISNS_MASK  BIT(6) +#define TAS2770_TDM_CFG_REG6_ISNS_ENABLE  BIT(6) +#define TAS2770_TDM_CFG_REG6_50_MASK  0x3f +    /* Brown Out Prevention Reg0 */ +#define TAS2770_BO_PRV_REG0  TAS2770_REG(0X0, 0x1B) +    /* Interrupt MASK Reg0 */ +#define TAS2770_INT_MASK_REG0  TAS2770_REG(0X0, 0x20) +#define TAS2770_INT_REG0_DEFAULT  0xfc +#define TAS2770_INT_MASK_REG0_DISABLE 0xff +    /* Interrupt MASK Reg1 */ +#define TAS2770_INT_MASK_REG1  TAS2770_REG(0X0, 0x21) +#define TAS2770_INT_REG1_DEFAULT  0xb1 +#define TAS2770_INT_MASK_REG1_DISABLE 0xff +    /* Live-Interrupt Reg0 */ +#define TAS2770_LVE_INT_REG0  TAS2770_REG(0X0, 0x22) +    /* Live-Interrupt Reg1 */ +#define TAS2770_LVE_INT_REG1  TAS2770_REG(0X0, 0x23) +    /* Latched-Interrupt Reg0 */ +#define TAS2770_LAT_INT_REG0  TAS2770_REG(0X0, 0x24) +#define TAS2770_LAT_INT_REG0_OCE_FLG  BIT(1) +#define TAS2770_LAT_INT_REG0_OTE_FLG  BIT(0) +    /* Latched-Interrupt Reg1 */ +#define TAS2770_LAT_INT_REG1  TAS2770_REG(0X0, 0x25) +#define TAS2770_LAT_INT_REG1_VBA_TOV  BIT(3) +#define TAS2770_LAT_INT_REG1_VBA_TUV  BIT(2) +#define TAS2770_LAT_INT_REG1_BOUT_FLG  BIT(1) +    /* VBAT MSB */ +#define TAS2770_VBAT_MSB  TAS2770_REG(0X0, 0x27) +    /* VBAT LSB */ +#define TAS2770_VBAT_LSB  TAS2770_REG(0X0, 0x28) +    /* TEMP MSB */ +#define TAS2770_TEMP_MSB  TAS2770_REG(0X0, 0x29) +    /* TEMP LSB */ +#define TAS2770_TEMP_LSB  TAS2770_REG(0X0, 0x2A) +    /* Interrupt Configuration */ +#define TAS2770_INT_CFG  TAS2770_REG(0X0, 0x30) +    /* Misc IRQ */ +#define TAS2770_MISC_IRQ  TAS2770_REG(0X0, 0x32) +    /* Clock Configuration */ +#define TAS2770_CLK_CGF  TAS2770_REG(0X0, 0x3C) +    /* TDM Clock detection monitor */ +#define TAS2770_TDM_CLK_DETC  TAS2770_REG(0X0, 0x77) +    /* Revision and PG ID */ +#define TAS2770_REV_AND_GPID  TAS2770_REG(0X0, 0x7D) + +#define TAS2770_POWER_ACTIVE 0 +#define TAS2770_POWER_MUTE 1 +#define TAS2770_POWER_SHUTDOWN 2 +#define ERROR_OVER_CURRENT  0x0000001 +#define ERROR_DIE_OVERTEMP  0x0000002 +#define ERROR_OVER_VOLTAGE  0x0000004 +#define ERROR_UNDER_VOLTAGE 0x0000008 +#define ERROR_BROWNOUT      0x0000010 +#define ERROR_CLASSD_PWR    0x0000020 + +struct tas2770_priv { +	struct device *dev; +	struct regmap *regmap; +	struct snd_soc_component *component; +	int power_state; +	int asi_format; +	struct gpio_desc *reset_gpio; +	int sampling_rate; +	int channel_size; +	int slot_width; +	int v_sense_slot; +	int i_sense_slot; +}; + +#endif /* __TAS2770__ */ diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index df627a08def9..f6f19fdc72f5 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -171,6 +171,7 @@ struct aic31xx_priv {  	int rate_div_line;  	bool master_dapm_route_applied;  	int irq; +	u8 ocmv; /* output common-mode voltage */  };  struct aic31xx_rate_divs { @@ -1312,6 +1313,11 @@ static int aic31xx_codec_probe(struct snd_soc_component *component)  	if (ret)  		return ret; +	/* set output common-mode voltage */ +	snd_soc_component_update_bits(component, AIC31XX_HPDRIVER, +				      AIC31XX_HPD_OCMV_MASK, +				      aic31xx->ocmv << AIC31XX_HPD_OCMV_SHIFT); +  	return 0;  } @@ -1501,6 +1507,43 @@ exit:  		return IRQ_NONE;  } +static void aic31xx_configure_ocmv(struct aic31xx_priv *priv) +{ +	struct device *dev = priv->dev; +	int dvdd, avdd; +	u32 value; + +	if (dev->fwnode && +	    fwnode_property_read_u32(dev->fwnode, "ai31xx-ocmv", &value)) { +		/* OCMV setting is forced by DT */ +		if (value <= 3) { +			priv->ocmv = value; +			return; +		} +	} + +	avdd = regulator_get_voltage(priv->supplies[3].consumer); +	dvdd = regulator_get_voltage(priv->supplies[5].consumer); + +	if (avdd > 3600000 || dvdd > 1950000) { +		dev_warn(dev, +			 "Too high supply voltage(s) AVDD: %d, DVDD: %d\n", +			 avdd, dvdd); +	} else if (avdd == 3600000 && dvdd == 1950000) { +		priv->ocmv = AIC31XX_HPD_OCMV_1_8V; +	} else if (avdd >= 3300000 && dvdd >= 1800000) { +		priv->ocmv = AIC31XX_HPD_OCMV_1_65V; +	} else if (avdd >= 3000000 && dvdd >= 1650000) { +		priv->ocmv = AIC31XX_HPD_OCMV_1_5V; +	} else if (avdd >= 2700000 && dvdd >= 1525000) { +		priv->ocmv = AIC31XX_HPD_OCMV_1_35V; +	} else { +		dev_warn(dev, +			 "Invalid supply voltage(s) AVDD: %d, DVDD: %d\n", +			 avdd, dvdd); +	} +} +  static int aic31xx_i2c_probe(struct i2c_client *i2c,  			     const struct i2c_device_id *id)  { @@ -1570,6 +1613,8 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c,  		return ret;  	} +	aic31xx_configure_ocmv(aic31xx); +  	if (aic31xx->irq > 0) {  		regmap_update_bits(aic31xx->regmap, AIC31XX_GPIO1,  				   AIC31XX_GPIO1_FUNC_MASK, diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index cb024955c978..83a8c7604cc3 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -232,6 +232,14 @@ struct aic31xx_pdata {  #define AIC31XX_HSD_HP			0x01  #define AIC31XX_HSD_HS			0x03 +/* AIC31XX_HPDRIVER */ +#define AIC31XX_HPD_OCMV_MASK		GENMASK(4, 3) +#define AIC31XX_HPD_OCMV_SHIFT		3 +#define AIC31XX_HPD_OCMV_1_35V		0x0 +#define AIC31XX_HPD_OCMV_1_5V		0x1 +#define AIC31XX_HPD_OCMV_1_65V		0x2 +#define AIC31XX_HPD_OCMV_1_8V		0x3 +  /* AIC31XX_MICBIAS */  #define AIC31XX_MICBIAS_MASK		GENMASK(1, 0)  #define AIC31XX_MICBIAS_SHIFT		0 diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 68165de1c8de..b4e9a6c73f90 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -573,6 +573,9 @@ static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,  	struct clk *pll;  	pll = devm_clk_get(component->dev, "pll"); +	if (IS_ERR(pll)) +		return PTR_ERR(pll); +  	mclk = clk_get_parent(pll);  	return clk_set_rate(mclk, freq); diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index f318403133e9..f11ffa28683b 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -2837,11 +2837,11 @@ static int wcd9335_codec_enable_dec(struct snd_soc_dapm_widget *w,  				   TX_HPF_CUT_OFF_FREQ_MASK) >> 5;  		snd_soc_component_update_bits(comp, tx_vol_ctl_reg, 0x10, 0x10);  		snd_soc_component_update_bits(comp, dec_cfg_reg, 0x08, 0x00); -			if (hpf_coff_freq != CF_MIN_3DB_150HZ) { -				snd_soc_component_update_bits(comp, dec_cfg_reg, -						    TX_HPF_CUT_OFF_FREQ_MASK, -						    hpf_coff_freq << 5); -			} +		if (hpf_coff_freq != CF_MIN_3DB_150HZ) { +			snd_soc_component_update_bits(comp, dec_cfg_reg, +						      TX_HPF_CUT_OFF_FREQ_MASK, +						      hpf_coff_freq << 5); +		}  		break;  	case SND_SOC_DAPM_POST_PMD:  		snd_soc_component_update_bits(comp, tx_vol_ctl_reg, 0x10, 0x00); diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index cf64e109c658..7b087d94141b 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -2410,6 +2410,8 @@ static int wm2200_i2c_probe(struct i2c_client *i2c,  err_pm_runtime:  	pm_runtime_disable(&i2c->dev); +	if (i2c->irq) +		free_irq(i2c->irq, wm2200);  err_reset:  	if (wm2200->pdata.reset)  		gpio_set_value_cansleep(wm2200->pdata.reset, 0); @@ -2426,12 +2428,15 @@ static int wm2200_i2c_remove(struct i2c_client *i2c)  {  	struct wm2200_priv *wm2200 = i2c_get_clientdata(i2c); +	pm_runtime_disable(&i2c->dev);  	if (i2c->irq)  		free_irq(i2c->irq, wm2200);  	if (wm2200->pdata.reset)  		gpio_set_value_cansleep(wm2200->pdata.reset, 0);  	if (wm2200->pdata.ldo_ena)  		gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0); +	regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), +			       wm2200->core_supplies);  	return 0;  } diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 4af0e519e623..91cc63c5a51f 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -2617,6 +2617,7 @@ static int wm5100_i2c_probe(struct i2c_client *i2c,  	return ret;  err_reset: +	pm_runtime_disable(&i2c->dev);  	if (i2c->irq)  		free_irq(i2c->irq, wm5100);  	wm5100_free_gpio(i2c); @@ -2640,6 +2641,7 @@ static int wm5100_i2c_remove(struct i2c_client *i2c)  {  	struct wm5100_priv *wm5100 = i2c_get_clientdata(i2c); +	pm_runtime_disable(&i2c->dev);  	if (i2c->irq)  		free_irq(i2c->irq, wm5100);  	wm5100_free_gpio(i2c); diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index bcb3c9d5abf0..7d7ea15d73e0 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -1410,34 +1410,6 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,  	return 0;  } - -static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, -			     unsigned int freq, int dir) -{ -	struct snd_soc_component *component = dai->component; -	struct wm8904_priv *priv = snd_soc_component_get_drvdata(component); - -	switch (clk_id) { -	case WM8904_CLK_MCLK: -		priv->sysclk_src = clk_id; -		priv->mclk_rate = freq; -		break; - -	case WM8904_CLK_FLL: -		priv->sysclk_src = clk_id; -		break; - -	default: -		return -EINVAL; -	} - -	dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); - -	wm8904_configure_clocking(component); - -	return 0; -} -  static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)  {  	struct snd_soc_component *component = dai->component; @@ -1824,6 +1796,50 @@ out:  	return 0;  } +static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, +			     unsigned int freq, int dir) +{ +	struct snd_soc_component *component = dai->component; +	struct wm8904_priv *priv = snd_soc_component_get_drvdata(component); +	unsigned long mclk_freq; +	int ret; + +	switch (clk_id) { +	case WM8904_CLK_AUTO: +		mclk_freq = clk_get_rate(priv->mclk); +		/* enable FLL if a different sysclk is desired */ +		if (mclk_freq != freq) { +			priv->sysclk_src = WM8904_CLK_FLL; +			ret = wm8904_set_fll(dai, WM8904_FLL_MCLK, +					     WM8904_FLL_MCLK, +					     mclk_freq, freq); +			if (ret) +				return ret; +			break; +		} +		clk_id = WM8904_CLK_MCLK; +		/* fallthrough */ + +	case WM8904_CLK_MCLK: +		priv->sysclk_src = clk_id; +		priv->mclk_rate = freq; +		break; + +	case WM8904_CLK_FLL: +		priv->sysclk_src = clk_id; +		break; + +	default: +		return -EINVAL; +	} + +	dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + +	wm8904_configure_clocking(component); + +	return 0; +} +  static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute)  {  	struct snd_soc_component *component = codec_dai->component; @@ -1917,6 +1933,7 @@ static int wm8904_set_bias_level(struct snd_soc_component *component,  		snd_soc_component_update_bits(component, WM8904_BIAS_CONTROL_0,  				    WM8904_BIAS_ENA, 0); +		snd_soc_component_write(component, WM8904_SW_RESET_AND_ID, 0);  		regcache_cache_only(wm8904->regmap, true);  		regcache_mark_dirty(wm8904->regmap); diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h index c1bca52f9927..de6340446b1f 100644 --- a/sound/soc/codecs/wm8904.h +++ b/sound/soc/codecs/wm8904.h @@ -10,6 +10,7 @@  #ifndef _WM8904_H  #define _WM8904_H +#define WM8904_CLK_AUTO 0  #define WM8904_CLK_MCLK 1  #define WM8904_CLK_FLL  2 diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index 18535b326680..ca42445b649d 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -25,6 +25,8 @@  #include <linux/mfd/wm8994/pdata.h>  #include <linux/mfd/wm8994/gpio.h> +#include <asm/unaligned.h> +  #include "wm8994.h"  #define WM_FW_BLOCK_INFO 0xff @@ -58,18 +60,15 @@ static int wm8958_dsp2_fw(struct snd_soc_component *component, const char *name,  	}  	if (memcmp(fw->data, "WMFW", 4) != 0) { -		memcpy(&data32, fw->data, sizeof(data32)); -		data32 = be32_to_cpu(data32); +		data32 = get_unaligned_be32(fw->data);  		dev_err(component->dev, "%s: firmware has bad file magic %08x\n",  			name, data32);  		goto err;  	} -	memcpy(&data32, fw->data + 4, sizeof(data32)); -	len = be32_to_cpu(data32); +	len = get_unaligned_be32(fw->data + 4); +	data32 = get_unaligned_be32(fw->data + 8); -	memcpy(&data32, fw->data + 8, sizeof(data32)); -	data32 = be32_to_cpu(data32);  	if ((data32 >> 24) & 0xff) {  		dev_err(component->dev, "%s: unsupported firmware version %d\n",  			name, (data32 >> 24) & 0xff); @@ -87,9 +86,8 @@ static int wm8958_dsp2_fw(struct snd_soc_component *component, const char *name,  	}  	if (check) { -		memcpy(&data64, fw->data + 24, sizeof(u64)); -		dev_info(component->dev, "%s timestamp %llx\n", -			 name, be64_to_cpu(data64)); +		data64 = get_unaligned_be64(fw->data + 24); +		dev_info(component->dev, "%s timestamp %llx\n",  name, data64);  	} else {  		snd_soc_component_write(component, 0x102, 0x2);  		snd_soc_component_write(component, 0x900, 0x2); @@ -104,8 +102,7 @@ static int wm8958_dsp2_fw(struct snd_soc_component *component, const char *name,  			goto err;  		} -		memcpy(&data32, data + 4, sizeof(data32)); -		block_len = be32_to_cpu(data32); +		block_len = get_unaligned_be32(data + 4);  		if (block_len + 8 > len) {  			dev_err(component->dev, "%zd byte block longer than file\n",  				block_len); @@ -116,8 +113,7 @@ static int wm8958_dsp2_fw(struct snd_soc_component *component, const char *name,  			goto err;  		} -		memcpy(&data32, data, sizeof(data32)); -		data32 = be32_to_cpu(data32); +		data32 = get_unaligned_be32(data);  		switch ((data32 >> 24) & 0xff) {  		case WM_FW_BLOCK_INFO: diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index d5fb7f5dd551..15ce64a48a87 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -167,12 +167,12 @@ static int configure_aif_clock(struct snd_soc_component *component, int aif)  	switch (wm8994->sysclk[aif]) {  	case WM8994_SYSCLK_MCLK1: -		rate = wm8994->mclk[0]; +		rate = wm8994->mclk_rate[0];  		break;  	case WM8994_SYSCLK_MCLK2:  		reg1 |= 0x8; -		rate = wm8994->mclk[1]; +		rate = wm8994->mclk_rate[1];  		break;  	case WM8994_SYSCLK_FLL1: @@ -1038,6 +1038,45 @@ static bool wm8994_check_class_w_digital(struct snd_soc_component *component)  	return true;  } +static int aif_mclk_set(struct snd_soc_component *component, int aif, bool enable) +{ +	struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); +	unsigned int offset, val, clk_idx; +	int ret; + +	if (aif) +		offset = 4; +	else +		offset = 0; + +	val = snd_soc_component_read32(component, WM8994_AIF1_CLOCKING_1 + offset); +	val &= WM8994_AIF1CLK_SRC_MASK; + +	switch (val) { +	case 0: +		clk_idx = WM8994_MCLK1; +		break; +	case 1: +		clk_idx = WM8994_MCLK2; +		break; +	default: +		return 0; +	} + +	if (enable) { +		ret = clk_prepare_enable(wm8994->mclk[clk_idx].clk); +		if (ret < 0) { +			dev_err(component->dev,	"Failed to enable MCLK%d\n", +				clk_idx); +			return ret; +		} +	} else { +		clk_disable_unprepare(wm8994->mclk[clk_idx].clk); +	} + +	return 0; +} +  static int aif1clk_ev(struct snd_soc_dapm_widget *w,  		      struct snd_kcontrol *kcontrol, int event)  { @@ -1045,7 +1084,7 @@ static int aif1clk_ev(struct snd_soc_dapm_widget *w,  	struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component);  	struct wm8994 *control = wm8994->wm8994;  	int mask = WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA; -	int i; +	int ret, i;  	int dac;  	int adc;  	int val; @@ -1061,6 +1100,10 @@ static int aif1clk_ev(struct snd_soc_dapm_widget *w,  	switch (event) {  	case SND_SOC_DAPM_PRE_PMU: +		ret = aif_mclk_set(component, 0, true); +		if (ret < 0) +			return ret; +  		/* Don't enable timeslot 2 if not in use */  		if (wm8994->channels[0] <= 2)  			mask &= ~(WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA); @@ -1133,6 +1176,12 @@ static int aif1clk_ev(struct snd_soc_dapm_widget *w,  		break;  	} +	switch (event) { +	case SND_SOC_DAPM_POST_PMD: +		aif_mclk_set(component, 0, false); +		break; +	} +  	return 0;  } @@ -1140,13 +1189,17 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w,  		      struct snd_kcontrol *kcontrol, int event)  {  	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); -	int i; +	int ret, i;  	int dac;  	int adc;  	int val;  	switch (event) {  	case SND_SOC_DAPM_PRE_PMU: +		ret = aif_mclk_set(component, 1, true); +		if (ret < 0) +			return ret; +  		val = snd_soc_component_read32(component, WM8994_AIF2_CONTROL_1);  		if ((val & WM8994_AIF2ADCL_SRC) &&  		    (val & WM8994_AIF2ADCR_SRC)) @@ -1218,6 +1271,12 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w,  		break;  	} +	switch (event) { +	case SND_SOC_DAPM_POST_PMD: +		aif_mclk_set(component, 1, false); +		break; +	} +  	return 0;  } @@ -1623,10 +1682,10 @@ SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)  static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = {  SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, aif1clk_ev,  		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | -		    SND_SOC_DAPM_PRE_PMD), +		    SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),  SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, aif2clk_ev,  		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | -		    SND_SOC_DAPM_PRE_PMD), +		    SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),  SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),  SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,  		   left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), @@ -2141,6 +2200,7 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src,  	u16 reg, clk1, aif_reg, aif_src;  	unsigned long timeout;  	bool was_enabled; +	struct clk *mclk;  	switch (id) {  	case WM8994_FLL1: @@ -2216,6 +2276,27 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src,  	snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_1 + reg_offset,  			    WM8994_FLL1_ENA, 0); +	/* Disable MCLK if needed before we possibly change to new clock parent */ +	if (was_enabled) { +		reg = snd_soc_component_read32(component, WM8994_FLL1_CONTROL_5 +							+ reg_offset); +		reg = ((reg & WM8994_FLL1_REFCLK_SRC_MASK) +			>> WM8994_FLL1_REFCLK_SRC_SHIFT) + 1; + +		switch (reg) { +		case WM8994_FLL_SRC_MCLK1: +			mclk = wm8994->mclk[WM8994_MCLK1].clk; +			break; +		case WM8994_FLL_SRC_MCLK2: +			mclk = wm8994->mclk[WM8994_MCLK2].clk; +			break; +		default: +			mclk = NULL; +		} + +		clk_disable_unprepare(mclk); +	} +  	if (wm8994->fll_byp && src == WM8994_FLL_SRC_BCLK &&  	    freq_in == freq_out && freq_out) {  		dev_dbg(component->dev, "Bypassing FLL%d\n", id + 1); @@ -2260,10 +2341,29 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src,  	/* Clear any pending completion from a previous failure */  	try_wait_for_completion(&wm8994->fll_locked[id]); +	switch (src) { +	case WM8994_FLL_SRC_MCLK1: +		mclk = wm8994->mclk[WM8994_MCLK1].clk; +		break; +	case WM8994_FLL_SRC_MCLK2: +		mclk = wm8994->mclk[WM8994_MCLK2].clk; +		break; +	default: +		mclk = NULL; +	} +  	/* Enable (with fractional mode if required) */  	if (freq_out) { +		ret = clk_prepare_enable(mclk); +		if (ret < 0) { +			dev_err(component->dev, "Failed to enable MCLK for FLL%d\n", +				id + 1); +			return ret; +		} +  		/* Enable VMID if we need it */  		if (!was_enabled) { +  			active_reference(component);  			switch (control->type) { @@ -2372,12 +2472,29 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,  	return _wm8994_set_fll(dai->component, id, src, freq_in, freq_out);  } +static int wm8994_set_mclk_rate(struct wm8994_priv *wm8994, unsigned int id, +				unsigned int *freq) +{ +	int ret; + +	if (!wm8994->mclk[id].clk || *freq == wm8994->mclk_rate[id]) +		return 0; + +	ret = clk_set_rate(wm8994->mclk[id].clk, *freq); +	if (ret < 0) +		return ret; + +	*freq = clk_get_rate(wm8994->mclk[id].clk); + +	return 0; +} +  static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,  		int clk_id, unsigned int freq, int dir)  {  	struct snd_soc_component *component = dai->component;  	struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); -	int i; +	int ret, i;  	switch (dai->id) {  	case 1: @@ -2392,7 +2509,12 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,  	switch (clk_id) {  	case WM8994_SYSCLK_MCLK1:  		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1; -		wm8994->mclk[0] = freq; + +		ret = wm8994_set_mclk_rate(wm8994, dai->id - 1, &freq); +		if (ret < 0) +			return ret; + +		wm8994->mclk_rate[0] = freq;  		dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n",  			dai->id, freq);  		break; @@ -2400,7 +2522,12 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,  	case WM8994_SYSCLK_MCLK2:  		/* TODO: Set GPIO AF */  		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2; -		wm8994->mclk[1] = freq; + +		ret = wm8994_set_mclk_rate(wm8994, dai->id - 1, &freq); +		if (ret < 0) +			return ret; + +		wm8994->mclk_rate[1] = freq;  		dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n",  			dai->id, freq);  		break; @@ -4456,6 +4583,7 @@ static const struct snd_soc_component_driver soc_component_dev_wm8994 = {  static int wm8994_probe(struct platform_device *pdev)  {  	struct wm8994_priv *wm8994; +	int ret;  	wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv),  			      GFP_KERNEL); @@ -4467,6 +4595,16 @@ static int wm8994_probe(struct platform_device *pdev)  	wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent); +	wm8994->mclk[WM8994_MCLK1].id = "MCLK1"; +	wm8994->mclk[WM8994_MCLK2].id = "MCLK2"; + +	ret = devm_clk_bulk_get_optional(pdev->dev.parent, ARRAY_SIZE(wm8994->mclk), +					 wm8994->mclk); +	if (ret < 0) { +		dev_err(&pdev->dev, "Failed to get clocks: %d\n", ret); +		return ret; +	} +  	pm_runtime_enable(&pdev->dev);  	pm_runtime_idle(&pdev->dev); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 1d6f2abe1c11..41c4b126114d 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -6,6 +6,7 @@  #ifndef _WM8994_H  #define _WM8994_H +#include <linux/clk.h>  #include <sound/soc.h>  #include <linux/firmware.h>  #include <linux/completion.h> @@ -14,6 +15,12 @@  #include "wm_hubs.h" +enum { +	WM8994_MCLK1, +	WM8994_MCLK2, +	WM8994_NUM_MCLK +}; +  /* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */  #define WM8994_SYSCLK_MCLK1 1  #define WM8994_SYSCLK_MCLK2 2 @@ -73,9 +80,10 @@ struct wm8994;  struct wm8994_priv {  	struct wm_hubs_data hubs;  	struct wm8994 *wm8994; +	struct clk_bulk_data mclk[WM8994_NUM_MCLK];  	int sysclk[2];  	int sysclk_rate[2]; -	int mclk[2]; +	int mclk_rate[2];  	int aifclk[2];  	int aifdiv[2];  	int channels[2]; diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 9b8bb7bbe945..2a9b610f6d43 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -599,6 +599,9 @@ struct wm_coeff_ctl_ops {  struct wm_coeff_ctl {  	const char *name;  	const char *fw_name; +	/* Subname is needed to match with firmware */ +	const char *subname; +	unsigned int subname_len;  	struct wm_adsp_alg_region alg_region;  	struct wm_coeff_ctl_ops ops;  	struct wm_adsp *dsp; @@ -1399,6 +1402,7 @@ static void wm_adsp_free_ctl_blk(struct wm_coeff_ctl *ctl)  {  	kfree(ctl->cache);  	kfree(ctl->name); +	kfree(ctl->subname);  	kfree(ctl);  } @@ -1472,6 +1476,15 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,  		ret = -ENOMEM;  		goto err_ctl;  	} +	if (subname) { +		ctl->subname_len = subname_len; +		ctl->subname = kmemdup(subname, +				       strlen(subname) + 1, GFP_KERNEL); +		if (!ctl->subname) { +			ret = -ENOMEM; +			goto err_ctl_name; +		} +	}  	ctl->enabled = 1;  	ctl->set = 0;  	ctl->ops.xget = wm_coeff_get; @@ -1485,7 +1498,7 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,  	ctl->cache = kzalloc(ctl->len, GFP_KERNEL);  	if (!ctl->cache) {  		ret = -ENOMEM; -		goto err_ctl_name; +		goto err_ctl_subname;  	}  	list_add(&ctl->list, &dsp->ctl_list); @@ -1508,6 +1521,8 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,  err_ctl_cache:  	kfree(ctl->cache); +err_ctl_subname: +	kfree(ctl->subname);  err_ctl_name:  	kfree(ctl->name);  err_ctl: @@ -1995,6 +2010,70 @@ out:  	return ret;  } +/* + * Find wm_coeff_ctl with input name as its subname + * If not found, return NULL + */ +static struct wm_coeff_ctl *wm_adsp_get_ctl(struct wm_adsp *dsp, +					     const char *name, int type, +					     unsigned int alg) +{ +	struct wm_coeff_ctl *pos, *rslt = NULL; + +	list_for_each_entry(pos, &dsp->ctl_list, list) { +		if (!pos->subname) +			continue; +		if (strncmp(pos->subname, name, pos->subname_len) == 0 && +				pos->alg_region.alg == alg && +				pos->alg_region.type == type) { +			rslt = pos; +			break; +		} +	} + +	return rslt; +} + +int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, +		      unsigned int alg, void *buf, size_t len) +{ +	struct wm_coeff_ctl *ctl; +	struct snd_kcontrol *kcontrol; +	int ret; + +	ctl = wm_adsp_get_ctl(dsp, name, type, alg); +	if (!ctl) +		return -EINVAL; + +	if (len > ctl->len) +		return -EINVAL; + +	ret = wm_coeff_write_control(ctl, buf, len); + +	kcontrol = snd_soc_card_get_kcontrol(dsp->component->card, ctl->name); +	snd_ctl_notify(dsp->component->card->snd_card, +		       SNDRV_CTL_EVENT_MASK_VALUE, &kcontrol->id); + +	return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + +int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, +		     unsigned int alg, void *buf, size_t len) +{ +	struct wm_coeff_ctl *ctl; + +	ctl = wm_adsp_get_ctl(dsp, name, type, alg); +	if (!ctl) +		return -EINVAL; + +	if (len > ctl->len) +		return -EINVAL; + +	return wm_coeff_read_control(ctl, buf, len); +} +EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); +  static void wm_adsp_ctl_fixup_base(struct wm_adsp *dsp,  				  const struct wm_adsp_alg_region *alg_region)  { diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index aa634ef6c9f5..4c481cf20275 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -201,5 +201,9 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream,  			  struct snd_compr_tstamp *tstamp);  int wm_adsp_compr_copy(struct snd_compr_stream *stream,  		       char __user *buf, size_t count); +int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name,  int type, +		      unsigned int alg, void *buf, size_t len); +int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name,  int type, +		      unsigned int alg, void *buf, size_t len);  #endif  | 

