diff options
Diffstat (limited to 'sound/soc/codecs/cs42l42.c')
| -rw-r--r-- | sound/soc/codecs/cs42l42.c | 132 | 
1 files changed, 96 insertions, 36 deletions
diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index eff013f295be..fb1e4c33e27d 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -405,7 +405,7 @@ static const struct regmap_config cs42l42_regmap = {  	.use_single_write = true,  }; -static DECLARE_TLV_DB_SCALE(adc_tlv, -9600, 100, false); +static DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 100, true);  static DECLARE_TLV_DB_SCALE(mixer_tlv, -6300, 100, true);  static const char * const cs42l42_hpf_freq_text[] = { @@ -425,34 +425,23 @@ static SOC_ENUM_SINGLE_DECL(cs42l42_wnf3_freq_enum, CS42L42_ADC_WNF_HPF_CTL,  			    CS42L42_ADC_WNF_CF_SHIFT,  			    cs42l42_wnf3_freq_text); -static const char * const cs42l42_wnf05_freq_text[] = { -	"280Hz", "315Hz", "350Hz", "385Hz", -	"420Hz", "455Hz", "490Hz", "525Hz" -}; - -static SOC_ENUM_SINGLE_DECL(cs42l42_wnf05_freq_enum, CS42L42_ADC_WNF_HPF_CTL, -			    CS42L42_ADC_WNF_CF_SHIFT, -			    cs42l42_wnf05_freq_text); -  static const struct snd_kcontrol_new cs42l42_snd_controls[] = {  	/* ADC Volume and Filter Controls */  	SOC_SINGLE("ADC Notch Switch", CS42L42_ADC_CTL, -				CS42L42_ADC_NOTCH_DIS_SHIFT, true, false), +				CS42L42_ADC_NOTCH_DIS_SHIFT, true, true),  	SOC_SINGLE("ADC Weak Force Switch", CS42L42_ADC_CTL,  				CS42L42_ADC_FORCE_WEAK_VCM_SHIFT, true, false),  	SOC_SINGLE("ADC Invert Switch", CS42L42_ADC_CTL,  				CS42L42_ADC_INV_SHIFT, true, false),  	SOC_SINGLE("ADC Boost Switch", CS42L42_ADC_CTL,  				CS42L42_ADC_DIG_BOOST_SHIFT, true, false), -	SOC_SINGLE_SX_TLV("ADC Volume", CS42L42_ADC_VOLUME, -				CS42L42_ADC_VOL_SHIFT, 0xA0, 0x6C, adc_tlv), +	SOC_SINGLE_S8_TLV("ADC Volume", CS42L42_ADC_VOLUME, -97, 12, adc_tlv),  	SOC_SINGLE("ADC WNF Switch", CS42L42_ADC_WNF_HPF_CTL,  				CS42L42_ADC_WNF_EN_SHIFT, true, false),  	SOC_SINGLE("ADC HPF Switch", CS42L42_ADC_WNF_HPF_CTL,  				CS42L42_ADC_HPF_EN_SHIFT, true, false),  	SOC_ENUM("HPF Corner Freq", cs42l42_hpf_freq_enum),  	SOC_ENUM("WNF 3dB Freq", cs42l42_wnf3_freq_enum), -	SOC_ENUM("WNF 05dB Freq", cs42l42_wnf05_freq_enum),  	/* DAC Volume and Filter Controls */  	SOC_SINGLE("DACA Invert Switch", CS42L42_DAC_CTL1, @@ -471,8 +460,8 @@ static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = {  	SND_SOC_DAPM_OUTPUT("HP"),  	SND_SOC_DAPM_DAC("DAC", NULL, CS42L42_PWR_CTL1, CS42L42_HP_PDN_SHIFT, 1),  	SND_SOC_DAPM_MIXER("MIXER", CS42L42_PWR_CTL1, CS42L42_MIXER_PDN_SHIFT, 1, NULL, 0), -	SND_SOC_DAPM_AIF_IN("SDIN1", NULL, 0, CS42L42_ASP_RX_DAI0_EN, CS42L42_ASP_RX0_CH1_SHIFT, 0), -	SND_SOC_DAPM_AIF_IN("SDIN2", NULL, 1, CS42L42_ASP_RX_DAI0_EN, CS42L42_ASP_RX0_CH2_SHIFT, 0), +	SND_SOC_DAPM_AIF_IN("SDIN1", NULL, 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_IN("SDIN2", NULL, 1, SND_SOC_NOPM, 0, 0),  	/* Playback Requirements */  	SND_SOC_DAPM_SUPPLY("ASP DAI0", CS42L42_PWR_CTL1, CS42L42_ASP_DAI_PDN_SHIFT, 1, NULL, 0), @@ -597,6 +586,7 @@ struct cs42l42_pll_params {   * Table 4-5 from the Datasheet   */  static const struct cs42l42_pll_params pll_ratio_table[] = { +	{ 1411200, 0, 1, 0x00, 0x80, 0x000000, 0x03, 0x10, 11289600, 128, 2},  	{ 1536000, 0, 1, 0x00, 0x7D, 0x000000, 0x03, 0x10, 12000000, 125, 2},  	{ 2304000, 0, 1, 0x00, 0x55, 0xC00000, 0x02, 0x10, 12288000,  85, 2},  	{ 2400000, 0, 1, 0x00, 0x50, 0x000000, 0x03, 0x10, 12000000,  80, 2}, @@ -630,6 +620,8 @@ static int cs42l42_pll_config(struct snd_soc_component *component)  	for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {  		if (pll_ratio_table[i].sclk == clk) { +			cs42l42->pll_config = i; +  			/* Configure the internal sample rate */  			snd_soc_component_update_bits(component, CS42L42_MCLK_CTL,  					CS42L42_INTERNAL_FS_MASK, @@ -638,14 +630,9 @@ static int cs42l42_pll_config(struct snd_soc_component *component)  					(pll_ratio_table[i].mclk_int !=  					24000000)) <<  					CS42L42_INTERNAL_FS_SHIFT); -			/* Set the MCLK src (PLL or SCLK) and the divide -			 * ratio -			 */ +  			snd_soc_component_update_bits(component, CS42L42_MCLK_SRC_SEL, -					CS42L42_MCLK_SRC_SEL_MASK |  					CS42L42_MCLKDIV_MASK, -					(pll_ratio_table[i].mclk_src_sel -					<< CS42L42_MCLK_SRC_SEL_SHIFT) |  					(pll_ratio_table[i].mclk_div <<  					CS42L42_MCLKDIV_SHIFT));  			/* Set up the LRCLK */ @@ -681,15 +668,6 @@ static int cs42l42_pll_config(struct snd_soc_component *component)  					CS42L42_FSYNC_PULSE_WIDTH_MASK,  					CS42L42_FRAC1_VAL(fsync - 1) <<  					CS42L42_FSYNC_PULSE_WIDTH_SHIFT); -			snd_soc_component_update_bits(component, -					CS42L42_ASP_FRM_CFG, -					CS42L42_ASP_5050_MASK, -					CS42L42_ASP_5050_MASK); -			/* Set the frame delay to 1.0 SCLK clocks */ -			snd_soc_component_update_bits(component, CS42L42_ASP_FRM_CFG, -					CS42L42_ASP_FSD_MASK, -					CS42L42_ASP_FSD_1_0 << -					CS42L42_ASP_FSD_SHIFT);  			/* Set the sample rates (96k or lower) */  			snd_soc_component_update_bits(component, CS42L42_FS_RATE_EN,  					CS42L42_FS_EN_MASK, @@ -789,7 +767,18 @@ static int cs42l42_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)  	/* interface format */  	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {  	case SND_SOC_DAIFMT_I2S: -	case SND_SOC_DAIFMT_LEFT_J: +		/* +		 * 5050 mode, frame starts on falling edge of LRCLK, +		 * frame delayed by 1.0 SCLKs +		 */ +		snd_soc_component_update_bits(component, +					      CS42L42_ASP_FRM_CFG, +					      CS42L42_ASP_STP_MASK | +					      CS42L42_ASP_5050_MASK | +					      CS42L42_ASP_FSD_MASK, +					      CS42L42_ASP_5050_MASK | +					      (CS42L42_ASP_FSD_1_0 << +						CS42L42_ASP_FSD_SHIFT));  		break;  	default:  		return -EINVAL; @@ -819,6 +808,25 @@ static int cs42l42_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)  	return 0;  } +static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ +	struct snd_soc_component *component = dai->component; +	struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + +	/* +	 * Sample rates < 44.1 kHz would produce an out-of-range SCLK with +	 * a standard I2S frame. If the machine driver sets SCLK it must be +	 * legal. +	 */ +	if (cs42l42->sclk) +		return 0; + +	/* Machine driver has not set a SCLK, limit bottom end to 44.1 kHz */ +	return snd_pcm_hw_constraint_minmax(substream->runtime, +					    SNDRV_PCM_HW_PARAM_RATE, +					    44100, 192000); +} +  static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream,  				struct snd_pcm_hw_params *params,  				struct snd_soc_dai *dai) @@ -832,6 +840,17 @@ static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream,  	cs42l42->srate = params_rate(params);  	cs42l42->bclk = snd_soc_params_to_bclk(params); +	/* I2S frame always has 2 channels even for mono audio */ +	if (channels == 1) +		cs42l42->bclk *= 2; + +	/* +	 * Assume 24-bit samples are in 32-bit slots, to prevent SCLK being +	 * more than assumed (which would result in overclocking). +	 */ +	if (params_width(params) == 24) +		cs42l42->bclk = (cs42l42->bclk / 3) * 4; +  	switch(substream->stream) {  	case SNDRV_PCM_STREAM_CAPTURE:  		if (channels == 2) { @@ -855,6 +874,17 @@ static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream,  		snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_CH2_AP_RES,  							 CS42L42_ASP_RX_CH_AP_MASK |  							 CS42L42_ASP_RX_CH_RES_MASK, val); + +		/* Channel B comes from the last active channel */ +		snd_soc_component_update_bits(component, CS42L42_SP_RX_CH_SEL, +					      CS42L42_SP_RX_CHB_SEL_MASK, +					      (channels - 1) << CS42L42_SP_RX_CHB_SEL_SHIFT); + +		/* Both LRCLK slots must be enabled */ +		snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_EN, +					      CS42L42_ASP_RX0_CH_EN_MASK, +					      BIT(CS42L42_ASP_RX0_CH1_SHIFT) | +					      BIT(CS42L42_ASP_RX0_CH2_SHIFT));  		break;  	default:  		break; @@ -868,10 +898,23 @@ static int cs42l42_set_sysclk(struct snd_soc_dai *dai,  {  	struct snd_soc_component *component = dai->component;  	struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); +	int i; -	cs42l42->sclk = freq; +	if (freq == 0) { +		cs42l42->sclk = 0; +		return 0; +	} -	return 0; +	for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) { +		if (pll_ratio_table[i].sclk == freq) { +			cs42l42->sclk = freq; +			return 0; +		} +	} + +	dev_err(component->dev, "SCLK %u not supported\n", freq); + +	return -EINVAL;  }  static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream) @@ -900,13 +943,21 @@ static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream)  			 */  			regmap_multi_reg_write(cs42l42->regmap, cs42l42_to_osc_seq,  					       ARRAY_SIZE(cs42l42_to_osc_seq)); + +			/* Must disconnect PLL before stopping it */ +			snd_soc_component_update_bits(component, +						      CS42L42_MCLK_SRC_SEL, +						      CS42L42_MCLK_SRC_SEL_MASK, +						      0); +			usleep_range(100, 200); +  			snd_soc_component_update_bits(component, CS42L42_PLL_CTL1,  						      CS42L42_PLL_START_MASK, 0);  		}  	} else {  		if (!cs42l42->stream_use) {  			/* SCLK must be running before codec unmute */ -			if ((cs42l42->bclk < 11289600) && (cs42l42->sclk < 11289600)) { +			if (pll_ratio_table[cs42l42->pll_config].mclk_src_sel) {  				snd_soc_component_update_bits(component, CS42L42_PLL_CTL1,  							      CS42L42_PLL_START_MASK, 1); @@ -927,6 +978,12 @@ static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream)  							       CS42L42_PLL_LOCK_TIMEOUT_US);  				if (ret < 0)  					dev_warn(component->dev, "PLL failed to lock: %d\n", ret); + +				/* PLL must be running to drive glitchless switch logic */ +				snd_soc_component_update_bits(component, +							      CS42L42_MCLK_SRC_SEL, +							      CS42L42_MCLK_SRC_SEL_MASK, +							      CS42L42_MCLK_SRC_SEL_MASK);  			}  			/* Mark SCLK as present, turn off internal oscillator */ @@ -960,8 +1017,8 @@ static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream)  			 SNDRV_PCM_FMTBIT_S24_LE |\  			 SNDRV_PCM_FMTBIT_S32_LE ) -  static const struct snd_soc_dai_ops cs42l42_ops = { +	.startup	= cs42l42_dai_startup,  	.hw_params	= cs42l42_pcm_hw_params,  	.set_fmt	= cs42l42_set_dai_fmt,  	.set_sysclk	= cs42l42_set_sysclk, @@ -2070,4 +2127,7 @@ MODULE_DESCRIPTION("ASoC CS42L42 driver");  MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, <[email protected]>");  MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <[email protected]>");  MODULE_AUTHOR("Michael White, Cirrus Logic Inc, <[email protected]>"); +MODULE_AUTHOR("Lucas Tanure <[email protected]>"); +MODULE_AUTHOR("Richard Fitzgerald <[email protected]>"); +MODULE_AUTHOR("Vitaly Rodionov <[email protected]>");  MODULE_LICENSE("GPL");  |