[alsa-devel] [PATCH v3] ASoC: Add Freescale SGTL5000 codec support

Arnaud Patard (Rtp) arnaud.patard at rtp-net.org
Wed Feb 16 19:01:47 CET 2011


<zhaoming.zeng at freescale.com> writes:

Hi,

> From: Zeng Zhaoming <zhaoming.zeng at freescale.com>
>
> Add Freescale SGTL5000 codec support
>
> Signed-off-by: Zeng Zhaoming <zhaoming.zeng at freescale.com>
> ---
> changes since v2:
> 1. clean up register default values
> 2. rewrite codec power up code, add sgtl5000_set_power_regs()
> 3. rewrite codec clock configure code sgtl5000_set_clock()
> 4. reimplement PM hooks, restore register by particular order.
> 5. clean up dapm code, remove dac and adc event hooks.
> 6. clean up codec private structure, remove unnecessary fields.
> 7. add comments for uncommon code.
>
> Thanks for Mark's review.
> ---
>  sound/soc/codecs/Kconfig    |    4 +
>  sound/soc/codecs/Makefile   |    1 +
>  sound/soc/codecs/sgtl5000.c | 1229 +++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/sgtl5000.h |  403 ++++++++++++++
>  4 files changed, 1637 insertions(+), 0 deletions(-)
>
> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
> index c48b23c..57b909a 100644
> --- a/sound/soc/codecs/Kconfig
> +++ b/sound/soc/codecs/Kconfig
> @@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS
>  	select SND_SOC_MAX98088 if I2C
>  	select SND_SOC_MAX9877 if I2C
>  	select SND_SOC_PCM3008
> +	select SND_SOC_SGTL5000 if I2C
>  	select SND_SOC_SPDIF
>  	select SND_SOC_SSM2602 if I2C
>  	select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
> @@ -176,6 +177,9 @@ config SND_SOC_MAX98088
>  config SND_SOC_PCM3008
>         tristate
>  
> +config SND_SOC_SGTL5000
> +	tristate
> +
>  config SND_SOC_SPDIF
>  	tristate
>  
> diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
> index 579af9c..78e36cc 100644
> --- a/sound/soc/codecs/Makefile
> +++ b/sound/soc/codecs/Makefile
> @@ -18,6 +18,7 @@ snd-soc-dmic-objs := dmic.o
>  snd-soc-l3-objs := l3.o
>  snd-soc-max98088-objs := max98088.o
>  snd-soc-pcm3008-objs := pcm3008.o
> +snd-soc-sgtl5000-objs := sgtl5000.o
>  snd-soc-alc5623-objs := alc5623.o
>  snd-soc-spdif-objs := spdif_transciever.o
>  snd-soc-ssm2602-objs := ssm2602.o

you're actually missing something like :
obj-$(CONFIG_SND_SOC_SGTL5000)  += snd-soc-sgtl5000.o


> diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
> new file mode 100644
> index 0000000..2646ecb
> --- /dev/null
> +++ b/sound/soc/codecs/sgtl5000.c

[...]

