From: Misael Lopez Cruz <[email protected]>

TWL6030 codec sysclk can be provided by: low-power or high
performance PLL.

The low-power PLL takes a low-frequency input at 32,768 Hz and
generates an approximate of 17.64 or 19.2 MHz.

The high-performance PLL generates an exact 19.2 MHz clock signal
from high-frequency input at 12/19.2/26/38.4 MHz.

For the particular case of headset path, PLL being used defines the
headset power mode: low-power, high-performance. Headset DAC and
output driver should be configured according to the selected mode.
17.64 MHz sysclk is recommended only for headset low-power playback
mode.

Signed-off-by: Misael Lopez Cruz <[email protected]>
Signed-off-by: Jorge Eduardo Candelaria <[email protected]>
Signed-off-by: Margarita Olaya Cabrera <[email protected]>
---
 sound/soc/codecs/twl6030.c |  232 +++++++++++++++++++++++++++++++++++++------
 sound/soc/codecs/twl6030.h |   16 +++
 2 files changed, 215 insertions(+), 33 deletions(-)

diff --git a/sound/soc/codecs/twl6030.c b/sound/soc/codecs/twl6030.c
index ec838b1..792407f 100644
--- a/sound/soc/codecs/twl6030.c
+++ b/sound/soc/codecs/twl6030.c
@@ -39,7 +39,7 @@
 
 #include "twl6030.h"
 
-#define TWL6030_RATES   (SNDRV_PCM_RATE_96000)
+#define TWL6030_RATES   (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
 #define TWL6030_FORMATS         (SNDRV_PCM_FMTBIT_S32_LE)
 
 /* codec private data */
@@ -47,6 +47,9 @@ struct twl6030_data {
        struct snd_soc_codec codec;
        int audpwron;
        int codec_powered;
+       int pll;
+       unsigned int sysclk;
+       struct snd_pcm_hw_constraint_list *sysclk_constraints;
 };
 
 /*
@@ -326,6 +329,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
@@ -607,55 +633,195 @@ static int twl6030_set_bias_level(struct snd_soc_codec 
*codec,
        return 0;
 }
 
+/* set of rates for each pll: low-power and high-performance */
+
+static unsigned int lp_rates[] = {
+       88200,
+       96000,
+};
+
+static struct snd_pcm_hw_constraint_list lp_constraints = {
+       .count  = ARRAY_SIZE(lp_rates),
+       .list   = lp_rates,
+};
+
+static unsigned int hp_rates[] = {
+       96000,
+};
+
+static struct snd_pcm_hw_constraint_list hp_constraints = {
+       .count  = ARRAY_SIZE(hp_rates),
+       .list   = hp_rates,
+};
+
+static int twl6030_startup(struct snd_pcm_substream *substream,
+                       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_data *priv = codec->private_data;
+
+       if (!priv->sysclk) {
+               dev_err(codec->dev,
+                       "no mclk configured, call set_sysclk() on init\n");
+               return -EINVAL;
+       }
+
+       snd_pcm_hw_constraint_list(substream->runtime, 0,
+                               SNDRV_PCM_HW_PARAM_RATE,
+                               priv->sysclk_constraints);
+
+       return 0;
+}
+
+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_data *priv = codec->private_data;
+       u8 lppllctl;
+       int rate;
+
+       /* nothing to do for high-perf pll, it supports only 48 kHz */
+       if (priv->pll == TWL6030_HPPLL_ID)
+               return 0;
+
+       lppllctl = twl6030_read_reg_cache(codec, TWL6030_REG_LPPLLCTL);
+
+       rate = params_rate(params);
+       switch (rate) {
+       case 88200:
+               lppllctl |= TWL6030_LPLLFIN;
+               priv->sysclk = 17640000;
+               break;
+       case 96000:
+               lppllctl &= ~TWL6030_LPLLFIN;
+               priv->sysclk = 19200000;
+               break;
+       default:
+               dev_err(codec->dev, "unsupported rate %d\n", rate);
+               return -EINVAL;
+       }
+
+       twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+
+       return 0;
+}
+
 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 hppllctl, lppllctl;
 
        hppllctl = twl6030_read_reg_cache(codec, TWL6030_REG_HPPLLCTL);
