zhaoming.zeng@freescale.com writes:
Hi,
From: Zeng Zhaoming zhaoming.zeng@freescale.com
Add Freescale SGTL5000 codec support
Signed-off-by: Zeng Zhaoming zhaoming.zeng@freescale.com
changes since v2:
- clean up register default values
- rewrite codec power up code, add sgtl5000_set_power_regs()
- rewrite codec clock configure code sgtl5000_set_clock()
- reimplement PM hooks, restore register by particular order.
- clean up dapm code, remove dac and adc event hooks.
- clean up codec private structure, remove unnecessary fields.
- 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:
- VAG, normally set to vdda/2
- chargepump, set to different value
- according to voltage of vdda and vddio
- line out VAG, normally set to vddio/2
- and should be set accroding to:
- vddd provided by external or not
- vdda and vddio voltage value. > 3.1v or not
- 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