diff options
Diffstat (limited to 'sound/soc/codecs/cs42l51.c')
| -rw-r--r-- | sound/soc/codecs/cs42l51.c | 225 | 
1 files changed, 220 insertions, 5 deletions
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index fd2bd74024c1..991e4ebd7a04 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -30,7 +30,9 @@  #include <sound/initval.h>  #include <sound/pcm_params.h>  #include <sound/pcm.h> +#include <linux/gpio/consumer.h>  #include <linux/regmap.h> +#include <linux/regulator/consumer.h>  #include "cs42l51.h" @@ -40,11 +42,21 @@ enum master_slave_mode {  	MODE_MASTER,  }; +static const char * const cs42l51_supply_names[] = { +	"VL", +	"VD", +	"VA", +	"VAHP", +}; +  struct cs42l51_private {  	unsigned int mclk;  	struct clk *mclk_handle;  	unsigned int audio_mode;	/* The mode (I2S or left-justified) */  	enum master_slave_mode func; +	struct regulator_bulk_data supplies[ARRAY_SIZE(cs42l51_supply_names)]; +	struct gpio_desc *reset_gpio; +	struct regmap *regmap;  };  #define CS42L51_FORMATS ( \ @@ -111,6 +123,7 @@ static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);  static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0);  static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const DECLARE_TLV_DB_SCALE(adc_boost_tlv, 2000, 2000, 0);  static const char *chan_mix[] = {  	"L R",  	"L+R", @@ -139,6 +152,8 @@ static const struct snd_kcontrol_new cs42l51_snd_controls[] = {  	SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),  	SOC_DOUBLE_TLV("Mic Boost Volume",  			CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), +	SOC_DOUBLE_TLV("ADC Boost Volume", +		       CS42L51_MIC_CTL, 5, 6, 1, 0, adc_boost_tlv),  	SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),  	SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),  	SOC_ENUM_EXT("PCM channel mixer", @@ -195,7 +210,8 @@ static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =  	SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);  static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { -	SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), +	SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1, NULL, +			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),  	SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,  		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),  	SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, @@ -329,6 +345,19 @@ static struct cs42l51_ratios slave_auto_ratios[] = {  	{  256, CS42L51_DSM_MODE, 1 }, {  384, CS42L51_DSM_MODE, 1 },  }; +/* + * Master mode mclk/fs ratios. + * Recommended configurations are SSM for 4-50khz and DSM for 50-100kHz ranges + * The table below provides support of following ratios: + * 128: SSM (%128) with div2 disabled + * 256: SSM (%128) with div2 enabled + * In both cases, if sampling rate is above 50kHz, SSM is overridden + * with DSM (%128) configuration + */ +static struct cs42l51_ratios master_ratios[] = { +	{ 128, CS42L51_SSM_MODE, 0 }, { 256, CS42L51_SSM_MODE, 1 }, +}; +  static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai,  		int clk_id, unsigned int freq, int dir)  { @@ -351,11 +380,13 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream,  	unsigned int ratio;  	struct cs42l51_ratios *ratios = NULL;  	int nr_ratios = 0; -	int intf_ctl, power_ctl, fmt; +	int intf_ctl, power_ctl, fmt, mode;  	switch (cs42l51->func) {  	case MODE_MASTER: -		return -EINVAL; +		ratios = master_ratios; +		nr_ratios = ARRAY_SIZE(master_ratios); +		break;  	case MODE_SLAVE:  		ratios = slave_ratios;  		nr_ratios = ARRAY_SIZE(slave_ratios); @@ -391,7 +422,16 @@ static int cs42l51_hw_params(struct snd_pcm_substream *substream,  	switch (cs42l51->func) {  	case MODE_MASTER:  		intf_ctl |= CS42L51_INTF_CTL_MASTER; -		power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); +		mode = ratios[i].speed_mode; +		/* Force DSM mode if sampling rate is above 50kHz */ +		if (rate > 50000) +			mode = CS42L51_DSM_MODE; +		power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(mode); +		/* +		 * Auto detect mode is not applicable for master mode and has to +		 * be disabled. Otherwise SPEED[1:0] bits will be ignored. +		 */ +		power_ctl &= ~CS42L51_MIC_POWER_CTL_AUTO;  		break;  	case MODE_SLAVE:  		power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); @@ -464,6 +504,13 @@ static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)  	return snd_soc_component_write(component, CS42L51_DAC_OUT_CTL, reg);  } +static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component, +				   struct device_node *endpoint) +{ +	/* return dai id 0, whatever the endpoint index */ +	return 0; +} +  static const struct snd_soc_dai_ops cs42l51_dai_ops = {  	.hw_params      = cs42l51_hw_params,  	.set_sysclk     = cs42l51_set_dai_sysclk, @@ -526,13 +573,113 @@ static const struct snd_soc_component_driver soc_component_device_cs42l51 = {  	.num_dapm_widgets	= ARRAY_SIZE(cs42l51_dapm_widgets),  	.dapm_routes		= cs42l51_routes,  	.num_dapm_routes	= ARRAY_SIZE(cs42l51_routes), +	.of_xlate_dai_id	= cs42l51_of_xlate_dai_id,  	.idle_bias_on		= 1,  	.use_pmdown_time	= 1,  	.endianness		= 1,  	.non_legacy_dai_naming	= 1,  }; +static bool cs42l51_writeable_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS42L51_POWER_CTL1: +	case CS42L51_MIC_POWER_CTL: +	case CS42L51_INTF_CTL: +	case CS42L51_MIC_CTL: +	case CS42L51_ADC_CTL: +	case CS42L51_ADC_INPUT: +	case CS42L51_DAC_OUT_CTL: +	case CS42L51_DAC_CTL: +	case CS42L51_ALC_PGA_CTL: +	case CS42L51_ALC_PGB_CTL: +	case CS42L51_ADCA_ATT: +	case CS42L51_ADCB_ATT: +	case CS42L51_ADCA_VOL: +	case CS42L51_ADCB_VOL: +	case CS42L51_PCMA_VOL: +	case CS42L51_PCMB_VOL: +	case CS42L51_BEEP_FREQ: +	case CS42L51_BEEP_VOL: +	case CS42L51_BEEP_CONF: +	case CS42L51_TONE_CTL: +	case CS42L51_AOUTA_VOL: +	case CS42L51_AOUTB_VOL: +	case CS42L51_PCM_MIXER: +	case CS42L51_LIMIT_THRES_DIS: +	case CS42L51_LIMIT_REL: +	case CS42L51_LIMIT_ATT: +	case CS42L51_ALC_EN: +	case CS42L51_ALC_REL: +	case CS42L51_ALC_THRES: +	case CS42L51_NOISE_CONF: +	case CS42L51_CHARGE_FREQ: +		return true; +	default: +		return false; +	} +} + +static bool cs42l51_volatile_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS42L51_STATUS: +		return true; +	default: +		return false; +	} +} + +static bool cs42l51_readable_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case CS42L51_CHIP_REV_ID: +	case CS42L51_POWER_CTL1: +	case CS42L51_MIC_POWER_CTL: +	case CS42L51_INTF_CTL: +	case CS42L51_MIC_CTL: +	case CS42L51_ADC_CTL: +	case CS42L51_ADC_INPUT: +	case CS42L51_DAC_OUT_CTL: +	case CS42L51_DAC_CTL: +	case CS42L51_ALC_PGA_CTL: +	case CS42L51_ALC_PGB_CTL: +	case CS42L51_ADCA_ATT: +	case CS42L51_ADCB_ATT: +	case CS42L51_ADCA_VOL: +	case CS42L51_ADCB_VOL: +	case CS42L51_PCMA_VOL: +	case CS42L51_PCMB_VOL: +	case CS42L51_BEEP_FREQ: +	case CS42L51_BEEP_VOL: +	case CS42L51_BEEP_CONF: +	case CS42L51_TONE_CTL: +	case CS42L51_AOUTA_VOL: +	case CS42L51_AOUTB_VOL: +	case CS42L51_PCM_MIXER: +	case CS42L51_LIMIT_THRES_DIS: +	case CS42L51_LIMIT_REL: +	case CS42L51_LIMIT_ATT: +	case CS42L51_ALC_EN: +	case CS42L51_ALC_REL: +	case CS42L51_ALC_THRES: +	case CS42L51_NOISE_CONF: +	case CS42L51_STATUS: +	case CS42L51_CHARGE_FREQ: +		return true; +	default: +		return false; +	} +} +  const struct regmap_config cs42l51_regmap = { +	.reg_bits = 8, +	.reg_stride = 1, +	.val_bits = 8, +	.use_single_write = true, +	.readable_reg = cs42l51_readable_reg, +	.volatile_reg = cs42l51_volatile_reg, +	.writeable_reg = cs42l51_writeable_reg,  	.max_register = CS42L51_CHARGE_FREQ,  	.cache_type = REGCACHE_RBTREE,  }; @@ -542,7 +689,7 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)  {  	struct cs42l51_private *cs42l51;  	unsigned int val; -	int ret; +	int ret, i;  	if (IS_ERR(regmap))  		return PTR_ERR(regmap); @@ -553,6 +700,7 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)  		return -ENOMEM;  	dev_set_drvdata(dev, cs42l51); +	cs42l51->regmap = regmap;  	cs42l51->mclk_handle = devm_clk_get(dev, "MCLK");  	if (IS_ERR(cs42l51->mclk_handle)) { @@ -561,6 +709,34 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)  		cs42l51->mclk_handle = NULL;  	} +	for (i = 0; i < ARRAY_SIZE(cs42l51->supplies); i++) +		cs42l51->supplies[i].supply = cs42l51_supply_names[i]; + +	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs42l51->supplies), +				      cs42l51->supplies); +	if (ret != 0) { +		dev_err(dev, "Failed to request supplies: %d\n", ret); +		return ret; +	} + +	ret = regulator_bulk_enable(ARRAY_SIZE(cs42l51->supplies), +				    cs42l51->supplies); +	if (ret != 0) { +		dev_err(dev, "Failed to enable supplies: %d\n", ret); +		return ret; +	} + +	cs42l51->reset_gpio = devm_gpiod_get_optional(dev, "reset", +						      GPIOD_OUT_LOW); +	if (IS_ERR(cs42l51->reset_gpio)) +		return PTR_ERR(cs42l51->reset_gpio); + +	if (cs42l51->reset_gpio) { +		dev_dbg(dev, "Release reset gpio\n"); +		gpiod_set_value_cansleep(cs42l51->reset_gpio, 0); +		mdelay(2); +	} +  	/* Verify that we have a CS42L51 */  	ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val);  	if (ret < 0) { @@ -579,11 +755,50 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap)  	ret = devm_snd_soc_register_component(dev,  			&soc_component_device_cs42l51, &cs42l51_dai, 1); +	if (ret < 0) +		goto error; + +	return 0; +  error: +	regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies), +			       cs42l51->supplies);  	return ret;  }  EXPORT_SYMBOL_GPL(cs42l51_probe); +int cs42l51_remove(struct device *dev) +{ +	struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + +	gpiod_set_value_cansleep(cs42l51->reset_gpio, 1); + +	return regulator_bulk_disable(ARRAY_SIZE(cs42l51->supplies), +				      cs42l51->supplies); +} +EXPORT_SYMBOL_GPL(cs42l51_remove); + +int __maybe_unused cs42l51_suspend(struct device *dev) +{ +	struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + +	regcache_cache_only(cs42l51->regmap, true); +	regcache_mark_dirty(cs42l51->regmap); + +	return 0; +} +EXPORT_SYMBOL_GPL(cs42l51_suspend); + +int __maybe_unused cs42l51_resume(struct device *dev) +{ +	struct cs42l51_private *cs42l51 = dev_get_drvdata(dev); + +	regcache_cache_only(cs42l51->regmap, false); + +	return regcache_sync(cs42l51->regmap); +} +EXPORT_SYMBOL_GPL(cs42l51_resume); +  const struct of_device_id cs42l51_of_match[] = {  	{ .compatible = "cirrus,cs42l51", },  	{ }  |