-       hppllctl &= ~TWL6030_MCLK_MSK;
-
-       switch (freq) {
-       case 12000000:
-               /* mclk input, pll enabled */
-               hppllctl |= TWL6030_MCLK_12000KHZ |
-                           TWL6030_HPLLSQRBP |
-                           TWL6030_HPLLENA;
-               break;
-       case 19200000:
-               /* mclk input, pll disabled */
-               hppllctl |= TWL6030_MCLK_19200KHZ |
-                           TWL6030_HPLLSQRBP |
-                           TWL6030_HPLLBP;
-               break;
-       case 26000000:
-               /* mclk input, pll enabled */
-               hppllctl |= TWL6030_MCLK_26000KHZ |
-                           TWL6030_HPLLSQRBP |
-                           TWL6030_HPLLENA;
+       lppllctl = twl6030_read_reg_cache(codec, TWL6030_REG_LPPLLCTL);
+
+       switch (clk_id) {
+       case TWL6030_SYSCLK_SEL_LPPLL:
+               switch (freq) {
+               case 32768:
+                       /* headset dac and driver must be in low-power mode */
+                       headset_power_mode(codec, 0);
+
+                       /* clk32k input requires low-power pll */
+                       lppllctl |= TWL6030_LPLLENA;
+                       twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+                       mdelay(5);
+                       lppllctl &= ~TWL6030_HPLLSEL;
+                       twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+                       hppllctl &= ~TWL6030_HPLLENA;
+                       twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppllctl);
+                       break;
+               default:
+                       dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+                       return -EINVAL;
+               }
+
+               /* lppll divider */
+               switch (priv->sysclk) {
+               case 17640000:
+                       lppllctl |= TWL6030_LPLLFIN;
+                       break;
+               case 19200000:
+                       lppllctl &= ~TWL6030_LPLLFIN;
+                       break;
+               default:
+                       /* sysclk not yet configured */
+                       lppllctl &= ~TWL6030_LPLLFIN;
+                       priv->sysclk = 19200000;
+                       break;
+               }
+
+               twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+
+               priv->pll = TWL6030_LPPLL_ID;
+               priv->sysclk_constraints = &lp_constraints;
                break;
-       case 38400000:
-               /* clk slicer, pll disabled */
-               hppllctl |= TWL6030_MCLK_38400KHZ |
-                           TWL6030_HPLLSQRENA |
-                           TWL6030_HPLLBP;
+       case TWL6030_SYSCLK_SEL_HPPLL:
+               hppllctl &= ~TWL6030_MCLK_MSK;
+
+               switch (freq) {
+               case 12000000:
+                       /* mclk input, pll enabled */
+                       hppllctl |= TWL6030_MCLK_12000KHZ |
+                                   TWL6030_HPLLSQRBP |
+                                   TWL6030_HPLLENA;
+                       break;
+               case 19200000:
+                       /* mclk input, pll disabled */
+                       hppllctl |= TWL6030_MCLK_19200KHZ |
+                                   TWL6030_HPLLSQRBP |
+                                   TWL6030_HPLLBP;
+                       break;
+               case 26000000:
+                       /* mclk input, pll enabled */
+                       hppllctl |= TWL6030_MCLK_26000KHZ |
+                                   TWL6030_HPLLSQRBP |
+                                   TWL6030_HPLLENA;
+                       break;
+               case 38400000:
+                       /* clk slicer, pll disabled */
+                       hppllctl |= TWL6030_MCLK_38400KHZ |
+                                   TWL6030_HPLLSQRENA |
+                                   TWL6030_HPLLBP;
+                       break;
+               default:
+                       dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+                       return -EINVAL;
+               }
+
+               /* headset dac and driver must be in high-performance mode */
+               headset_power_mode(codec, 1);
+
+               twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppllctl);
+               udelay(500);
+               lppllctl |= TWL6030_HPLLSEL;
+               twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+               lppllctl &= ~TWL6030_LPLLENA;
+               twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
+
+               /* high-performance pll can provide only 19.2 MHz */
+               priv->pll = TWL6030_HPPLL_ID;
+               priv->sysclk = 19200000;
+               priv->sysclk_constraints = &hp_constraints;
                break;
        default:
-               dev_err(codec->dev, "unknown sysclk freq %d\n", freq);
+               dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
                return -EINVAL;
        }
 
-       twl6030_write(codec, TWL6030_REG_HPPLLCTL, hppllctl);
-
-       /* Disable LPPLL and select HPPLL */
-       lppllctl = TWL6030_HPLLSEL;
-       twl6030_write(codec, TWL6030_REG_LPPLLCTL, lppllctl);
-
        return 0;
 }
 
 static struct snd_soc_dai_ops twl6030_dai_ops = {
+       .startup        = twl6030_startup,
+       .hw_params      = twl6030_hw_params,
        .set_sysclk     = twl6030_set_dai_sysclk,
 };
 
diff --git a/sound/soc/codecs/twl6030.h b/sound/soc/codecs/twl6030.h
index 81d25f6..c321347 100644
--- a/sound/soc/codecs/twl6030.h
+++ b/sound/soc/codecs/twl6030.h
@@ -100,10 +100,26 @@
 #define TWL6030_LPLLFIN                        0x08
 #define TWL6030_HPLLSEL                        0x10
 
+/* 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
 
+#define TWL6030_SYSCLK_SEL_LPPLL       1
+#define TWL6030_SYSCLK_SEL_HPPLL       2
+
+#define TWL6030_HPPLL_ID               1
+#define TWL6030_LPPLL_ID               2
+
 extern struct snd_soc_dai twl6030_dai;
 extern struct snd_soc_codec_device soc_codec_dev_twl6030;
 
-- 
1.6.1.3

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to