[alsa-devel] [PATCHv2 5/7] ASoC: TWL6030: Add support for low-power mode

TWL6030 codec supports two power modes: low-power and high performance.
In low-power mode, headset downlink must be the only path enabled and components in that path (headset DAC and driver) should be in that mode too. In this mode, codec can stream audio at 44.1 and 48 kHz if sys clock from CLK32K is configured to 17.64 and 19.2 MHz using low-power PLL, respectively.
In high-performance mode, codec can only work at 19.2 MHz from high-performance PLL. All paths supported in the codec can be used but audio can be streamed only at 48 kHz.
Signed-off-by: Misael Lopez Cruz x0052729@ti.com --- sound/soc/codecs/twl6030.c | 214 ++++++++++++++++++++++++++++++++++++++------ sound/soc/codecs/twl6030.h | 17 ++++ 2 files changed, 202 insertions(+), 29 deletions(-)
diff --git a/sound/soc/codecs/twl6030.c b/sound/soc/codecs/twl6030.c index f1e333f..032619d 100644 --- a/sound/soc/codecs/twl6030.c +++ b/sound/soc/codecs/twl6030.c @@ -46,6 +46,7 @@ struct twl6030_data { struct snd_soc_codec codec; int codec_powered; + unsigned int sysclk; };
/* @@ -305,6 +306,29 @@ static void twl6030_power_down(struct snd_soc_codec *codec) mdelay(10); }
+/* set headset dac and driver power mode */ +static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) +{ + int hslctl, hsrctl; + int mask = TWL6030_HSDRVMODEL | TWL6030_HSDACMODEL; + + hslctl = twl6030_read_reg_cache(codec, TWL6030_REG_HSLCTL); + hsrctl = twl6030_read_reg_cache(codec, TWL6030_REG_HSRCTL); + + if (high_perf) { + hslctl &= ~mask; + hsrctl &= ~mask; + } else { + hslctl |= mask; + hsrctl |= mask; + } + + twl6030_write(codec, TWL6030_REG_HSLCTL, hslctl); + twl6030_write(codec, TWL6030_REG_HSRCTL, hsrctl); + + return 0; +} + /* * MICATT volume control: * from -6 to 0 dB in 6 dB steps @@ -598,6 +622,7 @@ static int twl6030_hw_params(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 twl6030_data *priv = codec->private_data; int rate, format;
/* hardware dai (McPDM) requires bit stream of twice @@ -607,7 +632,20 @@ static int twl6030_hw_params(struct snd_pcm_substream *substream, rate = params_rate(params); switch (rate) { case 44100: + if (priv->sysclk != 17640000) { + dev_err(codec->dev, + "rate %d not supported at current sysclk %d\n", + rate, priv->sysclk); + return -EINVAL; + } + break; case 48000: + if (priv->sysclk != 19200000) { + dev_err(codec->dev, + "rate %d not supported at current sysclk %d\n", + rate, priv->sysclk); + return -EINVAL; + } break; default: dev_err(codec->dev, "unknown rate %d\n", rate); @@ -630,46 +668,163 @@ static int twl6030_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; + struct twl6030_data *priv = codec->private_data; u8 hppll, lppll;
+ lppll = twl6030_read_reg_cache(codec, TWL6030_REG_LPPLLCTL); hppll = twl6030_read_reg_cache(codec, TWL6030_REG_HPPLLCTL); - hppll &= TWL6030_HPLLRST; - - switch (freq) { - case 12000000: - /* MCLK input, PLL enabled */ - hppll = TWL6030_MCLK_12000KHZ - | TWL6030_HPLLSQRBP - | TWL6030_HPLLENA; - break; - case 19200000: - /* MCLK input, PLL disabled */ - hppll = TWL6030_MCLK_19200KHZ - | TWL6030_HPLLSQRBP - | TWL6030_HPLLBP; + + switch (clk_id) { + case TWL6030_SYSCLK_SEL_LPPLL: + if (freq != 32768) { + dev_err(codec->dev, "invalid sysclk freq %d\n", freq); + return -EINVAL; + } + + /* CLK32K input requires low-power PLL */ + lppll |= TWL6030_LPLLENA | TWL6030_LPLLSEL; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + mdelay(5); + lppll &= ~TWL6030_HPLLSEL; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + + /* headset dac and driver must be in low-power mode */ + headset_power_mode(codec, 0); + + hppll &= ~TWL6030_HPLLENA; + twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppll); break; - case 26000000: - /* MCLK input, PLL enabled */ - hppll = TWL6030_MCLK_26000KHZ - | TWL6030_HPLLSQRBP - | TWL6030_HPLLENA; + case TWL6030_SYSCLK_SEL_HPPLL: + switch (freq) { + case 12000000: + hppll = TWL6030_MCLK_12000KHZ; + break; + case 26000000: + hppll = TWL6030_MCLK_26000KHZ; + break; + default: + dev_err(codec->dev, "invalid sysclk freq %d\n", freq); + return -EINVAL; + } + + /* 12 and 26 MHz freqs require high-performance PLL */ + hppll |= TWL6030_HPLLSQRBP | TWL6030_HPLLENA; + twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppll); + udelay(500); + + /* headset dac and driver must be in high-performance mode */ + headset_power_mode(codec, 1); + + lppll |= TWL6030_HPLLSEL; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + lppll &= ~TWL6030_LPLLENA; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + + /* only 19.2 MHz can be generated by HPPLL */ + priv->sysclk = 19200000; break; - case 38400000: - /* clk slicer input, PLL disabled */ - hppll = TWL6030_MCLK_38400KHZ - | TWL6030_HPLLSQRENA - | TWL6030_HPLLBP; + case TWL6030_SYSCLK_SEL_MCLK: + switch (freq) { + case 19200000: + hppll = TWL6030_MCLK_19200KHZ | TWL6030_HPLLSQRBP; + break; + case 38400000: + hppll = TWL6030_MCLK_38400KHZ; + break; + default: + dev_err(codec->dev, "invalid sysclk freq %d\n", freq); + return -EINVAL; + } + + /* 19.2 and 38.4 MHz freqs don't require PLL */ + hppll |= TWL6030_HPLLBP; + twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppll); + udelay(500); + + /* headset dac and driver must be in high-performance mode */ + headset_power_mode(codec, 1); + + lppll |= TWL6030_HPLLSEL; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + lppll &= ~TWL6030_LPLLENA; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + + /* only 19.2 MHz can be generated by MCLK */ + priv->sysclk = 19200000; break; default: - dev_err(codec->dev, "unknown sysclk rate %d\n", freq); + dev_err(codec->dev, "unknown clk_id %d\n", clk_id); return -EINVAL; }
- twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppll); + return 0; +} + +static int twl6030_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct twl6030_data *priv = codec->private_data; + int div; + u8 ldoctl, lppllctl, hppllctl; + + ldoctl = twl6030_read_reg_cache(codec, TWL6030_REG_LDOCTL); + lppllctl = twl6030_read_reg_cache(codec, TWL6030_REG_LPPLLCTL); + hppllctl = twl6030_read_reg_cache(codec, TWL6030_REG_HPPLLCTL); + + if (freq_in && freq_out) { + /* div = round(freq_out / freq_in) */ + div = (freq_out + (freq_in >> 2)) / freq_in; + + switch (pll_id) { + case TWL6030_LPPLL_ID: + if (div < 512) { + dev_err(codec->dev, "invalid pll div %d\n", + div); + return -EINVAL; + } + twl6030_write(codec, TWL6030_REG_LPPLLDIV, div - 512); + lppllctl |= TWL6030_LPLLENA; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl); + + priv->sysclk = freq_out; + break; + case TWL6030_HPPLL_ID: + hppllctl |= TWL6030_HPLLENA; + twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppllctl); + break; + default: + dev_err(codec->dev, "unknown pll id %d\n", pll_id); + return -EINVAL; + }
- /* Disable LPPLL and select HPPLL */ - lppll = TWL6030_HPLLSEL; - twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll); + /* switch to pll */ + ldoctl &= ~TWL6030_OSCENA; + twl6030_write(codec, TWL6030_REG_LDOCTL, ldoctl); + udelay(10); + } else { + /* switch to internal oscillator */ + ldoctl |= TWL6030_OSCENA; + twl6030_write(codec, TWL6030_REG_LDOCTL, ldoctl); + udelay(10); + + /* turn-off pll */ + switch (pll_id) { + case TWL6030_HPPLL_ID: + hppllctl &= ~TWL6030_HPLLENA; + twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppllctl); + break; + case TWL6030_LPPLL_ID: + lppllctl &= ~TWL6030_LPLLENA; + twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl); + break; + default: + dev_err(codec->dev, "unknown pll id %d\n", pll_id); + return -EINVAL; + } + + priv->sysclk = 0; + }
return 0; } @@ -689,6 +844,7 @@ static int twl6030_set_dai_fmt(struct snd_soc_dai *codec_dai, static struct snd_soc_dai_ops twl6030_dai_ops = { .hw_params = twl6030_hw_params, .set_sysclk = twl6030_set_dai_sysclk, + .set_pll = twl6030_set_dai_pll, .set_fmt = twl6030_set_dai_fmt, };
diff --git a/sound/soc/codecs/twl6030.h b/sound/soc/codecs/twl6030.h index 7375ae8..15d3e1b 100644 --- a/sound/soc/codecs/twl6030.h +++ b/sound/soc/codecs/twl6030.h @@ -92,6 +92,10 @@ #define TWL6030_MCLK_38400KHZ (3 << 5) #define TWL6030_MCLK_MSK 0x60
+#define TWL6030_SYSCLK_SEL_LPPLL 1 +#define TWL6030_SYSCLK_SEL_HPPLL 2 +#define TWL6030_SYSCLK_SEL_MCLK 3 + /* LPPLLCTL (0x08) fields */
#define TWL6030_LPLLENA 0x01 @@ -100,6 +104,19 @@ #define TWL6030_LPLLFIN 0x08 #define TWL6030_HPLLSEL 0x10
+#define TWL6030_HPPLL_ID 1 +#define TWL6030_LPPLL_ID 2 + +/* HSLCTL (0x10) fields */ + +#define TWL6030_HSDACMODEL 0x02 +#define TWL6030_HSDRVMODEL 0x08 + +/* HSRCTL (0x11) fields */ + +#define TWL6030_HSDACMODER 0x02 +#define TWL6030_HSDRVMODER 0x08 + /* ACCCTL (0x2D) fields */
#define TWL6030_RESETSPLIT 0x04

On Fri, Sep 25, 2009 at 09:03:30PM -0500, Lopez Cruz, Misael wrote:
TWL6030 codec supports two power modes: low-power and high performance.
In low-power mode, headset downlink must be the only path enabled and components in that path (headset DAC and driver) should be in that mode too. In this mode, codec can stream audio at 44.1 and 48 kHz if sys clock from CLK32K is configured to 17.64 and 19.2 MHz using low-power PLL, respectively.
In high-performance mode, codec can only work at 19.2 MHz from high-performance PLL. All paths supported in the codec can be used but audio can be streamed only at 48 kHz.
The driver should also set constraints at runtime so that applications know they shouldn't use an unsupported sample rate. See wm8988 for an example of doing this - it has some similar SYSCLK based restrictions.
It also strikes me that this is something that should be exposed to user space since if there's a 19.2MHz MCLK it's something that the system could reasonably want to activate and deactivate on the fly. For bonus points the driver could prevent switching to low power mode if any paths are active which conflict with it. Doing it automatically would be ideal but since it sounds like a performance degradation to use low power mode that's not really suitable.

Mark,
Mark Brown wrote:
On Fri, Sep 25, 2009 at 09:03:30PM -0500, Lopez Cruz, Misael wrote:
TWL6030 codec supports two power modes: low-power and high performance.
In low-power mode, headset downlink must be the only path enabled and components in that path (headset DAC and driver) should be in that mode too. In this mode, codec can stream audio at 44.1 and 48 kHz if sys clock from CLK32K is configured to 17.64 and 19.2 MHz using low-power PLL, respectively.
In high-performance mode, codec can only work at 19.2 MHz from high-performance PLL. All paths supported in the codec can be used but audio can be streamed only at 48 kHz.
The driver should also set constraints at runtime so that applications know they shouldn't use an unsupported sample rate. See wm8988 for an example of doing this - it has some similar SYSCLK based restrictions.
I think I got confused with what to do in set_sysclk and set_pll. In my current approach:
- set_sysclk takes care of setting corresponding clk source: lppll or hppll. But it also disables the pll not in use (i.e. if lppll is set, then disable hppll) - set_pll takes care of setting pll div for lppll (which is meant to receive 32k clk) and configure hppll for any of the supported freq_in (12, 19.2, 26, 38.4 MHz). Because only after this point I know the value of sysclk, the constraints are set here. Is it fine? For lppll the sysclk is set to requested freq_out (if div value is in the valid range) and for hppll it's always 19.2 and it's the only clk rate support by that pll.
In contrast, in wm8988 the sysclk and contraints are set directly in set_sysclk.
I have been kind of insecure about all these clk configuration, so I'd be great if you could explain what's expected to go in set_sysclk as well as in set_pll.
It also strikes me that this is something that should be exposed to user space since if there's a 19.2MHz MCLK it's something that the system could reasonably want to activate and deactivate on the fly.
For bonus points the driver could prevent switching to low power mode if any paths are active which conflict with it. Doing it automatically would be ideal but since it sounds like a performance degradation to use low power mode that's not really suitable.
Any suggestion on how to detect the active paths? Using widgets with events? The only path active to switch to low-power should be headset.
Thanks, -Misa

On Tue, Sep 29, 2009 at 10:02:49PM -0500, Lopez Cruz, Misael wrote:
I think I got confused with what to do in set_sysclk and set_pll. In my current approach:
- set_sysclk takes care of setting corresponding clk source: lppll or hppll. But it also disables the pll not in use (i.e. if lppll is set, then disable hppll)
- set_pll takes care of setting pll div for lppll (which is meant to receive 32k clk) and configure hppll for any of the supported freq_in (12, 19.2, 26, 38.4 MHz). Because only after this point I know the value of sysclk, the constraints are set here. Is it fine? For lppll the sysclk is set to requested freq_out (if div value is in the valid range) and for hppll it's always 19.2 and it's the only clk rate support by that pll.
Hrm. Is it actually worth having manual configuration of the PLL (beyond selection of the PLL to use), or could the driver figure out the required output frequency for lppll? Either way is fine from an ASoC point of view, it's just that if the driver can figure the configuration out for itself that makes it a little easier to use.
In any case...
In contrast, in wm8988 the sysclk and contraints are set directly in set_sysclk.
Note that they're actually passed to ALSA when the substreams are opened - all that wm8988 is doing in set_sysclk() is recording the constraints that it will use next time that happens. Up until the point where you tell ALSA about the constraints you can do pretty much whatever you like so long as it makes sense, so what you say above is probably fine. For the wm8988 there's no PLL so once the input clock is known there's nothing more to do for it.

Mark,
Mark Brown wrote:
On Fri, Sep 25, 2009 at 09:03:30PM -0500, Lopez Cruz, Misael wrote:
In high-performance mode, codec can only work at 19.2 MHz from high-performance PLL. All paths supported in the codec can be used but audio can be streamed only at 48 kHz.
The driver should also set constraints at runtime so that applications know they shouldn't use an unsupported sample rate. See wm8988 for an example of doing this - it has some similar SYSCLK based restrictions.
In OMAP4, we can have the processor connected to Audio Backend and then to Phoenix (OMAP4->ABE->McPDM->TWL6030), but it's also possible to bypass the Audio Backend (OMAP4->McPDM->TWL6030).
The McPDM link requires data to be sent at twice the desired rate. In the first scenario (Audio Backend enabled), OMAP4 sends the data at 48kHz to ABE and it upsamples it to 96kHz and then send to TWL6030 through McPDM. In the second scenario (Audio Backend bypassed), OMAP4 has to send the audio data at 96kHz, since no upsampling blocks are available now.
_From TWL6030 CODEC driver pespective, the DAI link supported rate should be 48kHz or 96kHz? If the final goal is to explicitly represent all links in the system (ABE, Phoenix, McBSP, etc), then ABE link should expose somehow that it delives upsampled data. If that assumption is true, then TWL6030 DAI should support 96kHz.
A second concern is that runtime constraints will prevent rates supported by the system, when the constraints are only valid for the link. For example, if the TWL6030 CODEC adds a contraint for 96kHz, then it prevents the whole system to play 48kHz, which is supported when ABE is enabled since ABE upsamples it.
-Misa

On Tuesday 13 October 2009 23:15:45 ext Lopez Cruz, Misael wrote:
Mark,
Mark Brown wrote:
On Fri, Sep 25, 2009 at 09:03:30PM -0500, Lopez Cruz, Misael wrote:
In high-performance mode, codec can only work at 19.2 MHz from high-performance PLL. All paths supported in the codec can be used but audio can be streamed only at 48 kHz.
The driver should also set constraints at runtime so that applications know they shouldn't use an unsupported sample rate. See wm8988 for an example of doing this - it has some similar SYSCLK based restrictions.
In OMAP4, we can have the processor connected to Audio Backend and then to Phoenix (OMAP4->ABE->McPDM->TWL6030), but it's also possible to bypass the Audio Backend (OMAP4->McPDM->TWL6030).
The McPDM link requires data to be sent at twice the desired rate. In the first scenario (Audio Backend enabled), OMAP4 sends the data at 48kHz to ABE and it upsamples it to 96kHz and then send to TWL6030 through McPDM. In the second scenario (Audio Backend bypassed), OMAP4 has to send the audio data at 96kHz, since no upsampling blocks are available now.
Sounds interesting. Does McPDM requires that the audio is actually upsampled, or it requires that the for example 48KHz audio data is sent with 96KHz where for example the actual audio data is only in the first half?
From TWL6030 CODEC driver pespective, the DAI link supported rate should be 48kHz or 96kHz? If the final goal is to explicitly represent all links in the system (ABE, Phoenix, McBSP, etc), then ABE link should expose somehow that it delives upsampled data. If that assumption is true, then TWL6030 DAI should support 96kHz.
A second concern is that runtime constraints will prevent rates supported by the system, when the constraints are only valid for the link. For example, if the TWL6030 CODEC adds a contraint for 96kHz, then it prevents the whole system to play 48kHz, which is supported when ABE is enabled since ABE upsamples it.
-Misa
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

On Tue, Oct 13, 2009 at 03:15:45PM -0500, Lopez Cruz, Misael wrote:
From TWL6030 CODEC driver pespective, the DAI link supported rate should be 48kHz or 96kHz? If the final goal is to explicitly represent all links in the system (ABE, Phoenix, McBSP, etc), then ABE link should expose somehow that it delives upsampled data. If that assumption is true, then TWL6030 DAI should support 96kHz.
Yes, that sounds like the way to go.
A second concern is that runtime constraints will prevent rates supported by the system, when the constraints are only valid for the link. For example, if the TWL6030 CODEC adds a contraint for 96kHz, then it prevents the whole system to play 48kHz, which is supported when ABE is enabled since ABE upsamples it.
Initially I'd say that we should only represent the requirements on each end of a given link and make the devices responsible for imposing constraints that come from routing between two interfaces. We'd need to see how all this works in practice, though.
participants (3)
-
Lopez Cruz, Misael
-
Mark Brown
-
Peter Ujfalusi