From 830ce0d06445ad1024545431badb8005d7184a3f Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Mon, 24 Aug 2009 12:58:04 -0500 Subject: [PATCH] ENGR00107785 sgtl5000: adc power savings Previously ADC was powered up every time DAC was powered up, which wasted power. Also I2S input and output were powered up and down together. This ment that simultaneous playback and record stomped on each other. Changes in this patch: - We still have to power up the DAC every time the codec powers up to prevent pops (even if we are only recording). We are probably playing without recording a lot more than recording without playing anyway. - Save power by only powering up ADC if we are recording. - Same more power by only powering 1/2 of ADC if recording mono. - Power up I2S input or output section only as needed. - Don't shut I2S both input and output if input or output is still being used. - Constrain hw sample rate to current stream (i.e. new record stream is not allowed to mess up clocking if previous playback stream is still active). - Use mute bits instead of power bits for jack function and speaker function. - Control hp/line out power up sequence to prevent pops. - Don't use DAC mono mode. Doesn't work unless it is enabled when DAC is powered off, which means it breaks for playback that starts when capture has active since DAC is already powered up to prevent pops. - Also fixes issue where we couldn't record at 11025 or 22050Hz stereo. - During resume, if we are powering up to ON state, power up to PREPARE state first. Signed-off-by: Alan Tull --- sound/soc/codecs/sgtl5000.c | 223 ++++++++++++++++++++++++++++++------------- sound/soc/codecs/sgtl5000.h | 4 +- 2 files changed, 159 insertions(+), 68 deletions(-) diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 8b65dc3..27e83b6 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -31,6 +31,11 @@ struct sgtl5000_priv { int fmt; int rev; int lrclk; + int capture_channels; + int playback_active; + int capture_active; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; }; static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, @@ -46,6 +51,7 @@ static unsigned int sgtl5000_read_reg_cache(struct snd_soc_codec *codec, unsigned int offset = reg >> 1; if (offset >= ARRAY_SIZE(sgtl5000_regs)) return -EINVAL; + pr_debug("r r:%02x,v:%04x\n", reg, cache[offset]); return cache[offset]; } @@ -67,7 +73,7 @@ static unsigned int sgtl5000_hw_read(struct snd_soc_codec *codec, buf0[1] = reg & 0xff; i2c_ret = i2c_transfer(client->adapter, msg, 2); if (i2c_ret < 0) { - pr_err("%s: read reg error : reg=%x\n", __func__, reg); + pr_err("%s: read reg error : Reg 0x%02x\n", __func__, reg); return 0; } @@ -116,7 +122,7 @@ static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg, i2c_ret = i2c_transfer(client->adapter, &msg, 1); if (i2c_ret < 0) { - pr_err("%s: write reg error : R%02d = 0x%04x\n", + pr_err("%s: write reg error : Reg 0x%02x = 0x%04x\n", __func__, reg, value); return -EIO; } @@ -254,14 +260,14 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HP_OUT"), SND_SOC_DAPM_OUTPUT("LINE_OUT"), - SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0), - SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_CTRL, 4, 1, NULL, 0), + SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_CTRL, 8, 1, NULL, 0), SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &adc_mux), SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &dac_mux), SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_DIG_POWER, 6, 0), - SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_DIG_POWER, 5, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -391,25 +397,19 @@ static int sgtl5000_add_controls(struct snd_soc_codec *codec) static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; - u16 reg1, reg2; + u16 adcdac_ctrl; - reg1 = sgtl5000_read(codec, SGTL5000_CHIP_ANA_CTRL); - reg2 = sgtl5000_read(codec, SGTL5000_CHIP_ADCDAC_CTRL); + adcdac_ctrl = sgtl5000_read(codec, SGTL5000_CHIP_ADCDAC_CTRL); if (mute) { - reg1 |= SGTL5000_LINE_OUT_MUTE; - reg1 |= SGTL5000_HP_MUTE; - reg2 |= SGTL5000_DAC_MUTE_LEFT; - reg2 |= SGTL5000_DAC_MUTE_RIGHT; + adcdac_ctrl |= SGTL5000_DAC_MUTE_LEFT; + adcdac_ctrl |= SGTL5000_DAC_MUTE_RIGHT; } else { - reg1 &= ~SGTL5000_LINE_OUT_MUTE; - reg1 &= ~SGTL5000_HP_MUTE; - reg2 &= ~SGTL5000_DAC_MUTE_LEFT; - reg2 &= ~SGTL5000_DAC_MUTE_RIGHT; + adcdac_ctrl &= ~SGTL5000_DAC_MUTE_LEFT; + adcdac_ctrl &= ~SGTL5000_DAC_MUTE_RIGHT; } - sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg1); - sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, reg2); + sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, adcdac_ctrl); if (!mute) dump_reg(codec); return 0; @@ -503,15 +503,104 @@ static void sgtl5000_pcm_shutdown(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; int reg; reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); - reg &= ~(SGTL5000_I2S_IN_POWERUP | SGTL5000_I2S_OUT_POWERUP); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg |= SGTL5000_I2S_IN_POWERUP; + else + reg |= SGTL5000_I2S_OUT_POWERUP; sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); - reg = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL); - reg &= ~SGTL5000_I2S_MASTER; - sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, reg); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_ADC_POWERUP; + if (sgtl5000->capture_channels == 1) + reg &= ~SGTL5000_ADC_STEREO; + else + reg |= SGTL5000_ADC_STEREO; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + } + + return 0; +} + +static int sgtl5000_pcm_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sgtl5000->playback_active++; + else + sgtl5000->capture_active++; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (sgtl5000->master_substream) { + master_runtime = sgtl5000->master_substream->runtime; + + pr_debug("Constraining to %d bits at %dHz\n", + master_runtime->sample_bits, master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + sgtl5000->slave_substream = substream; + } else + sgtl5000->master_substream = substream; + + return 0; +} + +static void sgtl5000_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + int reg, dig_pwr, ana_pwr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sgtl5000->playback_active--; + else + sgtl5000->capture_active--; + + if (sgtl5000->master_substream == substream) + sgtl5000->master_substream = sgtl5000->slave_substream; + + sgtl5000->slave_substream = NULL; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ana_pwr = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + } + + dig_pwr = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dig_pwr &= ~SGTL5000_I2S_IN_POWERUP; + else + dig_pwr &= ~SGTL5000_I2S_OUT_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr); + + if (!sgtl5000->playback_active && !sgtl5000->capture_active) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL); + reg &= ~SGTL5000_I2S_MASTER; + sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, reg); + } } /* @@ -533,21 +622,21 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, int div2 = 0; int reg; + pr_debug("%s channels=%d\n", __func__, channels); + if (!sgtl5000->sysclk) { pr_err("%s: set sysclk first!\n", __func__); return -EFAULT; } - /* rev 1 does not support mono playback */ - if (sgtl5000->rev != 0x00) { - reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_TEST2); - if (channels == 1) - reg |= SGTL5000_MONO_DAC; - else - reg &= ~SGTL5000_MONO_DAC; - sgtl5000_write(codec, SGTL5000_CHIP_ANA_TEST2, reg); + if (substream == sgtl5000->slave_substream) { + pr_debug("Ignoring hw_params for slave substream\n"); + return 0; } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + sgtl5000->capture_channels = channels; + switch (sgtl5000->lrclk) { case 32000: clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; @@ -655,12 +744,23 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, } sgtl5000_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl); sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl); - reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); - reg |= SGTL5000_I2S_IN_POWERUP | SGTL5000_I2S_OUT_POWERUP; - sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + return 0; } +static void sgtl5000_mic_bias(struct snd_soc_codec *codec, int enable) +{ + int reg, bias_r = 0; + if (enable) + bias_r = SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT; + reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL); + if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) { + reg &= ~SGTL5000_BIAS_R_MASK; + reg |= bias_r; + sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); + } +} + static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -672,17 +772,10 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, if (codec->bias_level == SND_SOC_BIAS_ON) break; - reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL); - reg &= ~SGTL5000_BIAS_R_MASK; - reg |= SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT; - sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); + sgtl5000_mic_bias(codec, 1); - /* must power up hp/line out before vag & dac to - avoid pops. */ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); reg |= SGTL5000_VAG_POWERUP; - reg |= SGTL5000_DAC_POWERUP; - reg |= SGTL5000_ADC_POWERUP; sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); msleep(400); @@ -692,11 +785,7 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, if (codec->bias_level == SND_SOC_BIAS_PREPARE) break; - reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL); - if ((reg & SGTL5000_BIAS_R_MASK) != 0) { - reg &= ~SGTL5000_BIAS_R_MASK; - sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); - } + sgtl5000_mic_bias(codec, 0); /* must power up hp/line out before vag & dac to avoid pops. */ @@ -705,11 +794,16 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, delay = 400; reg &= ~SGTL5000_VAG_POWERUP; reg |= SGTL5000_DAC_POWERUP; - reg |= SGTL5000_ADC_POWERUP; + reg |= SGTL5000_HP_POWERUP; + reg |= SGTL5000_LINE_OUT_POWERUP; sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); if (delay) msleep(delay); + reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + reg |= SGTL5000_DAC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + break; case SND_SOC_BIAS_STANDBY: /* Off, with power */ @@ -722,25 +816,23 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, call digital_mute to mute after record. */ sgtl5000_digital_mute(&sgtl5000_dai, 1); - reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL); - if ((reg & SGTL5000_BIAS_R_MASK) != 0) { - reg &= ~SGTL5000_BIAS_R_MASK; - sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); - } + sgtl5000_mic_bias(codec, 0); - /* must power up hp/line out before vag & dac to - avoid pops. */ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); if (reg & SGTL5000_VAG_POWERUP) { reg &= ~SGTL5000_VAG_POWERUP; - reg |= SGTL5000_DAC_POWERUP; - reg |= SGTL5000_ADC_POWERUP; sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); msleep(400); } - reg &= ~(SGTL5000_DAC_POWERUP | SGTL5000_ADC_POWERUP); + reg &= ~SGTL5000_DAC_POWERUP; + reg &= ~SGTL5000_HP_POWERUP; + reg &= ~SGTL5000_LINE_OUT_POWERUP; sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + reg &= ~SGTL5000_DAC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + break; case SND_SOC_BIAS_OFF: /* Off, without power */ @@ -784,6 +876,8 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, struct snd_soc_dai_ops sgtl5000_ops = { .shutdown = sgtl5000_pcm_shutdown, .hw_params = sgtl5000_pcm_hw_params, + .prepare = sgtl5000_pcm_prepare, + .startup = sgtl5000_pcm_startup, .digital_mute = sgtl5000_digital_mute, .set_fmt = sgtl5000_set_dai_fmt, .set_sysclk = sgtl5000_set_dai_sysclk @@ -793,7 +887,7 @@ struct snd_soc_dai sgtl5000_dai = { .name = "SGTL5000", .playback = { .stream_name = "Playback", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = SGTL5000_RATES, .formats = SGTL5000_FORMATS, @@ -801,7 +895,7 @@ struct snd_soc_dai sgtl5000_dai = { .capture = { .stream_name = "Capture", .channels_min = 1, - .channels_max = 1, + .channels_max = 2, .rates = SGTL5000_RATES, .formats = SGTL5000_FORMATS, }, @@ -841,6 +935,8 @@ static int sgtl5000_resume(struct platform_device *pdev) /* Bring the codec back up to standby first to minimise pop/clicks */ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE); sgtl5000_set_bias_level(codec, codec->suspend_bias_level); return 0; @@ -871,11 +967,6 @@ static int sgtl5000_init(struct snd_soc_device *socdev) sgtl5000->rev = (val & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; dev_info(&client->dev, "SGTL5000 revision %d\n", sgtl5000->rev); - /* For Rev2 or higher, CODEC will copy left channel data to right. - For Rev1, set playback channels_min to 2. */ - if (sgtl5000->rev == 0x00) /* if chip is rev 1 */ - sgtl5000_dai.playback.channels_min = 2; - codec->name = "SGTL5000"; codec->owner = THIS_MODULE; codec->read = sgtl5000_read_reg_cache; @@ -902,10 +993,10 @@ static int sgtl5000_init(struct snd_soc_device *socdev) sgtl5000_sync_reg_cache(codec); /* reset value */ - ana_pwr = SGTL5000_DAC_STERO | + ana_pwr = SGTL5000_DAC_STEREO | SGTL5000_LINREG_SIMPLE_POWERUP | SGTL5000_STARTUP_POWERUP | - SGTL5000_ADC_STERO | SGTL5000_REFTOP_POWERUP; + SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP; lreg_ctrl = 0; ref_ctrl = 0; lo_ctrl = 0; diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h index d70552f..177be05 100644 --- a/sound/soc/codecs/sgtl5000.h +++ b/sound/soc/codecs/sgtl5000.h @@ -324,7 +324,7 @@ extern struct snd_soc_codec_device soc_codec_dev_sgtl5000; /* * SGTL5000_CHIP_ANA_POWER */ -#define SGTL5000_DAC_STERO 0x4000 +#define SGTL5000_DAC_STEREO 0x4000 #define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 #define SGTL5000_STARTUP_POWERUP 0x1000 #define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 @@ -332,7 +332,7 @@ extern struct snd_soc_codec_device soc_codec_dev_sgtl5000; #define SGTL5000_LINEREG_D_POWERUP 0x0200 #define SGTL5000_VCOAMP_POWERUP 0x0100 #define SGTL5000_VAG_POWERUP 0x0080 -#define SGTL5000_ADC_STERO 0x0040 +#define SGTL5000_ADC_STEREO 0x0040 #define SGTL5000_REFTOP_POWERUP 0x0020 #define SGTL5000_HP_POWERUP 0x0010 #define SGTL5000_DAC_POWERUP 0x0008 -- 1.6.0.4