> +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
> +				   enum snd_soc_bias_level level)
> +{
> +	int i;
> +	struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
> +
> +	switch (level) {
> +	case SND_SOC_BIAS_ON:
> +	case SND_SOC_BIAS_PREPARE:
> +		break;
> +	case SND_SOC_BIAS_STANDBY:
> +		if (codec->bias_level == SND_SOC_BIAS_OFF) {

I don't see a member 'bias_level' in snd_soc_codec in the trees I have
on my drive. Which tree did you use ?

> +			for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
> +				if (!sgtl5000->supplies[i])
> +					continue;
> +				regulator_enable(sgtl5000->supplies[i]);
> +			}
> +		}
> +		break;
> +	case SND_SOC_BIAS_OFF:
> +		for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
> +			if (!sgtl5000->supplies[i])
> +				continue;
> +			regulator_disable(sgtl5000->supplies[i]);
> +		}
> +		break;
> +	}
> +
> +	codec->bias_level = level;
> +	return 0;
> +}
> +
> +#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
> +			SNDRV_PCM_FMTBIT_S20_3LE |\
> +			SNDRV_PCM_FMTBIT_S24_LE |\
> +			SNDRV_PCM_FMTBIT_S32_LE)
> +
> +struct snd_soc_dai_ops sgtl5000_ops = {
> +	.hw_params = sgtl5000_pcm_hw_params,
> +	.digital_mute = sgtl5000_digital_mute,
> +	.set_fmt = sgtl5000_set_dai_fmt,
> +	.set_sysclk = sgtl5000_set_dai_sysclk,
> +};
> +
> +static struct snd_soc_dai_driver sgtl5000_dai = {
> +	.name = "sgtl5000",
> +	.playback = {
> +		.stream_name = "Playback",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		/*
> +		 * only support 8~48K + 96K,
> +		 * TODO modify hw_param to support more
> +		 */
> +		.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
> +		.formats = SGTL5000_FORMATS,
> +	},
> +	.capture = {
> +		.stream_name = "Capture",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
> +		.formats = SGTL5000_FORMATS,
> +	},
> +	.ops = &sgtl5000_ops,
> +	.symmetric_rates = 1,
> +};
> +
> +static int sgtl5000_volatile_register(unsigned int reg)
> +{
> +	switch (reg) {
> +	case SGTL5000_CHIP_ID:
> +	case SGTL5000_CHIP_ADCDAC_CTRL:
> +	case SGTL5000_CHIP_ANA_STATUS:
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state)
> +{
> +	sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +
> +	return 0;
> +}
> +
> +/*
> + * restore all sgtl5000 registers,
> + * since a big hole between dap and regular registers,
> + * we will restore them respectively.
> + */
> +static int sgtl5000_restore_regs(struct snd_soc_codec *codec)
> +{
> +	u16 *cache = codec->reg_cache;
> +	int i;
> +	int regular_regs = SGTL5000_CHIP_SHORT_CTRL >> 1;
> +
> +	/* restore regular registers */
> +	for (i = 0; i < regular_regs; i++) {
> +		int reg = i << 1;
> +
> +		/* this regs depends on the others */
> +		if (reg == SGTL5000_CHIP_ANA_POWER ||
> +			reg == SGTL5000_CHIP_CLK_CTRL ||
> +			reg == SGTL5000_CHIP_LINREG_CTRL ||
> +			reg == SGTL5000_CHIP_LINE_OUT_CTRL ||
> +			reg == SGTL5000_CHIP_CLK_CTRL)
> +			continue;
> +
> +		snd_soc_write(codec, reg, cache[i]);
> +	}
> +
> +	/* restore dap registers */
> +	for (i = SGTL5000_DAP_REG_OFFSET >> 1;
> +			i < SGTL5000_MAX_REG_OFFSET >> 1; i++) {
> +		int reg = i << 1;
> +
> +		snd_soc_write(codec, reg, cache[i]);
> +	}
> +
> +	/*
> +	 * restore power and other regs accroding
> +	 * to set_power() and set_clock()
> +	 */
> +	snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL,
> +			cache[SGTL5000_CHIP_LINREG_CTRL >> 1]);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER,
> +			cache[SGTL5000_CHIP_ANA_POWER >> 1]);
> +	mdelay(1);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL,
> +			cache[SGTL5000_CHIP_CLK_CTRL >> 1]);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL,
> +			cache[SGTL5000_CHIP_REF_CTRL >> 1]);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
> +			cache[SGTL5000_CHIP_LINE_OUT_CTRL >> 1]);
> +	return 0;
> +}
> +
> +static int sgtl5000_resume(struct snd_soc_codec *codec)
> +{
> +	/* Bring the codec back up to standby to enable regulators */
> +	sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
> +
> +	/* Restore registers by cached in memory */
> +	sgtl5000_restore_regs(codec);
> +	return 0;
> +}
> +
> +/*
> + * sgtl5000 has 3 internal power supplies:
> + * 1. VAG, normally set to vdda/2
> + * 2. chargepump, set to different value
> + *	according to voltage of vdda and vddio
> + * 3. line out VAG, normally set to vddio/2
> + *
> + * and should be set accroding to:
> + * 1. vddd provided by external or not
> + * 2. vdda and vddio voltage value. > 3.1v or not
> + * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd.
> + */
> +static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
> +{
> +	int vddd, vdda, vddio;
> +	u16 ana_pwr, lreg_ctrl;
> +	int vag;
> +	struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
> +
> +	vdda  = regulator_get_voltage(sgtl5000->supplies[VDDA]) / 1000;
> +	vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO]) / 1000;
> +
> +	if (sgtl5000->supplies[VDDD])
> +		vddd = regulator_get_voltage(sgtl5000->supplies[VDDD]) / 1000;
> +	else
> +		vddd = 0;
> +
> +	/* reset value */
> +	ana_pwr = SGTL5000_DAC_STEREO |
> +			SGTL5000_ADC_STEREO |
> +			SGTL5000_REFTOP_POWERUP;
> +	lreg_ctrl = 0;
> +
> +	/* if no external vddd, use internal vddd */
> +	if (!vddd) {
> +		/* set VDDD to 1.2v */
> +		lreg_ctrl |= 0x8 << SGTL5000_LINREG_VDDD_SHIFT;
> +		/* power up internal linear regulator */
> +		ana_pwr |= SGTL5000_LINEREG_D_POWERUP |
> +				SGTL5000_LINREG_SIMPLE_POWERUP |
> +				SGTL5000_STARTUP_POWERUP;
> +	}
> +
> +	if (vddio < 3100 && vdda < 3100) {
> +		/* enable internal oscillator used for charge pump */
> +		snd_soc_update_bits(codec, SGTL5000_CHIP_CLK_TOP_CTRL,
> +					SGTL5000_INT_OSC_EN,
> +					SGTL5000_INT_OSC_EN);
> +		/* Enable VDDC charge pump */
> +		ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
> +	} else if (vddio >= 3100 && vdda >= 3100) {
> +		/*
> +		 * if vddio and vddd > 3.1v,
> +		 * charge pump should be clean before set ana_pwr
> +		 */
> +		snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
> +				SGTL5000_VDDC_CHRGPMP_POWERUP, 0);
> +
> +		/* VDDC use VDDIO rail */
> +		lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
> +		lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO <<
> +			    SGTL5000_VDDC_MAN_ASSN_SHIFT;
> +	}
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
> +	mdelay(1);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
> +
> +	/*
> +	 * if vddd linear reg has been enabled,
> +	 * simple digital supply should be clear to get
> +	 * proper VDDD voltage.
> +	 */
> +	if (ana_pwr & SGTL5000_LINEREG_D_POWERUP) {
> +		ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
> +		snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
> +				SGTL5000_LINREG_SIMPLE_POWERUP |
> +				SGTL5000_STARTUP_POWERUP,
> +				0);
> +		mdelay(1);
> +	}
> +
> +	/*
> +	 * set ADC/DAC VAG to vdda / 2,
> +	 * should stay in range (0.8v, 1.575v)
> +	 */
> +	vag = vdda / 2;
> +	if (vag <= SGTL5000_ANA_GND_BASE)
> +		vag = 0;
> +	else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP *
> +		 (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT))
> +		vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT;
> +	else
> +		vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP;
> +
> +	snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
> +			vag << SGTL5000_ANA_GND_SHIFT,
> +			vag << SGTL5000_ANA_GND_SHIFT);
> +
> +	/* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */
> +	vag = vddio / 2;
> +	if (vag <= SGTL5000_LINE_OUT_GND_BASE)
> +		vag = 0;
> +	else if (vag >= SGTL5000_LINE_OUT_GND_BASE +
> +		SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX)
> +		vag = SGTL5000_LINE_OUT_GND_MAX;
> +	else
> +		vag = (vag - SGTL5000_LINE_OUT_GND_BASE) /
> +		    SGTL5000_LINE_OUT_GND_STP;
> +
> +	snd_soc_update_bits(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
> +			vag << SGTL5000_LINE_OUT_GND_SHIFT |
> +			SGTL5000_LINE_OUT_CURRENT_360u <<
> +				SGTL5000_LINE_OUT_CURRENT_SHIFT,
> +			vag << SGTL5000_LINE_OUT_GND_SHIFT |
> +			SGTL5000_LINE_OUT_CURRENT_360u <<
> +				SGTL5000_LINE_OUT_CURRENT_SHIFT);
> +
> +	return 0;
> +}
> +
> +static int sgtl5000_probe(struct snd_soc_codec *codec)
> +{
> +	struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
> +	u16 reg;
> +	int ret;
> +	int rev;
> +	int i;
> +
> +	/* setup i2c data ops */
> +	ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
> +	if (ret < 0) {
> +		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* read chip information */
> +	reg = snd_soc_read(codec, SGTL5000_CHIP_ID);
> +	if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
> +	    SGTL5000_PARTID_PART_ID) {
> +		dev_err(codec->dev,
> +			"Device with ID register %x is not a sgtl5000\n", reg);
> +		ret = -ENODEV;
> +		goto err_out;
> +	}
> +
> +	rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
> +	dev_info(codec->dev, "sgtl5000 revision %d\n", rev);
> +
> +	/* get and enable all regulators */
> +	for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
> +		struct regulator *reg;
> +
> +		/* workaround for revision 0x11, using internal LDO */
> +		if (i == VDDD && rev == 0x11)
> +			continue;
> +
> +		reg = regulator_get(codec->dev, supply_names[i]);
> +
> +		if (IS_ERR(reg))
> +			continue;
> +
> +		regulator_enable(reg);
> +		sgtl5000->supplies[i] = reg;
> +	}
> +
> +	/*
> +	 * vdda and vddio regulator must configured,
> +	 * vddd is an optional regulator, if vddd not supply externally,
> +	 * internal vddd will be enabled in sgtl5000_set_power_regs()
> +	 */
> +	if (!sgtl5000->supplies[VDDA] || !sgtl5000->supplies[VDDIO]) {
> +		dev_err(codec->dev,
> +			"Not set vdda or vddio regulator correctly\n");
> +
> +		/* platform regulator not configured correctly */
> +		ret = -ENODEV;
> +		goto err_out;
> +	}
> +
> +	/* power up sgtl5000 */
> +	ret = sgtl5000_set_power_regs(codec);
> +	if (ret)
> +		goto err_out;
> +
> +	/* enable small pop, introduce 400ms delay in turning off */
> +	snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
> +				SGTL5000_SMALL_POP,
> +				SGTL5000_SMALL_POP);
> +
> +	/* enable short cut detect */
> +	snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0);
> +
> +	/*
> +	 * set i2s as default input of sound switch
> +	 * TODO: add sound switch to control and dapm widge.
> +	 */
> +	snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL,
> +			SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT);
> +	snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER,
> +			SGTL5000_ADC_EN | SGTL5000_DAC_EN);
> +
> +	/* enable dac vol ramp by default */
> +	snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL,
> +			SGTL5000_DAC_VOL_RAMP_EN |
> +			SGTL5000_DAC_MUTE_RIGHT |
> +			SGTL5000_DAC_MUTE_LEFT);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
> +
> +	/* set default volume of adc */
> +	reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_ADC_CTRL);
> +	reg &= ~SGTL5000_ADC_VOL_M6DB;
> +	reg &= ~(SGTL5000_ADC_VOL_LEFT_MASK | SGTL5000_ADC_VOL_RIGHT_MASK);
> +	reg |= (0xf << SGTL5000_ADC_VOL_LEFT_SHIFT)
> +	    | (0xf << SGTL5000_ADC_VOL_RIGHT_SHIFT);
> +	snd_soc_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL,
> +			SGTL5000_HP_ZCD_EN |
> +			SGTL5000_ADC_ZCD_EN);
> +
> +	snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
> +
> +	/*
> +	 * disable DAP
> +	 * TODO:
> +	 * Enable DAP in control and dapm.
> +	 */
> +	snd_soc_write(codec, SGTL5000_DAP_CTRL, 0);
> +
> +	/* leading to standby state */
> +	ret = sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
> +	if (ret)
> +		goto err_out;
> +
> +	snd_soc_add_controls(codec, sgtl5000_snd_controls,
> +			     ARRAY_SIZE(sgtl5000_snd_controls));
> +
> +	snd_soc_dapm_new_controls(codec, sgtl5000_dapm_widgets,
> +				  ARRAY_SIZE(sgtl5000_dapm_widgets));

same kind of probleme here. on my drive, it's :
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num);


> +
> +	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
> +
> +	snd_soc_dapm_new_widgets(codec);

same here.


> +
> +	return 0;
> +
> +err_out:
> +	for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
> +		if (!sgtl5000->supplies[i])
> +			continue;
> +
> +		regulator_disable(sgtl5000->supplies[i]);
> +		regulator_put(sgtl5000->supplies[i]);
> +	}
> +
> +	return ret;
> +}
> +
> +static int sgtl5000_remove(struct snd_soc_codec *codec)
> +{
> +	struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
> +	int i;
> +
> +	sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +
> +	snd_soc_dapm_free(codec);

same here.

[...]

Arnaud


More information about the Alsa-devel mailing list