summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMisael Lopez Cruz <x0052729@ti.com>2009-07-20 10:43:11 -0500
committerMisael Lopez Cruz <x0052729@ti.com>2009-07-24 04:50:08 -0500
commit7fc4767275f77a4112b4249658d81b43f8cb03b1 (patch)
tree191c6cd272781395fce1c34c9e720629cea1e6cd /sound
parent139cdb4602f247166fbad1f7bb0437fdfe3e240f (diff)
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 is configured to 17.64 and 19.2 MHz from CLK32K 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 the audio can be streamed only at 48 kHz. Signed-off-by: Misael Lopez Cruz <x0052729@ti.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/twl6030.c201
-rw-r--r--sound/soc/codecs/twl6030.h19
2 files changed, 183 insertions, 37 deletions
diff --git a/sound/soc/codecs/twl6030.c b/sound/soc/codecs/twl6030.c
index fc4d65c0e58b..b4eb06a198a2 100644
--- a/sound/soc/codecs/twl6030.c
+++ b/sound/soc/codecs/twl6030.c
@@ -38,6 +38,11 @@
#include "twl6030.h"
+/* codec private data */
+struct twl6030_priv_data {
+ unsigned int sysclk;
+};
+
/*
* twl6030 register cache & default register settings
*/
@@ -52,10 +57,10 @@ static const u8 twl6030_reg[TWL6030_CACHEREGNUM] = {
0x00, /* TWL6030_HPPLLCTL 0x07 */
0x00, /* TWL6030_LPPLLCTL 0x08 */
0x00, /* TWL6030_LPPLLDIV 0x09 */
- 0x54, /* TWL6030_AMICBCTL 0x0A */
+ 0x00, /* TWL6030_AMICBCTL 0x0A */
0x00, /* TWL6030_DMICBCTL 0x0B */
- 0x09, /* TWL6030_MICLCTL 0x0C */
- 0x09, /* TWL6030_MICRCTL 0x0D */
+ 0x00, /* TWL6030_MICLCTL 0x0C */
+ 0x00, /* TWL6030_MICRCTL 0x0D */
0x00, /* TWL6030_MICGAIN 0x0E */
0x1B, /* TWL6030_LINEGAIN 0x0F */
0x00, /* TWL6030_HSLCTL 0x10 */
@@ -174,6 +179,29 @@ static void twl6030_power_down(struct snd_soc_codec *codec)
twl6030_write_reg_cache(codec, TWL6030_REG_LPPLLCTL, 0x00);
}
+/* 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
@@ -415,28 +443,47 @@ static int twl6030_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
+ 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_priv_data *twl6030_priv = codec->private_data;
+ int rate, format;
/* the sample rates handled by codec at A-D and D-A
* conversions are 44100 and 48000, but the dai requires
* input rates of twice those values
*/
- switch (params_rate(params)) {
+ rate = params_rate(params);
+ switch (rate) {
case 88200:
+ if (twl6030_priv->sysclk != 17640000) {
+ printk(KERN_ERR "TWL6030 hw params: rate %d "
+ "not supported at current sysclk %d\n",
+ rate, twl6030_priv->sysclk);
+ return -EINVAL;
+ }
break;
case 96000:
+ if (twl6030_priv->sysclk != 19200000) {
+ printk(KERN_ERR "TWL6030 hw params: rate %d "
+ "not supported at current sysclk %d\n",
+ rate, twl6030_priv->sysclk);
+ return -EINVAL;
+ }
break;
default:
printk(KERN_ERR "TWL6030 hw params: unknown rate %d\n",
- params_rate(params));
+ rate);
return -EINVAL;
}
/* the sample lenght handled by codec at A-D and D-A
* conversion is 16-bits, but the dai requires 32-bits
*/
- if (params_format(params) != SNDRV_PCM_FORMAT_S32_LE) {
+ format = params_format(params);
+ if (format != SNDRV_PCM_FORMAT_S32_LE) {
printk(KERN_ERR "TWL6030 hw params: unknown format %d\n",
- params_format(params));
+ format);
return -EINVAL;
}
@@ -447,46 +494,120 @@ 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_priv_data *twl6030_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) {
+ printk(KERN_ERR "TWL6030: set sysclk: invalid 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;
+ }
+
+ /* 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 */
+ twl6030_priv->sysclk = 19200000;
break;
- case 38400000:
- /* clk slicer input, PLL disabled */
- hppll = TWL6030_MCLK_38400KHZ
- | 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:
+ printk(KERN_ERR "TWL6030: set sysclk: invalid 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 */
+ twl6030_priv->sysclk = 19200000;
break;
default:
- printk(KERN_ERR "TWL6030 set sysclk: unknown rate %d\n",
- freq);
+ printk(KERN_ERR "TWL6030 set sysclk: 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_priv_data *twl6030_priv = codec->private_data;
+ int div;
+
+ if (!freq_in || !freq_out)
+ return -EINVAL;
+
+ /* div = round(freq_out / freq_in) */
+ div = (freq_out + (freq_in >> 2)) / freq_in;
+
+ if (pll_id == TWL6030_LPPLL_ID) {
+ if (div < 512) {
+ printk(KERN_ERR "TWL6030 set pll: invalid divider %d\n",
+ div);
+ return -EINVAL;
+ }
- /* Disable LPPLL and select HPPLL */
- lppll = TWL6030_HPLLSEL;
- twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppll);
+ twl6030_write(codec, TWL6030_REG_LPPLLDIV, div - 512);
+ twl6030_priv->sysclk = freq_out;
+ }
return 0;
}
@@ -509,6 +630,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,
};
@@ -613,11 +735,17 @@ static int twl6030_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
+ struct twl6030_priv_data *twl6030_priv;
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
+ twl6030_priv = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (twl6030_priv == NULL)
+ return -ENOMEM;
+
+ codec->private_data = twl6030_priv;
socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
@@ -638,6 +766,7 @@ static int twl6030_remove(struct platform_device *pdev)
twl6030_set_bias_level(codec, SND_SOC_BIAS_OFF);
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
+ kfree(codec->private_data);
kfree(codec);
return 0;
diff --git a/sound/soc/codecs/twl6030.h b/sound/soc/codecs/twl6030.h
index a83f9e79c1e5..9e49c85bc25a 100644
--- a/sound/soc/codecs/twl6030.h
+++ b/sound/soc/codecs/twl6030.h
@@ -77,14 +77,31 @@
#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
#define TWL6030_LPLLRST 0x02
#define TWL6030_LPLLSEL 0x04
-#define TWL6030_FIN 0x08
+#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
+
extern struct snd_soc_dai twl6030_dai;
extern struct snd_soc_codec_device soc_codec_dev_twl6030;