While using a tlv320aic3101 as a clock master, it often happened that the clock lines got stuck while changing the sampling rate. In a discussion with TI technical support it was suggested to shut down the ADC and DAC while there are changes being made to the clock.
Following that rule resolves the problem, but merely writing the registers to switch the ADC/DAC off is not enough. One needs to poll the power status registers to wait until they have completely shut down.
The aforementioned rule implies that we can not disable the PLL in stand-by bias level.
Signed-off-by: Daniel Glöckner dg@emlix.com --- sound/soc/codecs/tlv320aic3x.c | 89 ++++++++++++++++++++++++++++------------ sound/soc/codecs/tlv320aic3x.h | 8 ++++ 2 files changed, 71 insertions(+), 26 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index aea0cb7..4c44022 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -740,6 +740,57 @@ static int aic3x_add_widgets(struct snd_soc_codec *codec) return 0; }
+static u8 aic3x_power_codec(struct snd_soc_codec *codec, u8 new) +{ + u8 val, old; + + val = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); + old = (val & LADC_PWR_ON) ? 1 : 0; + if ((old ^ new) & 1) { + val ^= LADC_PWR_ON; + aic3x_write(codec, LINE1L_2_LADC_CTRL, val); + } + + val = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); + old += (val & RADC_PWR_ON) ? 2 : 0; + if ((old ^ new) & 2) { + val ^= RADC_PWR_ON; + aic3x_write(codec, LINE1R_2_RADC_CTRL, val); + } + + val = aic3x_read_reg_cache(codec, DAC_PWR); + old += (val & LDAC_PWR_ON) ? 4 : 0; + old += (val & RDAC_PWR_ON) ? 8 : 0; + if ((old ^ new) & 4) + val ^= LDAC_PWR_ON; + if ((old ^ new) & 8) + val ^= RDAC_PWR_ON; + if ((old ^ new) & 12) + aic3x_write(codec, DAC_PWR, val); + + return old; +} + +static u8 aic3x_power_off_wait(struct snd_soc_codec *codec) +{ + int timeout = 1000; + u8 old, data; + old = aic3x_power_codec(codec, 0); + + do { + aic3x_read(codec, ADC_FLAGS, &data); + } while ((data & (LADC_PWR_STATUS | RADC_PWR_STATUS)) && --timeout > 0); + + do { + aic3x_read(codec, MODULE_PWR_STATUS, &data); + } while ((data & (LDAC_PWR_STATUS | RDAC_PWR_STATUS)) && --timeout > 0); + + if (timeout < 0) + printk(KERN_WARNING "aic3x: timeout waiting for ADC/DAC to shut down"); + + return old; +} + static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -749,9 +800,11 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->codec; struct aic3x_priv *aic3x = codec->private_data; int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; - u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1, state_saved; u16 pll_d = 1;
+ state_saved = aic3x_power_off_wait(codec); + /* select data word length */ data = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); @@ -805,8 +858,10 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, data |= (data << 4); aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data);
- if (bypass_pll) + if (bypass_pll) { + aic3x_power_codec(codec, state_saved); return 0; + }
/* Use PLL * find an apropriate setup for j, d, r and p by iterating over @@ -847,6 +902,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream,
if (last_clk == 0) { printk(KERN_ERR "%s(): unable to setup PLL\n", __func__); + aic3x_power_codec(codec, state_saved); return -EINVAL; }
@@ -857,7 +913,11 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, aic3x_write(codec, AIC3X_PLL_PROGC_REG, (pll_d >> 6) << PLLD_MSB_SHIFT); aic3x_write(codec, AIC3X_PLL_PROGD_REG, (pll_d & 0x3F) << PLLD_LSB_SHIFT); + data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT) + | PLL_ENABLE);
+ aic3x_power_codec(codec, state_saved); return 0; }
@@ -951,37 +1011,14 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec,
switch (level) { case SND_SOC_BIAS_ON: - /* all power is driven by DAPM system */ - if (aic3x->master) { - /* enable pll */ - reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); - aic3x_write(codec, AIC3X_PLL_PROGA_REG, - reg | PLL_ENABLE); - } break; case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - /* - * all power is driven by DAPM system, - * so output power is safe if bypass was set - */ - if (aic3x->master) { - /* disable pll */ - reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); - aic3x_write(codec, AIC3X_PLL_PROGA_REG, - reg & ~PLL_ENABLE); - } break; case SND_SOC_BIAS_OFF: /* force all power off */ - reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); - aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); - reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); - aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON); - - reg = aic3x_read_reg_cache(codec, DAC_PWR); - aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON)); + aic3x_power_off_wait(codec);
reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index ac827e5..0b4fedf 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -69,6 +69,8 @@ #define RAGC_CTRL_B 30 #define RAGC_CTRL_C 31
+/* ADC Flag register */ +#define ADC_FLAGS 34 /* DAC Power and Left High Power Output control registers */ #define DAC_PWR 37 #define HPLCOM_CFG 37 @@ -126,6 +128,8 @@ #define DACR1_2_LLOPM_VOL 85 #define LLOPM_CTRL 86 #define RLOPM_CTRL 93 +/* Module Power status register */ +#define MODULE_PWR_STATUS 94 /* GPIO/IRQ registers */ #define AIC3X_STICKY_IRQ_FLAGS_REG 96 #define AIC3X_RT_IRQ_FLAGS_REG 97 @@ -191,6 +195,10 @@ #define MONOLOPM_PWR_ON 0x01 #define LLOPM_PWR_ON 0x01 #define RLOPM_PWR_ON 0x01 +#define LADC_PWR_STATUS 0x40 +#define RADC_PWR_STATUS 0x04 +#define LDAC_PWR_STATUS 0x80 +#define RDAC_PWR_STATUS 0x40
#define INVERT_VOL(val) (0x7f - val)