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", }, { } |