diff options
Diffstat (limited to 'sound/soc/codecs/twl4030.c')
-rw-r--r-- | sound/soc/codecs/twl4030.c | 173 |
1 files changed, 168 insertions, 5 deletions
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index a1b76d7fd13..cc2968cf640 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -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); |