|
@@ -1484,6 +1484,144 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int twl4030_voice_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;
|
|
|
+ u8 infreq;
|
|
|
+ u8 mode;
|
|
|
+
|
|
|
+ /* If the system master clock is not 26MHz, the voice PCM interface is
|
|
|
+ * not avilable.
|
|
|
+ */
|
|
|
+ infreq = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL)
|
|
|
+ & TWL4030_APLL_INFREQ;
|
|
|
+
|
|
|
+ if (infreq != TWL4030_APLL_INFREQ_26000KHZ) {
|
|
|
+ printk(KERN_ERR "TWL4030 voice startup: "
|
|
|
+ "MCLK is not 26MHz, call set_sysclk() on init\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If the codec mode is not option2, the voice PCM interface is not
|
|
|
+ * avilable.
|
|
|
+ */
|
|
|
+ mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
|
|
|
+ & TWL4030_OPT_MODE;
|
|
|
+
|
|
|
+ if (mode != TWL4030_OPTION_2) {
|
|
|
+ printk(KERN_ERR "TWL4030 voice startup: "
|
|
|
+ "the codec mode is not option2\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int twl4030_voice_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;
|
|
|
+ u8 old_mode, mode;
|
|
|
+
|
|
|
+ /* bit rate */
|
|
|
+ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
|
|
|
+ & ~(TWL4030_CODECPDZ);
|
|
|
+ mode = old_mode;
|
|
|
+
|
|
|
+ switch (params_rate(params)) {
|
|
|
+ case 8000:
|
|
|
+ mode &= ~(TWL4030_SEL_16K);
|
|
|
+ break;
|
|
|
+ case 16000:
|
|
|
+ mode |= TWL4030_SEL_16K;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n",
|
|
|
+ params_rate(params));
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mode != old_mode) {
|
|
|
+ /* change rate and set CODECPDZ */
|
|
|
+ twl4030_codec_enable(codec, 0);
|
|
|
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
|
|
|
+ twl4030_codec_enable(codec, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int twl4030_voice_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;
|
|
|
+ u8 infreq;
|
|
|
+
|
|
|
+ switch (freq) {
|
|
|
+ case 26000000:
|
|
|
+ infreq = TWL4030_APLL_INFREQ_26000KHZ;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ printk(KERN_ERR "TWL4030 voice set sysclk: unknown rate %d\n",
|
|
|
+ freq);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ infreq |= TWL4030_APLL_EN;
|
|
|
+ twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|
|
+ unsigned int fmt)
|
|
|
+{
|
|
|
+ struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
+ u8 old_format, format;
|
|
|
+
|
|
|
+ /* get format */
|
|
|
+ old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
|
|
|
+ format = old_format;
|
|
|
+
|
|
|
+ /* set master/slave audio interface */
|
|
|
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
|
+ case SND_SOC_DAIFMT_CBS_CFM:
|
|
|
+ format &= ~(TWL4030_VIF_SLAVE_EN);
|
|
|
+ break;
|
|
|
+ case SND_SOC_DAIFMT_CBS_CFS:
|
|
|
+ format |= TWL4030_VIF_SLAVE_EN;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* clock inversion */
|
|
|
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
|
+ case SND_SOC_DAIFMT_IB_NF:
|
|
|
+ format &= ~(TWL4030_VIF_FORMAT);
|
|
|
+ break;
|
|
|
+ case SND_SOC_DAIFMT_NB_IF:
|
|
|
+ format |= TWL4030_VIF_FORMAT;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (format != old_format) {
|
|
|
+ /* change format and set CODECPDZ */
|
|
|
+ twl4030_codec_enable(codec, 0);
|
|
|
+ twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
|
|
|
+ twl4030_codec_enable(codec, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000)
|
|
|
#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
|
|
|
|
|
@@ -1495,7 +1633,15 @@ static struct snd_soc_dai_ops twl4030_dai_ops = {
|
|
|
.set_fmt = twl4030_set_dai_fmt,
|
|
|
};
|
|
|
|
|
|
-struct snd_soc_dai twl4030_dai = {
|
|
|
+static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
|
|
|
+ .startup = twl4030_voice_startup,
|
|
|
+ .hw_params = twl4030_voice_hw_params,
|
|
|
+ .set_sysclk = twl4030_voice_set_dai_sysclk,
|
|
|
+ .set_fmt = twl4030_voice_set_dai_fmt,
|
|
|
+};
|
|
|
+
|
|
|
+struct snd_soc_dai twl4030_dai[] = {
|
|
|
+{
|
|
|
.name = "twl4030",
|
|
|
.playback = {
|
|
|
.stream_name = "Playback",
|
|
@@ -1510,6 +1656,23 @@ struct snd_soc_dai twl4030_dai = {
|
|
|
.rates = TWL4030_RATES,
|
|
|
.formats = TWL4030_FORMATS,},
|
|
|
.ops = &twl4030_dai_ops,
|
|
|
+},
|
|
|
+{
|
|
|
+ .name = "twl4030 Voice",
|
|
|
+ .playback = {
|
|
|
+ .stream_name = "Playback",
|
|
|
+ .channels_min = 1,
|
|
|
+ .channels_max = 1,
|
|
|
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
|
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
|
|
+ .capture = {
|
|
|
+ .stream_name = "Capture",
|
|
|
+ .channels_min = 1,
|
|
|
+ .channels_max = 2,
|
|
|
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
|
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
|
|
+ .ops = &twl4030_dai_voice_ops,
|
|
|
+},
|
|
|
};
|
|
|
EXPORT_SYMBOL_GPL(twl4030_dai);
|
|
|
|
|
@@ -1550,8 +1713,8 @@ static int twl4030_init(struct snd_soc_device *socdev)
|
|
|
codec->read = twl4030_read_reg_cache;
|
|
|
codec->write = twl4030_write;
|
|
|
codec->set_bias_level = twl4030_set_bias_level;
|
|
|
- codec->dai = &twl4030_dai;
|
|
|
- codec->num_dai = 1;
|
|
|
+ codec->dai = twl4030_dai;
|
|
|
+ codec->num_dai = ARRAY_SIZE(twl4030_dai),
|
|
|
codec->reg_cache_size = sizeof(twl4030_reg);
|
|
|
codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg),
|
|
|
GFP_KERNEL);
|
|
@@ -1645,13 +1808,13 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030);
|
|
|
|
|
|
static int __init twl4030_modinit(void)
|
|
|
{
|
|
|
- return snd_soc_register_dai(&twl4030_dai);
|
|
|
+ return snd_soc_register_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
|
|
|
}
|
|
|
module_init(twl4030_modinit);
|
|
|
|
|
|
static void __exit twl4030_exit(void)
|
|
|
{
|
|
|
- snd_soc_unregister_dai(&twl4030_dai);
|
|
|
+ snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai));
|
|
|
}
|
|
|
module_exit(twl4030_exit);
|
|
|
|