diff options
Diffstat (limited to 'sound/soc/codecs/tlv320adcx140.c')
-rw-r--r-- | sound/soc/codecs/tlv320adcx140.c | 278 |
1 files changed, 228 insertions, 50 deletions
diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index 8efe20605f9b..41342b340680 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -30,10 +30,10 @@ struct adcx140_priv { struct regmap *regmap; struct device *dev; - int micbias_vg; + bool micbias_vg; + bool phase_calib_on; unsigned int dai_fmt; - unsigned int tdm_delay; unsigned int slot_width; }; @@ -161,7 +161,7 @@ static const struct regmap_config adcx140_i2c_regmap = { }; /* Digital Volume control. From -100 to 27 dB in 0.5 dB steps */ -static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10000, 50, 0); +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10050, 50, 0); /* ADC gain. From 0 to 42 dB in 1 dB steps */ static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 100, 0); @@ -412,6 +412,16 @@ static const struct snd_soc_dapm_widget adcx140_dapm_widgets[] = { SND_SOC_DAPM_ADC("CH3_ADC", "CH3 Capture", ADCX140_IN_CH_EN, 5, 0), SND_SOC_DAPM_ADC("CH4_ADC", "CH4 Capture", ADCX140_IN_CH_EN, 4, 0), + SND_SOC_DAPM_ADC("CH1_DIG", "CH1 Capture", ADCX140_IN_CH_EN, 7, 0), + SND_SOC_DAPM_ADC("CH2_DIG", "CH2 Capture", ADCX140_IN_CH_EN, 6, 0), + SND_SOC_DAPM_ADC("CH3_DIG", "CH3 Capture", ADCX140_IN_CH_EN, 5, 0), + SND_SOC_DAPM_ADC("CH4_DIG", "CH4 Capture", ADCX140_IN_CH_EN, 4, 0), + SND_SOC_DAPM_ADC("CH5_DIG", "CH5 Capture", ADCX140_IN_CH_EN, 3, 0), + SND_SOC_DAPM_ADC("CH6_DIG", "CH6 Capture", ADCX140_IN_CH_EN, 2, 0), + SND_SOC_DAPM_ADC("CH7_DIG", "CH7 Capture", ADCX140_IN_CH_EN, 1, 0), + SND_SOC_DAPM_ADC("CH8_DIG", "CH8 Capture", ADCX140_IN_CH_EN, 0, 0), + + SND_SOC_DAPM_SWITCH("CH1_ASI_EN", SND_SOC_NOPM, 0, 0, &adcx140_dapm_ch1_en_switch), SND_SOC_DAPM_SWITCH("CH2_ASI_EN", SND_SOC_NOPM, 0, 0, @@ -470,6 +480,15 @@ static const struct snd_soc_dapm_route adcx140_audio_map[] = { {"CH3_ASI_EN", "Switch", "CH3_ADC"}, {"CH4_ASI_EN", "Switch", "CH4_ADC"}, + {"CH1_ASI_EN", "Switch", "CH1_DIG"}, + {"CH2_ASI_EN", "Switch", "CH2_DIG"}, + {"CH3_ASI_EN", "Switch", "CH3_DIG"}, + {"CH4_ASI_EN", "Switch", "CH4_DIG"}, + {"CH5_ASI_EN", "Switch", "CH5_DIG"}, + {"CH6_ASI_EN", "Switch", "CH6_DIG"}, + {"CH7_ASI_EN", "Switch", "CH7_DIG"}, + {"CH8_ASI_EN", "Switch", "CH8_DIG"}, + {"CH5_ASI_EN", "Switch", "CH5_OUT"}, {"CH6_ASI_EN", "Switch", "CH6_OUT"}, {"CH7_ASI_EN", "Switch", "CH7_OUT"}, @@ -541,6 +560,15 @@ static const struct snd_soc_dapm_route adcx140_audio_map[] = { {"PDM Clk Div Select", "705.6 kHz", "MIC1P Input Mux"}, {"PDM Clk Div Select", "5.6448 MHz", "MIC1P Input Mux"}, + {"MIC1P Input Mux", NULL, "CH1_DIG"}, + {"MIC1M Input Mux", NULL, "CH2_DIG"}, + {"MIC2P Input Mux", NULL, "CH3_DIG"}, + {"MIC2M Input Mux", NULL, "CH4_DIG"}, + {"MIC3P Input Mux", NULL, "CH5_DIG"}, + {"MIC3M Input Mux", NULL, "CH6_DIG"}, + {"MIC4P Input Mux", NULL, "CH7_DIG"}, + {"MIC4M Input Mux", NULL, "CH8_DIG"}, + {"MIC1 Analog Mux", "Line In", "MIC1P"}, {"MIC2 Analog Mux", "Line In", "MIC2P"}, {"MIC3 Analog Mux", "Line In", "MIC3P"}, @@ -554,8 +582,63 @@ static const struct snd_soc_dapm_route adcx140_audio_map[] = { {"MIC3M Input Mux", "Analog", "MIC3M"}, {"MIC4P Input Mux", "Analog", "MIC4P"}, {"MIC4M Input Mux", "Analog", "MIC4M"}, + + {"MIC1P Input Mux", "Digital", "MIC1P"}, + {"MIC1M Input Mux", "Digital", "MIC1M"}, + {"MIC2P Input Mux", "Digital", "MIC2P"}, + {"MIC2M Input Mux", "Digital", "MIC2M"}, + {"MIC3P Input Mux", "Digital", "MIC3P"}, + {"MIC3M Input Mux", "Digital", "MIC3M"}, + {"MIC4P Input Mux", "Digital", "MIC4P"}, + {"MIC4M Input Mux", "Digital", "MIC4M"}, }; +#define ADCX140_PHASE_CALIB_SWITCH(xname) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = adcx140_phase_calib_info, \ + .get = adcx140_phase_calib_get, \ + .put = adcx140_phase_calib_put} + +static int adcx140_phase_calib_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int adcx140_phase_calib_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_soc_component *codec = + snd_soc_kcontrol_component(kcontrol); + struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(codec); + + value->value.integer.value[0] = adcx140->phase_calib_on ? 1 : 0; + + + return 0; +} + +static int adcx140_phase_calib_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_soc_component *codec + = snd_soc_kcontrol_component(kcontrol); + struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(codec); + + bool v = value->value.integer.value[0] ? true : false; + + if (adcx140->phase_calib_on != v) { + adcx140->phase_calib_on = v; + return 1; + } + return 0; +} + static const struct snd_kcontrol_new adcx140_snd_controls[] = { SOC_SINGLE_TLV("Analog CH1 Mic Gain Volume", ADCX140_CH1_CFG1, 2, 42, 0, adc_tlv), @@ -592,6 +675,7 @@ static const struct snd_kcontrol_new adcx140_snd_controls[] = { 0, 0xff, 0, dig_vol_tlv), SOC_SINGLE_TLV("Digital CH8 Out Volume", ADCX140_CH8_CFG2, 0, 0xff, 0, dig_vol_tlv), + ADCX140_PHASE_CALIB_SWITCH("Phase Calibration Switch"), }; static int adcx140_reset(struct adcx140_priv *adcx140) @@ -614,11 +698,36 @@ static int adcx140_reset(struct adcx140_priv *adcx140) return ret; } +static void adcx140_pwr_ctrl(struct adcx140_priv *adcx140, bool power_state) +{ + int pwr_ctrl = 0; + int ret = 0; + struct snd_soc_component *component = adcx140->component; + + if (power_state) + pwr_ctrl = ADCX140_PWR_CFG_ADC_PDZ | ADCX140_PWR_CFG_PLL_PDZ; + + if (adcx140->micbias_vg && power_state) + pwr_ctrl |= ADCX140_PWR_CFG_BIAS_PDZ; + + if (pwr_ctrl) { + ret = regmap_write(adcx140->regmap, ADCX140_PHASE_CALIB, + adcx140->phase_calib_on ? 0x00 : 0x40); + if (ret) + dev_err(component->dev, "%s: register write error %d\n", + __func__, ret); + } + + regmap_update_bits(adcx140->regmap, ADCX140_PWR_CFG, + ADCX140_PWR_CTRL_MSK, pwr_ctrl); +} + static int adcx140_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 adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component); u8 data = 0; switch (params_width(params)) { @@ -640,9 +749,13 @@ static int adcx140_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + adcx140_pwr_ctrl(adcx140, false); + snd_soc_component_update_bits(component, ADCX140_ASI_CFG0, ADCX140_WORD_LEN_MSK, data); + adcx140_pwr_ctrl(adcx140, true); + return 0; } @@ -654,37 +767,17 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai, u8 iface_reg1 = 0; u8 iface_reg2 = 0; int offset = 0; - int width = adcx140->slot_width; + bool inverted_bclk = false; /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: iface_reg2 |= ADCX140_BCLK_FSYNC_MASTER; break; - case SND_SOC_DAIFMT_CBS_CFS: - break; - case SND_SOC_DAIFMT_CBS_CFM: - case SND_SOC_DAIFMT_CBM_CFS: - default: - dev_err(component->dev, "Invalid DAI master/slave interface\n"); - return -EINVAL; - } - - /* signal polarity */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_IF: - iface_reg1 |= ADCX140_FSYNCINV_BIT; - break; - case SND_SOC_DAIFMT_IB_IF: - iface_reg1 |= ADCX140_BCLKINV_BIT | ADCX140_FSYNCINV_BIT; - break; - case SND_SOC_DAIFMT_IB_NF: - iface_reg1 |= ADCX140_BCLKINV_BIT; - break; - case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_CBC_CFC: break; default: - dev_err(component->dev, "Invalid DAI clock signal polarity\n"); + dev_err(component->dev, "Invalid DAI clock provider\n"); return -EINVAL; } @@ -697,18 +790,40 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai, iface_reg1 |= ADCX140_LEFT_JUST_BIT; break; case SND_SOC_DAIFMT_DSP_A: - offset += (adcx140->tdm_delay * width + 1); + offset = 1; + inverted_bclk = true; break; case SND_SOC_DAIFMT_DSP_B: - offset += adcx140->tdm_delay * width; + inverted_bclk = true; break; default: dev_err(component->dev, "Invalid DAI interface format\n"); return -EINVAL; } + /* signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + inverted_bclk = !inverted_bclk; + break; + case SND_SOC_DAIFMT_NB_IF: + iface_reg1 |= ADCX140_FSYNCINV_BIT; + break; + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(component->dev, "Invalid DAI clock signal polarity\n"); + return -EINVAL; + } + + if (inverted_bclk) + iface_reg1 |= ADCX140_BCLKINV_BIT; + adcx140->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + adcx140_pwr_ctrl(adcx140, false); + snd_soc_component_update_bits(component, ADCX140_ASI_CFG0, ADCX140_FSYNCINV_BIT | ADCX140_BCLKINV_BIT | @@ -721,6 +836,7 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai, snd_soc_component_update_bits(component, ADCX140_ASI_CFG1, ADCX140_TX_OFFSET_MASK, offset); + adcx140_pwr_ctrl(adcx140, true); return 0; } @@ -731,12 +847,13 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, { struct snd_soc_component *component = codec_dai->component; struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component); - unsigned int lsb; - /* TDM based on DSP mode requires slots to be adjacent */ - lsb = __ffs(tx_mask); - if ((lsb + 1) != __fls(tx_mask)) { - dev_err(component->dev, "Invalid mask, slots must be adjacent\n"); + /* + * The chip itself supports arbitrary masks, but the driver currently + * only supports adjacent slots beginning at the first slot. + */ + if (tx_mask != GENMASK(__fls(tx_mask), 0)) { + dev_err(component->dev, "Only lower adjacent slots are supported\n"); return -EINVAL; } @@ -751,7 +868,6 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, return -EINVAL; } - adcx140->tdm_delay = lsb; adcx140->slot_width = slot_width; return 0; @@ -800,6 +916,42 @@ static int adcx140_configure_gpo(struct adcx140_priv *adcx140) } +static int adcx140_configure_gpio(struct adcx140_priv *adcx140) +{ + int gpio_count = 0; + u32 gpio_outputs[ADCX140_NUM_GPIO_CFGS]; + u32 gpio_output_val = 0; + int ret; + + gpio_count = device_property_count_u32(adcx140->dev, + "ti,gpio-config"); + if (gpio_count <= 0) + return 0; + + if (gpio_count != ADCX140_NUM_GPIO_CFGS) + return -EINVAL; + + ret = device_property_read_u32_array(adcx140->dev, "ti,gpio-config", + gpio_outputs, gpio_count); + if (ret) + return ret; + + if (gpio_outputs[0] > ADCX140_GPIO_CFG_MAX) { + dev_err(adcx140->dev, "GPIO config out of range\n"); + return -EINVAL; + } + + if (gpio_outputs[1] > ADCX140_GPIO_DRV_MAX) { + dev_err(adcx140->dev, "GPIO drive out of range\n"); + return -EINVAL; + } + + gpio_output_val = gpio_outputs[0] << ADCX140_GPIO_SHIFT + | gpio_outputs[1]; + + return regmap_write(adcx140->regmap, ADCX140_GPIO_CFG0, gpio_output_val); +} + static int adcx140_codec_probe(struct snd_soc_component *component) { struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component); @@ -815,15 +967,15 @@ static int adcx140_codec_probe(struct snd_soc_component *component) u32 gpi_input_val = 0; int i; int ret; + bool tx_high_z; ret = device_property_read_u32(adcx140->dev, "ti,mic-bias-source", &bias_source); - if (ret) + if (ret || bias_source > ADCX140_MIC_BIAS_VAL_AVDD) { bias_source = ADCX140_MIC_BIAS_VAL_VREF; - - if (bias_source > ADCX140_MIC_BIAS_VAL_AVDD) { - dev_err(adcx140->dev, "Mic Bias source value is invalid\n"); - return -EINVAL; + adcx140->micbias_vg = false; + } else { + adcx140->micbias_vg = true; } ret = device_property_read_u32(adcx140->dev, "ti,vref-source", @@ -897,6 +1049,10 @@ static int adcx140_codec_probe(struct snd_soc_component *component) return ret; } + ret = adcx140_configure_gpio(adcx140); + if (ret) + return ret; + ret = adcx140_configure_gpo(adcx140); if (ret) goto out; @@ -906,6 +1062,18 @@ static int adcx140_codec_probe(struct snd_soc_component *component) ADCX140_MIC_BIAS_VREF_MSK, bias_cfg); if (ret) dev_err(adcx140->dev, "setting MIC bias failed %d\n", ret); + + tx_high_z = device_property_read_bool(adcx140->dev, "ti,asi-tx-drive"); + if (tx_high_z) { + ret = regmap_update_bits(adcx140->regmap, ADCX140_ASI_CFG0, + ADCX140_TX_FILL, ADCX140_TX_FILL); + if (ret) { + dev_err(adcx140->dev, "Setting Tx drive failed %d\n", ret); + goto out; + } + } + + adcx140_pwr_ctrl(adcx140, true); out: return ret; } @@ -914,21 +1082,19 @@ static int adcx140_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component); - int pwr_cfg = 0; switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: case SND_SOC_BIAS_STANDBY: - pwr_cfg = ADCX140_PWR_CFG_BIAS_PDZ | ADCX140_PWR_CFG_PLL_PDZ | - ADCX140_PWR_CFG_ADC_PDZ; + adcx140_pwr_ctrl(adcx140, true); break; case SND_SOC_BIAS_OFF: - pwr_cfg = 0x0; + adcx140_pwr_ctrl(adcx140, false); break; } - return regmap_write(adcx140->regmap, ADCX140_PWR_CFG, pwr_cfg); + return 0; } static const struct snd_soc_component_driver soc_codec_driver_adcx140 = { @@ -944,7 +1110,6 @@ static const struct snd_soc_component_driver soc_codec_driver_adcx140 = { .idle_bias_on = 0, .use_pmdown_time = 1, .endianness = 1, - .non_legacy_dai_naming = 1, }; static struct snd_soc_dai_driver adcx140_dai_driver[] = { @@ -958,10 +1123,11 @@ static struct snd_soc_dai_driver adcx140_dai_driver[] = { .formats = ADCX140_FORMATS, }, .ops = &adcx140_dai_ops, - .symmetric_rates = 1, + .symmetric_rate = 1, } }; +#ifdef CONFIG_OF static const struct of_device_id tlv320adcx140_of_match[] = { { .compatible = "ti,tlv320adc3140" }, { .compatible = "ti,tlv320adc5140" }, @@ -969,9 +1135,16 @@ static const struct of_device_id tlv320adcx140_of_match[] = { {}, }; MODULE_DEVICE_TABLE(of, tlv320adcx140_of_match); +#endif -static int adcx140_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static void adcx140_disable_regulator(void *arg) +{ + struct adcx140_priv *adcx140 = arg; + + regulator_disable(adcx140->supply_areg); +} + +static int adcx140_i2c_probe(struct i2c_client *i2c) { struct adcx140_priv *adcx140; int ret; @@ -980,6 +1153,7 @@ static int adcx140_i2c_probe(struct i2c_client *i2c, if (!adcx140) return -ENOMEM; + adcx140->phase_calib_on = false; adcx140->dev = &i2c->dev; adcx140->gpio_reset = devm_gpiod_get_optional(adcx140->dev, @@ -1000,6 +1174,10 @@ static int adcx140_i2c_probe(struct i2c_client *i2c, dev_err(adcx140->dev, "Failed to enable areg\n"); return ret; } + + ret = devm_add_action_or_reset(&i2c->dev, adcx140_disable_regulator, adcx140); + if (ret) + return ret; } adcx140->regmap = devm_regmap_init_i2c(i2c, &adcx140_i2c_regmap); |