summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
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;