[alsa-devel] [PATCH v2] tlv320aic3x: disable ADC/DAC while changing clock

Daniel Glöckner dg at emlix.com
Wed Apr 1 12:56:55 CEST 2009


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.

Changes compared to v1 of this patch:

Enabling/disabling of the PLL has been reinstated in another bias level
and is done only if the PLL is required. Waiting for ADC/DAC shutdown
has been added at those points.

Redundant code disabling dapm widgets in bias level off has been removed.

Changing of hw parameters is allowed only while the stream in the other
direction is not running. snd_pcm_hw_params() takes care of our stream
not running.

Comments have been added to describe the helper functions.

Signed-off-by: Daniel Glöckner <dg at emlix.com>
---
 sound/soc/codecs/tlv320aic3x.c |  160 +++++++++++++++++++++++++++++----------
 sound/soc/codecs/tlv320aic3x.h |    8 ++
 2 files changed, 127 insertions(+), 41 deletions(-)

diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
index ab099f4..3422700 100644
--- a/sound/soc/codecs/tlv320aic3x.c
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -55,6 +55,9 @@
 struct aic3x_priv {
 	unsigned int sysclk;
 	int master;
+	unsigned int rate;
+	unsigned int format;
+	char running[SNDRV_PCM_STREAM_LAST + 1];
 };
 
 /*
@@ -756,6 +759,67 @@ static int aic3x_add_widgets(struct snd_soc_codec *codec)
 	return 0;
 }
 
+/*
+ * Enables or disables the left/right ADC/DAC according to new.
+ * Bits in new:
+ *	0 left ADC
+ *	1 right ADC
+ *	2 left DAC
+ *	4 right DAC
+ * A set bit enables the component.
+ * Returns the previous state of those components encoded in the same way.
+ */
+static u8 aic3x_power_adc_dac(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;
+}
+
+/*
+ * Polls the power status registers until ADC and DAC are both off or timeout
+ */
+static void aic3x_wait_adc_dac_off(struct snd_soc_codec *codec)
+{
+	int timeout = 1000;
+	u8 data;
+	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");
+}
+
 static int aic3x_hw_params(struct snd_pcm_substream *substream,
 			   struct snd_pcm_hw_params *params,
 			   struct snd_soc_dai *dai)
@@ -765,9 +829,23 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream,
 	struct snd_soc_codec *codec = socdev->card->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, old_state;
 	u16 pll_d = 1;
 
+	if (aic3x->format == params_format(params) &&
+	    aic3x->rate == params_rate(params))
+		return 0;
+
+	if (aic3x->running[substream->stream ^ 1])
+		return -EINVAL;
+
+	aic3x->format = params_format(params);
+	aic3x->rate = params_rate(params);
+
+	/* switch off the ADC/DAC while changing parameters */
+	old_state = aic3x_power_adc_dac(codec, 0);
+	aic3x_wait_adc_dac_off(codec);
+
 	/* select data word length */
 	data =
 	    aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4));
@@ -821,8 +899,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_adc_dac(codec, old_state);
 		return 0;
+	}
 
 	/* Use PLL
 	 * find an apropriate setup for j, d, r and p by iterating over
@@ -863,17 +943,23 @@ 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->rate = 0;
 		return -EINVAL;
 	}
 
 	data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
-	aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT));
+	if (codec->bias_level == SND_SOC_BIAS_ON ||
+	    codec->bias_level == SND_SOC_BIAS_PREPARE)
+		data |= PLL_ENABLE;
+
 	aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT);
 	aic3x_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT);
 	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);
+	aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT));
 
+	aic3x_power_adc_dac(codec, old_state);
 	return 0;
 }
 
@@ -959,70 +1045,60 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
 	return 0;
 }
 
+static int aic3x_trigger(struct snd_pcm_substream *substream, int cmd,
+			 struct snd_soc_dai *codec_dai)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic3x_priv *aic3x = codec->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		aic3x->running[substream->stream] = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		aic3x->running[substream->stream] = 0;
+	}
+	return 0;
+}
+
 static int aic3x_set_bias_level(struct snd_soc_codec *codec,
 				enum snd_soc_bias_level level)
 {
-	struct aic3x_priv *aic3x = codec->private_data;
 	u8 reg;
 
 	switch (level) {
 	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
 		/* all power is driven by DAPM system */
-		if (aic3x->master) {
+		if ((aic3x_read_reg_cache(codec, AIC3X_GPIOB_REG) & 1)
+				== CODEC_CLKIN_PLLDIV) {
 			/* enable pll */
 			reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+			aic3x_wait_adc_dac_off(codec);
 			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) {
+		if ((aic3x_read_reg_cache(codec, AIC3X_GPIOB_REG) & 1)
+				 == CODEC_CLKIN_PLLDIV) {
 			/* disable pll */
 			reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+			aic3x_wait_adc_dac_off(codec);
 			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));
-
-		reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL);
-		aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON);
-		reg = aic3x_read_reg_cache(codec, HPROUT_CTRL);
-		aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON);
-
-		reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL);
-		aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON);
-		reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL);
-		aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON);
-
-		reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL);
-		aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON);
-
-		reg = aic3x_read_reg_cache(codec, LLOPM_CTRL);
-		aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON);
-		reg = aic3x_read_reg_cache(codec, RLOPM_CTRL);
-		aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON);
-
-		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;
 	}
 	codec->bias_level = level;
@@ -1093,6 +1169,7 @@ static struct snd_soc_dai_ops aic3x_dai_ops = {
 	.digital_mute	= aic3x_mute,
 	.set_sysclk	= aic3x_set_dai_sysclk,
 	.set_fmt	= aic3x_set_dai_fmt,
+	.trigger	= aic3x_trigger,
 };
 
 struct snd_soc_dai aic3x_dai = {
@@ -1383,6 +1460,7 @@ static int aic3x_probe(struct platform_device *pdev)
 		kfree(codec);
 		return -ENOMEM;
 	}
+	aic3x->format = SNDRV_PCM_FORMAT_LAST + 1;
 
 	codec->private_data = aic3x;
 	socdev->card->codec = codec;
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)
 
-- 
1.6.1.3



More information about the Alsa-devel mailing list