The following patch series merges some of the outstanding changes in the linux-2.6-asoc tree. It updates the core and existing drivers and adds some of the new drivers currently in the tree.
Add myself as a point of contact for the ALSA SoC subsystem and add a reference to the development GIT tree.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- MAINTAINERS | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS index cad0882..a522a67 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3513,6 +3513,9 @@ S: Maintained SOUND - SOC LAYER / DYNAMIC AUDIO POWER MANAGEMENT P: Liam Girdwood M: liam.girdwood@wolfsonmicro.com +P: Mark Brown +M: broonie@opensource.wolfsonmicro.com +T: git opensource.wolfsonmicro.com/linux-2.6-asoc L: alsa-devel@alsa-project.org (subscribers-only) S: Supported
From: Graeme Gregory graeme@openmoko.com
This one changes the DMA initialisation as it turns out the DMA driver in s3c24xx doesnt store registers between suspend/resume so you have to re-initialise the channels on every resume.
Signed-off-by: Graeme Gregory graeme@openmoko.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/s3c24xx/s3c24xx-pcm.c | 48 ++++++++++++++++++++------------------ 1 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index 4107a87..e9f3b60 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -49,7 +49,9 @@ static const struct snd_pcm_hardware s3c24xx_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID, + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U8 | @@ -176,28 +178,6 @@ static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, } }
- /* channel needs configuring for mem=>device, increment memory addr, - * sync to pclk, half-word transfers to the IIS-FIFO. */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | - S3C2410_DISRCC_APB, prtd->params->dma_addr); - - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_SYNC_PCLK | - S3C2410_DCON_HANDSHAKE); - } else { - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_HANDSHAKE | - S3C2410_DCON_SYNC_PCLK); - - s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_HW, 0x3, - prtd->params->dma_addr); - } - s3c2410_dma_set_buffdone_fn(prtd->params->channel, s3c24xx_audio_buffdone);
@@ -246,6 +226,28 @@ static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) if (!prtd->params) return 0;
+ /* channel needs configuring for mem=>device, increment memory addr, + * sync to pclk, half-word transfers to the IIS-FIFO. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | + S3C2410_DISRCC_APB, prtd->params->dma_addr); + + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size, + S3C2410_DCON_SYNC_PCLK | + S3C2410_DCON_HANDSHAKE); + } else { + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size, + S3C2410_DCON_HANDSHAKE | + S3C2410_DCON_SYNC_PCLK); + + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_HW, 0x3, + prtd->params->dma_addr); + } + /* flush the DMA channel */ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); prtd->dma_loaded = 0;
From: Graeme Gregory graeme@openmoko.com
Signed-off-by: Graeme Gregory graeme@openmoko.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/s3c24xx/s3c24xx-i2s.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 files changed, 38 insertions(+), 0 deletions(-)
diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index cd89c41..fee7925 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -75,6 +75,10 @@ static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = { struct s3c24xx_i2s_info { void __iomem *regs; struct clk *iis_clk; + u32 iiscon; + u32 iismod; + u32 iisfcon; + u32 iispsr; }; static struct s3c24xx_i2s_info s3c24xx_i2s;
@@ -405,6 +409,38 @@ static int s3c24xx_i2s_probe(struct platform_device *pdev) return 0; }
+#ifdef CONFIG_PM +int s3c24xx_i2s_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); + + clk_disable(s3c24xx_i2s.iis_clk); + + return 0; +} + +int s3c24xx_i2s_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + clk_enable(s3c24xx_i2s.iis_clk); + + writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); + + return 0; +} +#else +#define s3c24xx_i2s_suspend NULL +#define s3c24xx_i2s_resume NULL +#endif + + #define S3C24XX_I2S_RATES \ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ @@ -415,6 +451,8 @@ struct snd_soc_cpu_dai s3c24xx_i2s_dai = { .id = 0, .type = SND_SOC_DAI_I2S, .probe = s3c24xx_i2s_probe, + .suspend = s3c24xx_i2s_suspend, + .resume = s3c24xx_i2s_resume, .playback = { .channels_min = 2, .channels_max = 2,
From: Joe Sauer jsauer@vernier.com
Signed-off-by: Joe Sauer jsauer@vernier.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/wm9712.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 986b5d5..111c266 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -102,7 +102,7 @@ SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), -SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), @@ -131,7 +131,7 @@ SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
-SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 0), +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), @@ -145,8 +145,8 @@ SOC_ENUM("Bass Control", wm9712_enum[5]), SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), -SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 0), -SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1), SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
From: Liam Girdwood liam@llanelli.wolfsonmicro.main
Added a device level dapm event so that both the machine and codec are informed when dapm events occur.
Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 1 + include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 23 +++++++++++------------ sound/soc/soc-dapm.c | 23 +++++++++++++++++++++++ 4 files changed, 38 insertions(+), 12 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 2b1ae8e..db02159 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -199,6 +199,7 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev); /* dapm events */ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream, int event); +int snd_soc_dapm_device_event(struct snd_soc_device *socdev, int event);
/* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); diff --git a/include/sound/soc.h b/include/sound/soc.h index f47ef1f..000f3e7 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -426,6 +426,9 @@ struct snd_soc_machine { int (*resume_pre)(struct platform_device *pdev); int (*resume_post)(struct platform_device *pdev);
+ /* callbacks */ + int (*dapm_event)(struct snd_soc_machine *, int event); + /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; int num_links; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e6a67b5..aaf141c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -289,15 +289,16 @@ static void close_delayed_work(struct work_struct *work) if (codec_dai->pop_wait == 1) {
codec_dai->pop_wait = 0; - snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, + snd_soc_dapm_stream_event(codec, + codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP);
/* power down the codec power domain if no longer active */ if (codec->active == 0) { dbg("pop wq D3 %s %s\n", codec->name, codec_dai->playback.stream_name); - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D3hot); } } } @@ -353,12 +354,12 @@ static int soc_codec_close(struct snd_pcm_substream *substream) } else { /* capture streams can be powered down now */ snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); + codec_dai->capture.stream_name, + SND_SOC_DAPM_STREAM_STOP);
- if (codec->active == 0 && codec_dai->pop_wait == 0){ - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); - } + if (codec->active == 0 && codec_dai->pop_wait == 0) + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D3hot); }
mutex_unlock(&pcm_mutex); @@ -433,8 +434,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) /* no delayed work - do we need to power up codec */ if (codec->dapm_state != SNDRV_CTL_POWER_D0) {
- if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D1); + snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D1);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_stream_event(codec, @@ -445,8 +445,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START);
- if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D0); + snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D0); if (codec_dai->dai_ops.digital_mute) codec_dai->dai_ops.digital_mute(codec_dai, 0);
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 29a546f..0a9d192 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1280,6 +1280,29 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
/** + * snd_soc_dapm_device_event - send a device event to the dapm core + * @socdev: audio device + * @event: device event + * + * Sends a device event to the dapm core. The core then makes any + * necessary machine or codec power changes.. + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_device_event(struct snd_soc_device *socdev, int event) +{ + struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_machine *machine = socdev->machine; + + if (machine->dapm_event) + machine->dapm_event(machine, event); + if (codec->dapm_event) + codec->dapm_event(codec, event); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_device_event); + +/** * snd_soc_dapm_set_endpoint - set audio endpoint status * @codec: audio codec * @endpoint: audio signal endpoint (or start point)
From: Philipp Zabel philipp.zabel@gmail.com
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 24 ++++++++++++++--- include/sound/soc.h | 58 +++++++++++++++++++++++++++++++++------- sound/soc/soc-core.c | 66 ++++++++++++++++++++++++++------------------- sound/soc/soc-dapm.c | 14 +++++---- 4 files changed, 114 insertions(+), 48 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index db02159..335d73c 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -131,18 +131,34 @@ .shift = wshift, .invert = winvert}
/* dapm kcontrol types */ -#define SOC_DAPM_SINGLE(xname, reg, shift, mask, invert) \ +#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, \ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ - .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) } -#define SOC_DAPM_DOUBLE(xname, reg, shift_left, shift_right, mask, invert, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_DAPM_DOUBLE(xname, reg, shift_left, shift_right, max, invert, \ power) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw, \ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) |\ - ((mask) << 16) | ((invert) << 24) } + ((max) << 16) | ((invert) << 24) } +#define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_DAPM_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, \ + power, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \ + .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) |\ + ((max) << 16) | ((invert) << 24) } #define SOC_DAPM_ENUM(xname, xenum) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ diff --git a/include/sound/soc.h b/include/sound/soc.h index 000f3e7..c69b58c 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -27,27 +27,53 @@ /* * Convenience kcontrol builders */ -#define SOC_SINGLE_VALUE(reg,shift,mask,invert) ((reg) | ((shift) << 8) |\ - ((shift) << 12) | ((mask) << 16) | ((invert) << 24)) -#define SOC_SINGLE_VALUE_EXT(reg,mask,invert) ((reg) | ((mask) << 16) |\ +#define SOC_SINGLE_VALUE(reg, shift, max, invert) ((reg) | ((shift) << 8) |\ + ((shift) << 12) | ((max) << 16) | ((invert) << 24)) +#define SOC_SINGLE_VALUE_EXT(reg, max, invert) ((reg) | ((max) << 16) |\ ((invert) << 31)) -#define SOC_SINGLE(xname, reg, shift, mask, invert) \ +#define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ .put = snd_soc_put_volsw, \ - .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) } -#define SOC_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ + .put = snd_soc_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } +#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ .put = snd_soc_put_volsw, \ .private_value = (reg) | ((shift_left) << 8) | \ - ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) } -#define SOC_DOUBLE_R(xname, reg_left, reg_right, shift, mask, invert) \ + ((shift_right) << 12) | ((max) << 16) | ((invert) << 24) } +#define SOC_DOUBLE_R(xname, reg_left, reg_right, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ .private_value = (reg_left) | ((shift) << 8) | \ - ((mask) << 12) | ((invert) << 20) | ((reg_right) << 24) } + ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } +#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ + .put = snd_soc_put_volsw, \ + .private_value = (reg) | ((shift_left) << 8) | \ + ((shift_right) << 12) | ((max) << 16) | ((invert) << 24) } +#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ + .private_value = (reg_left) | ((shift) << 8) | \ + ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .mask = xmask, .texts = xtexts } @@ -105,9 +131,21 @@ #define SND_SOC_DAIFMT_GATED (1 << 4) /* clock is gated when not Tx/Rx */
/* + * DAI Sync + * Synchronous LR (Left Right) clocks and Frame signals. + */ +#define SND_SOC_DAIFMT_SYNC (0 << 5) /* Tx FRM = Rx FRM */ +#define SND_SOC_DAIFMT_ASYNC (1 << 5) /* Tx FRM ~ Rx FRM */ + +/* + * TDM + */ +#define SND_SOC_DAIFMT_TDM (1 << 6) + +/* * DAI hardware signal inversions */ -#define SND_SOC_DAIFMT_NB_NF (0 << 8) /* normal bit clock + frame */ +#define SND_SOC_DAIFMT_NB_NF (0 << 8) /* normal bclk + frm */ #define SND_SOC_DAIFMT_NB_IF (1 << 8) /* normal bclk + inv frm */ #define SND_SOC_DAIFMT_IB_NF (2 << 8) /* invert bclk + nor frm */ #define SND_SOC_DAIFMT_IB_IF (3 << 8) /* invert bclk + frm */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index aaf141c..265bd55 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1214,7 +1214,6 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, memcpy(&template, _template, sizeof(template)); if (long_name) template.name = long_name; - template.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; template.index = 0;
return snd_ctl_new1(&template, data); @@ -1349,13 +1348,16 @@ EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext); int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = kcontrol->private_value; + int max = kcontrol->private_value; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); @@ -1372,15 +1374,18 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f;
- uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw); @@ -1401,7 +1406,8 @@ int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01;
ucontrol->value.integer.value[0] = @@ -1411,10 +1417,10 @@ int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, (snd_soc_read(codec, reg) >> rshift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; if (shift != rshift) ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; }
return 0; @@ -1437,25 +1443,24 @@ int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01; - int err; unsigned short val, val2, val_mask;
val = (ucontrol->value.integer.value[0] & mask); if (invert) - val = mask - val; + val = max - val; val_mask = mask << shift; val = val << shift; if (shift != rshift) { val2 = (ucontrol->value.integer.value[1] & mask); if (invert) - val2 = mask - val2; + val2 = max - val2; val_mask |= mask << rshift; val |= val2 << rshift; } - err = snd_soc_update_bits(codec, reg, val_mask, val); - return err; + return snd_soc_update_bits(codec, reg, val_mask, val); } EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
@@ -1472,13 +1477,16 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); @@ -1499,7 +1507,8 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int reg2 = (kcontrol->private_value >> 24) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + int mask = (1<<fls(max))-1; int invert = (kcontrol->private_value >> 20) & 0x01;
ucontrol->value.integer.value[0] = @@ -1508,9 +1517,9 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, (snd_soc_read(codec, reg2) >> shift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; }
return 0; @@ -1533,7 +1542,8 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int reg2 = (kcontrol->private_value >> 24) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 20) & 0x01; int err; unsigned short val, val2, val_mask; @@ -1543,8 +1553,8 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, val2 = (ucontrol->value.integer.value[1] & mask);
if (invert) { - val = mask - val; - val2 = mask - val2; + val = max - val; + val2 = max - val2; }
val = val << shift; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 0a9d192..d5d3c17 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1019,8 +1019,9 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; int invert = (kcontrol->private_value >> 24) & 0x01; + int mask = (1 << fls(max)) - 1;
/* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) { @@ -1035,10 +1036,10 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, (snd_soc_read(widget->codec, reg) >> rshift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; if (shift != rshift) ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; }
return 0; @@ -1061,7 +1062,8 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01; unsigned short val, val2, val_mask; int ret; @@ -1069,13 +1071,13 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, val = (ucontrol->value.integer.value[0] & mask);
if (invert) - val = mask - val; + val = max - val; val_mask = mask << shift; val = val << shift; if (shift != rshift) { val2 = (ucontrol->value.integer.value[1] & mask); if (invert) - val2 = mask - val2; + val2 = max - val2; val_mask |= mask << rshift; val |= val2 << rshift; }
From: Liam Girdwood liam@localhost.localdomain
--- sound/soc/soc-core.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 265bd55..f3e1b74 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -288,6 +288,14 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) {
+ /* power down the codec to D1 if no longer active */ + if (codec->active == 0) { + dbg("pop wq D1 %s %s\n", codec->name, + codec_dai->playback.stream_name); + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D1); + } + codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name,
From: Liam Girdwood liam@localhost.localdomain
This fixes a bug whereby PCMs were not being suspended when the rest of the audio subsystem was suspended. --- include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 5 +++++ 2 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index c69b58c..e32fa2f 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -448,6 +448,9 @@ struct snd_soc_dai_link {
/* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec); + + /* DAI pcm */ + struct snd_pcm *pcm; };
/* SoC machine */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f3e1b74..7c41212 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -646,6 +646,10 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) dai->dai_ops.digital_mute(dai, 1); }
+ /* suspend all pcms */ + for (i = 0; i < machine->num_links; i++) + snd_pcm_suspend_all(machine->dai_link[i].pcm); + if (machine->suspend_pre) machine->suspend_pre(pdev, state);
@@ -880,6 +884,7 @@ static int soc_new_pcm(struct snd_soc_device *socdev, return ret; }
+ dai_link->pcm = pcm; pcm->private_data = rtd; soc_pcm_ops.mmap = socdev->platform->pcm_ops->mmap; soc_pcm_ops.pointer = socdev->platform->pcm_ops->pointer;
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Milan plzik milan.plzik@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- sound/soc/soc-dapm.c | 5 +++-- 1 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d5d3c17..e8d7fae 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -692,7 +692,7 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, return 0; }
-/* test and update the power status of a mixer widget */ +/* test and update the power status of a mixer or switch widget */ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int reg, int val_mask, int val, int invert) @@ -700,7 +700,8 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_soc_dapm_path *path; int found = 0;
- if (widget->id != snd_soc_dapm_mixer) + if (widget->id != snd_soc_dapm_mixer && + widget->id != snd_soc_dapm_switch) return -ENODEV;
if (!snd_soc_test_bits(widget->codec, reg, val_mask, val))
From: Liam Girdwood liam@localhost.localdomain
--- sound/soc/soc-dapm.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index e8d7fae..4a5bd85 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1298,9 +1298,9 @@ int snd_soc_dapm_device_event(struct snd_soc_device *socdev, int event) struct snd_soc_machine *machine = socdev->machine;
if (machine->dapm_event) - machine->dapm_event(machine, event); + machine->dapm_event(machine, event); if (codec->dapm_event) - codec->dapm_event(codec, event); + codec->dapm_event(codec, event); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_device_event);
From: Liam Girdwood lg@opensource.wolfsonmicro.com
Signed-off-by: Laim Girdwood lg@opensource.wolfsonmicro.com --- include/sound/soc-dapm.h | 2 +- sound/soc/soc-dapm.c | 40 +++++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 335d73c..0df89bb 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -289,7 +289,7 @@ struct snd_soc_dapm_widget {
/* external events */ unsigned short event_flags; /* flags to specify event types */ - int (*event)(struct snd_soc_dapm_widget*, int); + int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
/* kcontrols that relate to this widget */ int num_kcontrols; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4a5bd85..a4ce882 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -524,11 +524,13 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue;
if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); if (ret < 0) return ret; } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); if (ret < 0) return ret; } @@ -539,11 +541,13 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue;
if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, SND_SOC_DAPM_POST_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); if (ret < 0) return ret; } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, SND_SOC_DAPM_POST_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret; } @@ -567,26 +571,30 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (power) { /* power up event */ if (w->event_flags & SND_SOC_DAPM_PRE_PMU) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); if (ret < 0) return ret; } dapm_update_bits(w); if (w->event_flags & SND_SOC_DAPM_POST_PMU){ - ret = w->event(w, SND_SOC_DAPM_POST_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); if (ret < 0) return ret; } } else { /* power down event */ if (w->event_flags & SND_SOC_DAPM_PRE_PMD) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); if (ret < 0) return ret; } dapm_update_bits(w); if (w->event_flags & SND_SOC_DAPM_POST_PMD) { - ret = w->event(w, SND_SOC_DAPM_POST_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret; } @@ -1096,13 +1104,17 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, dapm_mixer_update_power(widget, kcontrol, reg, val_mask, val, invert); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, SND_SOC_DAPM_PRE_REG); - if (ret < 0) + ret = widget->event(widget, kcontrol, + SND_SOC_DAPM_PRE_REG); + if (ret < 0) { + ret = 1; goto out; + } } ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, SND_SOC_DAPM_POST_REG); + ret = widget->event(widget, kcontrol, + SND_SOC_DAPM_POST_REG); } else ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
@@ -1177,13 +1189,15 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, dapm_mux_update_power(widget, kcontrol, mask, mux, e); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, SND_SOC_DAPM_PRE_REG); + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_PRE_REG); if (ret < 0) goto out; } ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, SND_SOC_DAPM_POST_REG); + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_POST_REG); } else ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
--- include/sound/soc.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index e32fa2f..1c22931 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -22,7 +22,7 @@ #include <sound/control.h> #include <sound/ac97_codec.h>
-#define SND_SOC_VERSION "0.13.1" +#define SND_SOC_VERSION "0.13.2"
/* * Convenience kcontrol builders
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Marc Mulcahy marc@levelstar.com Signed-off-by: Graeme Gregory gg@opensource.wolfsonmicro.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/Kconfig | 5 +- sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm9713.c | 1289 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm9713.h | 53 ++ 4 files changed, 1348 insertions(+), 1 deletions(-) create mode 100644 sound/soc/codecs/wm9713.c create mode 100644 sound/soc/codecs/wm9713.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7824880..e0675ef 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -18,6 +18,10 @@ config SND_SOC_WM9712 tristate depends on SND_SOC
+config SND_SOC_WM9713 + tristate + depends on SND_SOC + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate @@ -36,4 +40,3 @@ config SND_SOC_CS4270_HWMUTE config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 - diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 7ad78e3..f64eb07 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,6 +3,7 @@ snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm9712-objs := wm9712.o +snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o @@ -10,4 +11,5 @@ obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o +obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c new file mode 100644 index 0000000..7eaa034 --- /dev/null +++ b/sound/soc/codecs/wm9713.c @@ -0,0 +1,1289 @@ +/* + * wm9713.c -- ALSA Soc WM9713 codec support + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 4th Feb 2006 Initial version. + * + * Features:- + * + * o Support for AC97 Codec, Voice DAC and Aux DAC + * o Support for DAPM + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "wm9713.h" + +#define WM9713_VERSION "0.15" + +struct wm9713_priv { + u32 pll_in; /* PLL input frequency */ + u32 pll_out; /* PLL output frequency */ +}; + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * WM9713 register cache + * Reg 0x3c bit 15 is used by touch driver. + */ +static const u16 wm9713_reg[] = { + 0x6174, 0x8080, 0x8080, 0x8080, + 0xc880, 0xe808, 0xe808, 0x0808, + 0x00da, 0x8000, 0xd600, 0xaaa0, + 0xaaa0, 0xaaa0, 0x0000, 0x0000, + 0x0f0f, 0x0040, 0x0000, 0x7f00, + 0x0405, 0x0410, 0xbb80, 0xbb80, + 0x0000, 0xbb80, 0x0000, 0x4523, + 0x0000, 0x2000, 0x7eff, 0xffff, + 0x0000, 0x0000, 0x0080, 0x0000, + 0x0000, 0x0000, 0xfffe, 0xffff, + 0x0000, 0x0000, 0x0000, 0xfffe, + 0x4000, 0x0000, 0x0000, 0x0000, + 0xb032, 0x3e00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0006, + 0x0001, 0x0000, 0x574d, 0x4c13, + 0x0000, 0x0000, 0x0000 +}; + +/* virtual HP mixers regs */ +#define HPL_MIXER 0x80 +#define HPR_MIXER 0x82 +#define MICB_MUX 0x82 + +static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; +static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; +static const char *wm9713_rec_src[] = + {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker", + "Mono Out", "Zh"}; +static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv", + "Mono Vmid", "Inv Vmid"}; +static const char *wm9713_spk_pga[] = + {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid", + "Speaker Vmid", "Inv Vmid"}; +static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone", + "Headphone Vmid"}; +static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"}; +static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"}; +static const char *wm9713_dac_inv[] = + {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone", + "Headphone Mono", "NC", "Vmid"}; +static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"}; +static const char *wm9713_micb_select[] = {"MPB", "MPA"}; + +static const struct soc_enum wm9713_enum[] = { +SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/ +SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ +SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ +SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ +SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */ +}; + +static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = { +SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1), +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1), +SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), +SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1), +SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), + +SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), +SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), + +SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9713_enum[5]), +SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0), +SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), + +SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1), +SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), +SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9713_enum[6]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9713_enum[17]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), + +SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0), +SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), + +SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1), +SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0), +SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1), + +SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1), +SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0), +SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1), + +SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1), + +SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1), +SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1), +SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1), + +SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1), +SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1), +SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1), +SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1), +SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1), + +SOC_ENUM("Bass Control", wm9713_enum[16]), +SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), +SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), +SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), +}; + +/* add non dapm controls */ +static int wm9713_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm9713_snd_ac97_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path using the current + * register map, thus we add a new (virtual) register to help determine the + * audio route within the device. + */ +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 l, r, beep, tone, phone, rec, pcm, aux; + + l = ac97_read(w->codec, HPL_MIXER); + r = ac97_read(w->codec, HPR_MIXER); + beep = ac97_read(w->codec, AC97_PC_BEEP); + tone = ac97_read(w->codec, AC97_MASTER_TONE); + phone = ac97_read(w->codec, AC97_PHONE); + rec = ac97_read(w->codec, AC97_REC_SEL); + pcm = ac97_read(w->codec, AC97_PCM); + aux = ac97_read(w->codec, AC97_AUX); + + if (event & SND_SOC_DAPM_PRE_REG) + return 0; + if (l & 0x1 || r & 0x1) + ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); + else + ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + + if (l & 0x2 || r & 0x2) + ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff); + else + ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000); + + if (l & 0x4 || r & 0x4) + ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); + else + ac97_write(w->codec, AC97_PHONE, phone | 0x8000); + + if (l & 0x8 || r & 0x8) + ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff); + else + ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000); + + if (l & 0x10 || r & 0x10) + ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); + else + ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + + if (l & 0x20 || r & 0x20) + ac97_write(w->codec, AC97_AUX, aux & 0x7fff); + else + ac97_write(w->codec, AC97_AUX, aux | 0x8000); + + return 0; +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0), +}; + +/* headphone capture mux */ +static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[1]); + +/* headphone mic mux */ +static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[0]); + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1), +SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1), +SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1), +}; + +/* mono mic mux */ +static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[2]); + +/* mono output mux */ +static const struct snd_kcontrol_new wm9713_mono_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[7]); + +/* speaker left output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[8]); + +/* speaker right output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[9]); + +/* headphone left output mux */ +static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[10]); + +/* headphone right output mux */ +static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[11]); + +/* Out3 mux */ +static const struct snd_kcontrol_new wm9713_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[12]); + +/* Out4 mux */ +static const struct snd_kcontrol_new wm9713_out4_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[13]); + +/* DAC inv mux 1 */ +static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[14]); + +/* DAC inv mux 2 */ +static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[15]); + +/* Capture source left */ +static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[3]); + +/* Capture source right */ +static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[4]); + +/* mic source */ +static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[18]); + +/* mic source B virtual control */ +static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[19]); + +static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = { +SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_rec_mux_controls), +SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_mic_mux_controls), +SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mic_mux_controls), +SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mux_controls), +SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkl_mux_controls), +SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkr_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out3_mux_controls), +SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out4_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv1_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv2_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcl_mux_controls), +SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcr_mux_controls), +SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, + &wm9713_mic_sel_mux_controls), +SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, + &wm9713_micb_sel_mux_controls), +SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, + &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, + &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, + &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, + &wm9713_speaker_mixer_controls[0], + ARRAY_SIZE(wm9713_speaker_mixer_controls)), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), +SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_EXTENDED_MID, 5, 1), +SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_EXTENDED_MID, 4, 1), +SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1), +SND_SOC_DAPM_OUTPUT("MONO"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKL"), +SND_SOC_DAPM_OUTPUT("SPKR"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_INPUT("LINEL"), +SND_SOC_DAPM_INPUT("LINER"), +SND_SOC_DAPM_INPUT("MONOIN"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2A"), +SND_SOC_DAPM_INPUT("MIC2B"), +SND_SOC_DAPM_VMID("VMID"), +}; + +static const char *audio_map[][3] = { + /* left HP mixer */ + {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Left HP Mixer", NULL, "Capture Headphone Mux"}, + + /* right HP mixer */ + {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Right HP Mixer", NULL, "Capture Headphone Mux"}, + + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + {"Line Mixer", NULL, "Right Line In"}, + {"Line Mixer", NULL, "Left Line In"}, + {"HP Mixer", NULL, "Left HP Mixer"}, + {"HP Mixer", NULL, "Right HP Mixer"}, + {"Capture Mixer", NULL, "Left Capture Source"}, + {"Capture Mixer", NULL, "Right Capture Source"}, + + /* speaker mixer */ + {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"}, + + /* mono mixer */ + {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Mono Mixer", NULL, "Capture Mono Mux"}, + + /* DAC inv mux 1 */ + {"DAC Inv Mux 1", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"}, + + /* DAC inv mux 2 */ + {"DAC Inv Mux 2", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"}, + + /* headphone left mux */ + {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"}, + + /* headphone right mux */ + {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"}, + + /* speaker left mux */ + {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"}, + {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* speaker right mux */ + {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"}, + {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"}, + + /* mono mux */ + {"Mono Out Mux", "Mono", "Mono Mixer"}, + {"Mono Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* out 3 mux */ + {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"}, + + /* out 4 mux */ + {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Out Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Out Mux"}, + {"OUT3", NULL, "Out 3"}, + {"Out 3", NULL, "Out 3 Mux"}, + {"OUT4", NULL, "Out 4"}, + {"Out 4", NULL, "Out 4 Mux"}, + {"SPKL", NULL, "Left Speaker"}, + {"Left Speaker", NULL, "Left Speaker Out Mux"}, + {"SPKR", NULL, "Right Speaker"}, + {"Right Speaker", NULL, "Right Speaker Out Mux"}, + {"MONO", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono Out Mux"}, + + /* input pga */ + {"Left Line In", NULL, "LINEL"}, + {"Right Line In", NULL, "LINER"}, + {"Mono In", NULL, "MONOIN"}, + {"Mic A PGA", NULL, "Mic A Pre Amp"}, + {"Mic B PGA", NULL, "Mic B Pre Amp"}, + + /* left capture select */ + {"Left Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Left Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Left Capture Source", "Line", "LINEL"}, + {"Left Capture Source", "Mono In", "MONOIN"}, + {"Left Capture Source", "Headphone", "Left HP Mixer"}, + {"Left Capture Source", "Speaker", "Speaker Mixer"}, + {"Left Capture Source", "Mono Out", "Mono Mixer"}, + + /* right capture select */ + {"Right Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Right Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Right Capture Source", "Line", "LINER"}, + {"Right Capture Source", "Mono In", "MONOIN"}, + {"Right Capture Source", "Headphone", "Right HP Mixer"}, + {"Right Capture Source", "Speaker", "Speaker Mixer"}, + {"Right Capture Source", "Mono Out", "Mono Mixer"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Source"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Source"}, + + /* mic */ + {"Mic A Pre Amp", NULL, "Mic A Source"}, + {"Mic A Source", "Mic 1", "MIC1"}, + {"Mic A Source", "Mic 2 A", "MIC2A"}, + {"Mic A Source", "Mic 2 B", "Mic B Source"}, + {"Mic B Pre Amp", "MPB", "Mic B Source"}, + {"Mic B Source", NULL, "MIC2B"}, + + /* headphone capture */ + {"Capture Headphone Mux", "Stereo", "Capture Mixer"}, + {"Capture Headphone Mux", "Left", "Left Capture Source"}, + {"Capture Headphone Mux", "Right", "Right Capture Source"}, + + /* mono capture */ + {"Capture Mono Mux", "Stereo", "Capture Mixer"}, + {"Capture Mono Mux", "Left", "Left Capture Source"}, + {"Capture Mono Mux", "Right", "Right Capture Source"}, + + {NULL, NULL, NULL}, +}; + +static int wm9713_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm9713_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm9713_dapm_widgets[i]); + + /* set up audio path audio_mapnects */ + for (i = 0; audio_map[i][0] != NULL; i++) + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || + reg == AC97_CD) + return soc_ac97_ops.read(codec->ac97, reg); + else { + reg = reg >> 1; + + if (reg > (ARRAY_SIZE(wm9713_reg))) + return -EIO; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + if (reg < 0x7c) + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg <= (ARRAY_SIZE(wm9713_reg))) + cache[reg] = val; + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 divsel:1; + u32 divctl:1; + u32 lf:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the PLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + + /* The the PLL output is always 98.304MHz. */ + target = 98304000; + + /* If the input frequency is over 14.4MHz then scale it down. */ + if (source > 14400000) { + source >>= 1; + pll_div->divsel = 1; + + if (source > 14400000) { + source >>= 1; + pll_div->divctl = 1; + } else + pll_div->divctl = 0; + + } else { + pll_div->divsel = 0; + pll_div->divctl = 0; + } + + /* Low frequency sources require an additional divide in the + * loop. + */ + if (source < 8192000) { + pll_div->lf = 1; + target >>= 2; + } else + pll_div->lf = 0; + + Ndiv = target / source; + if ((Ndiv < 5) || (Ndiv > 12)) + printk(KERN_WARNING + "WM9713 PLL N value %d out of recommended range!\n", + Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +/** + * Please note that changing the PLL input frequency may require + * resynchronisation with the AC97 controller. + */ +static int wm9713_set_pll(struct snd_soc_codec *codec, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm9713_priv *wm9713 = codec->private_data; + u16 reg, reg2; + struct _pll_div pll_div; + + /* turn PLL off ? */ + if (freq_in == 0 || freq_out == 0) { + /* disable PLL power and select ext source */ + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080); + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200); + wm9713->pll_out = 0; + return 0; + } + + pll_factors(&pll_div, freq_in); + + if (pll_div.k == 0) { + reg = (pll_div.n << 12) | (pll_div.lf << 11) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } else { + /* write the fractional k to the reg 0x46 pages */ + reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + + /* K [21:20] */ + reg = reg2 | (0x5 << 4) | (pll_div.k >> 20); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [19:16] */ + reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [15:12] */ + reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [11:8] */ + reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [7:4] */ + reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } + + /* turn PLL on and select as source */ + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff); + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f); + wm9713->pll_out = freq_out; + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ + schedule_timeout_interruptible(msecs_to_jiffies(10)); + return 0; +} + +static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + return wm9713_set_pll(codec, pll_id, freq_in, freq_out); +} + +/* + * Tristate the PCM DAI lines, tristate can be disabled by calling + * wm9713_set_dai_fmt() + */ +static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, + int tristate) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff; + + if (tristate) + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + + return 0; +} + +/* + * Configure WM9713 clock dividers. + * Voice DAC needs 256 FS + */ +static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM9713_PCMCLK_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKA_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKB_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_HIFI_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_PCMBCLK_DIV: + reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff; + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div); + break; + case WM9713_PCMCLK_PLL_DIV: + reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; + ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div); + break; + case WM9713_HIFI_PLL_DIV: + reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; + ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div); + break; + default: + return -EINVAL; + } + + return 0; +}; + +static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5; + u16 reg = 0x8000; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + reg |= 0x4000; + gpio |= 0x0010; + break; + case SND_SOC_DAIFMT_CBM_CFS: + reg |= 0x6000; + gpio |= 0x0018; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg |= 0x0200; + gpio |= 0x001a; + break; + case SND_SOC_DAIFMT_CBS_CFM: + gpio |= 0x0012; + break; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + reg |= 0x00c0; + break; + case SND_SOC_DAIFMT_IB_NF: + reg |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + reg |= 0x0040; + break; + } + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + reg |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + reg |= 0x0043; + break; + } + + ac97_write(codec, AC97_GPIO_CFG, gpio); + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x000c; + break; + } + + /* enable PCM interface in master mode */ + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 status; + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; + ac97_write(codec, AC97_HANDSET_RATE, 0x0280); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); + ac97_write(codec, AC97_EXTENDED_MID, status); +} + +static int ac97_hifi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg; + u16 vra; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return ac97_write(codec, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 vra, xsle; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + xsle = ac97_read(codec, AC97_PCI_SID); + ac97_write(codec, AC97_PCI_SID, xsle | 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_codec_dai wm9713_dai[] = { +{ + .name = "AC97 HiFi", + .type = SND_SOC_DAI_AC97_BUS, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_hifi_prepare,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll,}, + }, + { + .name = "AC97 Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_aux_prepare,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll,}, + }, + { + .name = "WM9713 Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = WM9713_PCM_FORMATS,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = WM9713_PCM_FORMATS,}, + .ops = { + .hw_params = wm9713_pcm_hw_params, + .shutdown = wm9713_voiceshutdown,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, + .set_fmt = wm9713_set_dai_fmt, + .set_tristate = wm9713_set_dai_tristate, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm9713_dai); + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (!(ac97_read(codec, 0) & 0x8000)) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (ac97_read(codec, 0) & 0x8000) + return -EIO; + return 0; +} +EXPORT_SYMBOL_GPL(wm9713_reset); + +static int wm9713_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* enable thermal shutdown */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* enable master bias and vmid */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* disable everything including AC link */ + ac97_write(codec, AC97_EXTENDED_MID, 0xffff); + ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); + ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->dapm_state = event; + return 0; +} + +static int wm9713_soc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm9713_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm9713_priv *wm9713 = codec->private_data; + int i, ret; + u16 *cache = codec->reg_cache; + + ret = wm9713_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "could not reset AC97 codec\n"); + return ret; + } + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* do we need to re-start the PLL ? */ + if (wm9713->pll_out) + wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out); + + /* only synchronise the codec if warm reset failed */ + if (ret == 0) { + for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) { + if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || + i == AC97_EXTENDED_MSTATUS || i > 0x66) + continue; + soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); + } + } + + if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D0); + + return ret; +} + +static int wm9713_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0, reg; + + printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(wm9713_reg); + codec->reg_cache_step = 2; + + codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + codec->name = "WM9713"; + codec->owner = THIS_MODULE; + codec->dai = wm9713_dai; + codec->num_dai = ARRAY_SIZE(wm9713_dai); + codec->write = ac97_write; + codec->read = ac97_read; + codec->dapm_event = wm9713_dapm_event; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) + goto codec_err; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + wm9713_reset(codec, 0); + ret = wm9713_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* unmute the adc - move to kcontrol */ + reg = ac97_read(codec, AC97_CD) & 0x7fff; + ac97_write(codec, AC97_CD, reg); + + wm9713_add_controls(codec); + wm9713_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) + goto reset_err; + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->private_data); + +priv_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int wm9713_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->private_data); + kfree(codec->reg_cache); + kfree(codec->dai); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm9713 = { + .probe = wm9713_soc_probe, + .remove = wm9713_soc_remove, + .suspend = wm9713_soc_suspend, + .resume = wm9713_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713); + +MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h new file mode 100644 index 0000000..d357b6c --- /dev/null +++ b/sound/soc/codecs/wm9713.h @@ -0,0 +1,53 @@ +/* + * wm9713.h -- WM9713 Soc Audio driver + */ + +#ifndef _WM9713_H +#define _WM9713_H + +/* clock inputs */ +#define WM9713_CLKA_PIN 0 +#define WM9713_CLKB_PIN 1 + +/* clock divider ID's */ +#define WM9713_PCMCLK_DIV 0 +#define WM9713_CLKA_MULT 1 +#define WM9713_CLKB_MULT 2 +#define WM9713_HIFI_DIV 3 +#define WM9713_PCMBCLK_DIV 4 +#define WM9713_PCMCLK_PLL_DIV 5 +#define WM9713_HIFI_PLL_DIV 6 + +/* Calculate the appropriate bit mask for the external PCM clock divider */ +#define WM9713_PCMDIV(x) ((x - 1) << 8) + +/* Calculate the appropriate bit mask for the external HiFi clock divider */ +#define WM9713_HIFIDIV(x) ((x - 1) << 12) + +/* MCLK clock mulitipliers */ +#define WM9713_CLKA_X1 (0 << 1) +#define WM9713_CLKA_X2 (1 << 1) +#define WM9713_CLKB_X1 (0 << 2) +#define WM9713_CLKB_X2 (1 << 2) + +/* MCLK clock MUX */ +#define WM9713_CLK_MUX_A (0 << 0) +#define WM9713_CLK_MUX_B (1 << 0) + +/* Voice DAI BCLK divider */ +#define WM9713_PCMBCLK_DIV_1 (0 << 9) +#define WM9713_PCMBCLK_DIV_2 (1 << 9) +#define WM9713_PCMBCLK_DIV_4 (2 << 9) +#define WM9713_PCMBCLK_DIV_8 (3 << 9) +#define WM9713_PCMBCLK_DIV_16 (4 << 9) + +#define WM9713_DAI_AC97_HIFI 0 +#define WM9713_DAI_AC97_AUX 1 +#define WM9713_DAI_PCM_VOICE 2 + +extern struct snd_soc_codec_device soc_codec_dev_wm9713; +extern struct snd_soc_codec_dai wm9713_dai[3]; + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm); + +#endif
From: Liam Girdwood liam@localhost.localdomain
--- sound/soc/codecs/wm8753.c | 10 ++++++++-- 1 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index efced93..8f2a6d1 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -48,6 +48,7 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include <asm/div64.h>
#include "wm8753.h" @@ -258,6 +259,11 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, return 1; }
+static const unsigned int rec_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0,7, TLV_DB_LINEAR_ITEM(-1500, 600), +}; + static const struct snd_kcontrol_new wm8753_snd_controls[] = { SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0),
@@ -287,8 +293,8 @@ SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8753_enum[2]),
-SOC_DOUBLE("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1), -SOC_SINGLE("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1), +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, rec_mix_tlv),
SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),
From: Richard Purdie richard@openedhand.com
Signed-off-by: Richard Purdie richard@openedhand.com Signed-off-by: Milan Plzik milan.plzik@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ak4535.c | 716 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ak4535.h | 46 +++ 5 files changed, 769 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/ak4535.c create mode 100644 sound/soc/codecs/ak4535.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 88c8140..5ced329 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -121,6 +121,7 @@ #define I2C_DRIVERID_LM4857 92 /* LM4857 Audio Amplifier */ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ +#define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e0675ef..8244107 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2,6 +2,10 @@ config SND_SOC_AC97_CODEC tristate depends on SND_SOC
+config SND_SOC_AK4535 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f64eb07..6859145 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ak4535-objs := ak4535.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -7,6 +8,7 @@ snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c new file mode 100644 index 0000000..0db47ef --- /dev/null +++ b/sound/soc/codecs/ak4535.c @@ -0,0 +1,716 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie richard@openedhand.com + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ak4535.h" + +#define AUDIO_NAME "ak4535" +#define AK4535_VERSION "0.3" + +#define AK4535_DEBUG 0 +#if AK4535_DEBUG +#else +#define ak4535dbg_dump(s) if (0) {} +#endif + +struct snd_soc_codec_device soc_codec_dev_ak4535; + +/* codec private data */ +struct ak4535_priv { + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const u16 ak4535_reg[AK4535_CACHEREGNUM] = { + 0x0000, 0x0080, 0x0000, 0x0003, + 0x0002, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0040, 0x0036, 0x0010, + 0x0000, 0x0000, 0x0057, 0x0000, +}; + +/* + * read ak4535 register cache + */ +static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak4535_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 data; + data = reg; + + if (codec->hw_write(codec->control_data, &data, 1) != 1) + return -EIO; + + if (codec->hw_read(codec->control_data, &data, 1) != 1) + return -EIO; + + return data; +}; + +/* + * write ak4535 register cache + */ +static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK4535 register space + */ +static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4535 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak4535_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static int ak4535_sync(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i, r=0; + + for (i = 0; i < AK4535_CACHEREGNUM; i++) + r |= ak4535_write (codec, i, cache[i]); + + ak4535dbg_dump (codec); + + return r; +}; + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* add non dapm controls */ +static int ak4535_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4535_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[4]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_input_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0), /* speaker powersave bit */ + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const char *audio_map[][3] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", NULL, "Line Out Enable"}, + {"ROUT", NULL, "Line Out Enable"}, + {"Line Out Enable", "Switch", "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* left HP */ + {"HPL", NULL, "Left HP Enable"}, + {"Left HP Enable", "Switch", "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", NULL, "Right HP Enable"}, + {"Right HP Enable", "Switch", "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", NULL, "Speaker Enable"}, + {"SPN", NULL, "Speaker Enable"}, + {"Speaker Enable", "Switch", "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int ak4535_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(ak4535_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &ak4535_dapm_widgets[i]); + } + + /* set up audio path audio_map interconnects */ + for(i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak4535_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ak4535_priv *ak4535 = codec->private_data; + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ak4535_priv *ak4535 = codec->private_data; + u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + ak4535_write(codec, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + ak4535_write(codec, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf; + if (!mute) + ak4535_write(codec, AK4535_DAC, mute_reg); + else + ak4535_write(codec, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 i; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + ak4535_mute(codec->dai, 0); + /* vref/mid, clk and osc on, dac unmute, active */ + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + ak4535_mute(codec->dai, 1); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + i = ak4535_read_reg_cache (codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i|0x80); + i = ak4535_read_reg_cache (codec, AK4535_PM2); + ak4535_write(codec, AK4535_PM2, i & (~0x80)); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, inactive */ + i = ak4535_read_reg_cache (codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i & (~0x80)); + break; + } + codec->dapm_state = event; + ak4535dbg_dump (codec); + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +struct snd_soc_codec_dai ak4535_dai = { + .name = "AK4535", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = ak4535_hw_params, + }, + .dai_ops = { + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(ak4535_dai); + +static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int ak4535_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + ak4535_sync(codec); + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + ak4535_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the AK4535 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4535_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "AK4535"; + codec->owner = THIS_MODULE; + codec->read = ak4535_read_reg_cache; + codec->write = ak4535_write; + codec->dapm_event = ak4535_dapm_event; + codec->dai = &ak4535_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ak4535_reg); + codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + ak4535_add_controls(codec); + ak4535_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + + return ret; +} + +static struct snd_soc_device *ak4535_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver ak4535_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int ak4535_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = ak4535_socdev; + struct ak4535_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = ak4535_init(socdev); + if (ret < 0) { + printk(KERN_ERR "failed to initialise AK4535\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int ak4535_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int ak4535_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, ak4535_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "AK4535 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_AK4535, + .attach_adapter = ak4535_i2c_attach, + .detach_client = ak4535_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "AK4535", + .driver = &ak4535_i2c_driver, +}; +#endif + +static int ak4535_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ak4535_setup_data *setup; + struct snd_soc_codec* codec; + struct ak4535_priv *ak4535; + int ret = 0; + + printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL); + if (ak4535 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak4535; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ak4535_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->hw_read = (hw_read_t)i2c_master_recv; + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int ak4535_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec* codec = socdev->codec; + + if (codec->control_data) + ak4535_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&ak4535_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4535 = { + .probe = ak4535_probe, + .remove = ak4535_remove, + .suspend = ak4535_suspend, + .resume = ak4535_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h new file mode 100644 index 0000000..612164e --- /dev/null +++ b/sound/soc/codecs/ak4535.h @@ -0,0 +1,46 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie richard@openedhand.com + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#define AK4535_CACHEREGNUM 0x10 + +struct ak4535_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai ak4535_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4535; + +#endif
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Nicola Perrino nicola.perrino@atlab.it Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320.c | 603 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tlv320.h | 111 +++++++++ 5 files changed, 721 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/tlv320.c create mode 100644 sound/soc/codecs/tlv320.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 5ced329..f6a1ddf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -122,6 +122,7 @@ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ +#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8244107..2608499 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -6,6 +6,10 @@ config SND_SOC_AK4535 tristate depends on SND_SOC
+config SND_SOC_TLV320 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 6859145..3cce68b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,5 +1,6 @@ snd-soc-ac97-objs := ac97.o snd-soc-ak4535-objs := ak4535.o +snd-soc-tlv320-objs := tlv320.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -9,6 +10,7 @@ snd-soc-cs4270-objs := cs4270.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_TLV320) += snd-soc-tlv320.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/tlv320.c b/sound/soc/codecs/tlv320.c new file mode 100644 index 0000000..58c7c3e --- /dev/null +++ b/sound/soc/codecs/tlv320.c @@ -0,0 +1,603 @@ +/* + * tlv320.c -- TLV 320 ALSA Soc Audio driver + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com + * Nicola Perrino nicola.perrino@atlab.it + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "tlv320.h" + +#define AUDIO_NAME "tlv320" +#define TLV320_VERSION "0.1" + +/* + * Debug + */ + +//#define TLV320_DEBUG 0 + +#ifdef TLV320_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + + +#define TLV320_VOICE_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + + +#define TLV320_VOICE_BITS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + + +static int caps_charge = 2000; +static int setting = 1; +module_param(caps_charge, int, 0); +module_param(setting, int, 0); +MODULE_PARM_DESC(caps_charge, "TLV320 cap charge time (msecs)"); + +static struct workqueue_struct *tlv320_workq = NULL; +//static struct work_struct tlv320_dapm_work; + +/* codec private data */ +struct tlv320_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + + +#ifdef TLV320AIC24K +/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x01, /* CONTROL REG 1 */ + 0x02, /* CONTROL REG 2 */ + 0x03, /* CONTROL REG 3A */ + 0x03, /* CONTROL REG 3B */ + 0x03, /* CONTROL REG 3C */ + 0x03, /* CONTROL REG 3D */ + 0x04, /* CONTROL REG 4 */ + 0x04, /* CONTROL REG 4 Bis */ + 0x05, /* CONTROL REG 5A */ + 0x05, /* CONTROL REG 5B */ + 0x05, /* CONTROL REG 5C */ + 0x05, /* CONTROL REG 5D */ + 0x06, /* CONTROL REG 6A */ + 0x06, /* CONTROL REG 6B */ +}; + +/* + * DATA case digital SET1: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * IN = HNSI (MIC) + * OUT = HDSO (SPKG) + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set1[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x49, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x01, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x81, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x00, /* CONTROL REG 5A */ + 0x40,//(0dB) /* CONTROL REG 5B */ + 0xbf, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x02,//(HNSI) /* CONTROL REG 6A */ + 0x81 //(HDSO) /* CONTROL REG 6B */ +}; + +/* + * DATA case digital SET2: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * IN = HDSI (PHONE IN) + * OUT = HNSO (PHONE OUT) + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set2[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x49, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x01, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x81, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x00, /* CONTROL REG 5A */ + 0x52,//(-27dB) /* CONTROL REG 5B */ + 0xbf, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x01,//(PHONE IN) /* CONTROL REG 6A */ + 0x82 //(PHONE OUT) /* CONTROL REG 6B */ +}; + +/* + * DATA case analog: + * ADC, DAC, SSP off + * Headset input to output (HDSI2O -> 1) + * Handset input to output (HNSI2O -> 1) + * Usage: room monitor + */ +static const unsigned char tlv320_reg_data_init_set3[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x08, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x11, /* CONTROL REG 3A */ + 0x40, /* CONTROL REG 3B */ + 0x80, /* CONTROL REG 3C */ + 0xc0, /* CONTROL REG 3D */ + 0x00, /* CONTROL REG 4 */ + 0x00, /* CONTROL REG 5A */ + 0x40, /* CONTROL REG 5B */ + 0x80, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x60, /* CONTROL REG 6A */ + 0x80 /* CONTROL REG 6B */ +}; + +#else // TLV320AIC14k + +/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x01, /* CONTROL REG 1 */ + 0x02, /* CONTROL REG 2 */ + 0x03, /* CONTROL REG 3 */ + 0x04, /* CONTROL REG 4 */ + 0x04, /* CONTROL REG 4 Bis */ + 0x05, /* CONTROL REG 5A */ + 0x05, /* CONTROL REG 5B */ + 0x05, /* CONTROL REG 5C */ + 0x05, /* CONTROL REG 5D */ + 0x06 /* CONTROL REG 6 */ +}; + +/* + * DATA case digital: + * SSP -> DAC -> OUT + * IN -> ADC -> SSP + * Usage: playback, capture streams + */ +static const unsigned char tlv320_reg_data_init_set1[] = { + 0x00, /* CONTROL REG 0 No Operation */ + 0x41, /* CONTROL REG 1 */ + 0x20, /* CONTROL REG 2 */ + 0x09, /* CONTROL REG 3 */ + 0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */ + 0x88,//0x90, /* CONTROL REG 4 Bis */ + 0x2A, /* CONTROL REG 5A */ + 0x6A, /* CONTROL REG 5B */ + 0xbc, /* CONTROL REG 5C */ + 0xc0, /* CONTROL REG 5D */ + 0x00 /* CONTROL REG 6 */ +}; +#endif +/* + * read tlv320 register cache + */ +static inline unsigned int tlv320_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + if (reg > ARRAY_SIZE(tlv320_reg_addr)) + return -1; + return cache[reg]; +} + +/* + * write tlv320 register cache + */ +static inline void tlv320_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u8 *cache = codec->reg_cache; + if (reg > ARRAY_SIZE(tlv320_reg_addr)) + return; + cache[reg] = value; +} + +/* + * read tlv320 + */ +static int tlv320_read (struct snd_soc_codec *codec, u8 reg) +{ + return i2c_smbus_read_byte_data(codec->control_data, reg); +} + +/* + * write tlv320 + */ +static int tlv320_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + if (tlv320_reg_addr[reg] > 0x06) + return -1; + + tlv320_write_reg_cache (codec, reg, value); + + return i2c_smbus_write_byte_data(codec->control_data, tlv320_reg_addr[reg], value); +} + + +/* + * write block tlv320 + */ +static int tlv320_write_block (struct snd_soc_codec *codec, + const u8 *data, unsigned int len) +{ + int ret = -1; + int i; + + for (i=0; i<len; i++) { + dbg("addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], data[i]); + if ((ret = tlv320_write(codec, i, data[i])) < 0) + break; + } + + return ret; +} + + +static int tlv320_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + dbg("tlv320_set_dai_fmt enter"); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int tlv320_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + dbg("tlv320_pcm_hw_params enter"); + return 0; +} + + +static int tlv320_config_pcm_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + dbg("tlv320_config_pcm_sysclk enter"); + return 0; +} + + +/* + * Voice over PCM DAI + */ +struct snd_soc_codec_dai tlv320_dai[] = { +{ .name = "TLV320 Voice", + .id = 1, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TLV320_VOICE_RATES, + .formats = TLV320_VOICE_BITS,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TLV320_VOICE_RATES, + .formats = TLV320_VOICE_BITS,}, + .ops = { + .hw_params = tlv320_pcm_hw_params,}, + .dai_ops = { + .digital_mute = NULL, + .set_fmt = tlv320_set_dai_fmt, + .set_clkdiv = NULL, + .set_pll = NULL, + .set_sysclk = tlv320_config_pcm_sysclk, + }, +}, + + +}; +EXPORT_SYMBOL_GPL(tlv320_dai); + + +static void tlv320_work(struct work_struct *work) +{ +#if 0 + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + //wm8753_dapm_event(codec, codec->dapm_state); +#endif +} + +/* + * initialise the TLV320 driver + * register the mixer and dsp interfaces with the kernel + */ +static int tlv320_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "TLV320"; + codec->owner = THIS_MODULE; + codec->read = tlv320_read_reg_cache; + codec->write = tlv320_write; + codec->dai = tlv320_dai; + codec->num_dai = ARRAY_SIZE(tlv320_dai); + codec->reg_cache_size = sizeof(tlv320_reg_addr); + + codec->reg_cache = + kmemdup(tlv320_reg_addr, sizeof(tlv320_reg_addr), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + kfree(codec->reg_cache); + return ret; + } + + queue_delayed_work(tlv320_workq, + &codec->delayed_work, msecs_to_jiffies(caps_charge)); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + } + + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *tlv320_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver tlv320_i2c_driver; +static struct i2c_client client_template; + +static int tlv320_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = tlv320_socdev; + struct tlv320_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret, len; + const unsigned char *data; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = tlv320_init(socdev); + if (ret < 0) { + err("failed to initialise TLV320\n"); + goto err; + } + + switch(setting) { + case 1: + data = tlv320_reg_data_init_set1; + len = sizeof(tlv320_reg_data_init_set1); + break; + case 2: + data = tlv320_reg_data_init_set2; + len = sizeof(tlv320_reg_data_init_set2); + break; + case 3: + data = tlv320_reg_data_init_set3; + len = sizeof(tlv320_reg_data_init_set3); + break; + default: + data = tlv320_reg_data_init_set1; + len = sizeof(tlv320_reg_data_init_set1); + break; + } + + ret = tlv320_write_block(codec, data, len); + + if (ret < 0) { + err("attach error: init status %d\n", ret); + } else { + info("attach: chip tlv320 at address 0x%02x", + tlv320_read(codec, 0x02) << 1); + } + + //tlv320_write(codec, CODEC_REG6B, 0x80); +#if 0 + int value; + int i; + + for (i=0; i<len; i++) { + value = tlv320_read(codec, tlv320_reg_addr[i]); + dbg("read addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], value); + mdelay(10); + } + +#endif + + + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int tlv320_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int tlv320_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, tlv320_codec_probe); +} + +/* tlv320 i2c codec control layer */ +static struct i2c_driver tlv320_i2c_driver = { + .driver = { + .name = "tlv320 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_TLV320, + .attach_adapter = tlv320_i2c_attach, + .detach_client = tlv320_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "tlv320", + .driver = &tlv320_i2c_driver, +}; +#endif + +static int tlv320_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct tlv320_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + struct tlv320_priv *tlv320; + + info("TLV320 Audio Codec %s", TLV320_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + tlv320 = kzalloc(sizeof(struct tlv320_priv), GFP_KERNEL); + if (tlv320 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = tlv320; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + tlv320_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, tlv320_work); + tlv320_workq = create_workqueue("tlv320"); + if (tlv320_workq == NULL) { + kfree(codec); + return -ENOMEM; + } +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&tlv320_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int tlv320_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (tlv320_workq) + destroy_workqueue(tlv320_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_tlv320 = { + .probe = tlv320_probe, + .remove = tlv320_remove, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320); + +MODULE_DESCRIPTION("ASoC TLV320 driver"); +MODULE_AUTHOR("Nicola Perrino"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320.h b/sound/soc/codecs/tlv320.h new file mode 100644 index 0000000..bbcd53d --- /dev/null +++ b/sound/soc/codecs/tlv320.h @@ -0,0 +1,111 @@ +/* + * tlv320.h -- TLV 320 ALSA Soc Audio driver + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com + * Nicola Perrino nicola.perrino@atlab.it + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _TLV320_H +#define _TLV320_H + +#define TLV320AIC24K + + +/* TLV320 register space */ +#define CODEC_NOOP 0x00 +#define CODEC_REG1 0x01 +#define CODEC_REG2 0x02 +#define CODEC_REG3A 0x03 +#define CODEC_REG3B 0x04 +#define CODEC_REG3C 0x05 +#define CODEC_REG3D 0x06 +#define CODEC_REG4A 0x07 +#define CODEC_REG4B 0x08 +#define CODEC_REG5A 0x09 +#define CODEC_REG5B 0x0a +#define CODEC_REG5C 0x0b +#define CODEC_REG5D 0x0c +#define CODEC_REG6A 0x0d +#define CODEC_REG6B 0x0e + + +// Control Register 1 +#define REG1_CONTINUOUS 0x40 +#define REG1_IIR_EN 0x20 +#define REG1_MIC_BIAS_235 0x08 +#define REG1_ANALOG_LOOP_BACK 0x04 +#define REG1_DIGITAL_LOOP_BACK 0x02 +#define REG1_DAC16 0x01 + +// Control Register 2 +#define REG2_TURBO_EN 0x80 +#define REG2_FIR_BYPASS 0x40 +#define REG2_GPIO 0x02 +#define REG2_GPIO_1 0x06 + +// Control Register 3A +#define REG3_PWDN_ALL 0x30 +#define REG3_PWDN_ADC 0x10 +#define REG3_PWDN_DAC 0x20 +#define REG3_SW_RESET 0x08 +#define REG3_SAMPLING_FACTOR1 0x01 +#define REG3_SAMPLING_FACTOR2 0x02 + +// Control Register 3B +#define REG3_8KBP_EN 0x60 +#define REG3_MUTE_OUTP1 0x42 +#define REG3_MUTE_OUTP2 0x48 +#define REG3_MUTE_OUTP3 0x44 + +// Control Register 4 +#define REG4_FSDIV_M 0x85 //M=5 +#define REG4_FSDIV_NP 0x08 //N=1, P=8 +//#define REG4_FSDIV_NP 0x01 //N=1, P=8 +#define REG4_FSDIV_NP1 0x02 //N=16, P=2 + +// Control Register 5 +#define REG5A_ADC_GAIN 0x02 //3dB +#define REG5A_ADC_MUTE 0x0f //Mute +#define REG5B_DAC_GAIN 0x42 //-3dB +#define REG5B_DAC_MUTE 0x4f //Mute +#define REG5C_SIDETONE_MUTE 0xBF + +// Control Register 6 +#define REG6A_AIC24A_CH1_IN 0x08 //INP1 to ADC +#define REG6B_AIC24A_CH1_OUT 0x82 //OUTP2 to DAC +#define REG6A_AIC24A_CH2_IN 0x02 //INP2 to ADC +#define REG6B_AIC24A_CH2_OUT 0x81 //OUTP3 to DAC + +/* clock inputs */ +#define TLV320_MCLK 0 +#define TLV320_PCMCLK 1 + + +struct tlv320_setup_data { + unsigned short i2c_address; +}; + +/* DAI ifmodes */ +/* mode 1 IFMODE = 00 */ +#define TLV320_DAI_MODE1_VOICE 0 +#define TLV320_DAI_MODE1_HIFI 1 +/* mode 2 IFMODE = 01 */ +#define TLV320_DAI_MODE2_VOICE 2 +/* mode 3 IFMODE = 10 */ +#define TLV320_DAI_MODE3_HIFI 3 +/* mode 4 IFMODE = 11 */ +#define TLV320_DAI_MODE4_HIFI 4 + +extern struct snd_soc_codec_dai tlv320_dai[5]; +extern struct snd_soc_codec_device soc_codec_dev_tlv320; + +#endif
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Giorgio Padrin giorgio@mandarinlogiq.org Signed-off-by: Richard Purdie richard@openedhand.com Signed-off-by: Philipp Zabel philipp.zabel@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/uda1380.c | 740 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/uda1380.h | 89 ++++++ 5 files changed, 836 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/uda1380.c create mode 100644 sound/soc/codecs/uda1380.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index f6a1ddf..85472a1 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -123,6 +123,7 @@ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ #define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */ +#define I2C_DRIVERID_UDA1380 98 /* Philips UDA1380 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2608499..4c05f60 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -10,6 +10,10 @@ config SND_SOC_TLV320 tristate depends on SND_SOC
+config SND_SOC_UDA1380 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3cce68b..bce9fe0 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,7 @@ snd-soc-ac97-objs := ac97.o snd-soc-ak4535-objs := ak4535.o snd-soc-tlv320-objs := tlv320.o +snd-soc-uda1380-objs := uda1380.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -11,6 +12,7 @@ snd-soc-cs4270-objs := cs4270.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_TLV320) += snd-soc-tlv320.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c new file mode 100644 index 0000000..0fa9832 --- /dev/null +++ b/sound/soc/codecs/uda1380.c @@ -0,0 +1,740 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Modified by Richard Purdie richard@openedhand.com to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin giorgio@mandarinlogiq.org + * Copyright 2005 Openedhand Ltd. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "uda1380.h" + +#define UDA1380_VERSION "0.5" +#define AUDIO_NAME "uda1380" +/* + * Debug + */ + +#define UDA1380_DEBUG 0 + +#ifdef UDA1380_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache (codec, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!codec->active && (reg >= UDA1380_MVOL)) + return 0; + dbg("uda hw write %x val %x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) { + unsigned int val; + i2c_master_send(codec->control_data, data, 1); + i2c_master_recv(codec->control_data, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + dbg("READ BACK VAL %x\n", (data[0]<<8) | data[1]); + return -EIO; + } + return 0; + } else + return -EIO; +} + +#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz", + "96kHz"}; +static const char *uda1380_input_sel[] = {"Line", "Mic"}; +static const char *uda1380_output_sel[] = {"Direct", "Mixer"}; +static const char *uda1380_spf_mode[] = {"Flat", "Minimum1", "Minimum2", + "Maximum"}; + +static const struct soc_enum uda1380_enum[] = { + SOC_ENUM_DOUBLE(UDA1380_DEEMP, 0, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_ADC, 3, 2, uda1380_input_sel), + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode), + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel), /* R02_EN_AVC */ +}; + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE("Playback Volume", UDA1380_MVOL, 0, 8, 255, 1), + SOC_DOUBLE("Mixer Volume", UDA1380_MIXVOL, 0, 8, 255, 1), + SOC_ENUM("Sound Processing Filter Mode", uda1380_enum[2]), + SOC_DOUBLE("Treble Volume", UDA1380_MODE, 4, 12, 3, 0), + SOC_DOUBLE("Bass Volume", UDA1380_MODE, 0, 8, 15, 0), + SOC_ENUM("Playback De-emphasis", uda1380_enum[0]), + SOC_DOUBLE("Capture Volume", UDA1380_DEC, 0, 8, 127, 0), + SOC_DOUBLE("Line Capture Volume", UDA1380_PGA, 0, 8, 15, 0), + SOC_SINGLE("Mic Capture Volume", UDA1380_PGA, 8, 11, 0), + SOC_DOUBLE("Playback Switch", UDA1380_DEEMP, 3, 11, 1, 1), + SOC_SINGLE("Capture Switch", UDA1380_PGA, 15, 1, 0), + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), + SOC_SINGLE("Silence", UDA1380_MIXER, 7, 1, 0), + SOC_SINGLE("Silence Detection", UDA1380_MIXER, 6, 1, 0), +}; + +/* add non dapm controls */ +static int uda1380_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Input Select", uda1380_enum[1]); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Output Select", uda1380_enum[3]); + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const char *audio_map[][3] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "Direct", "DAC"}, + {"Output Mux", "Mixer", "Analog Mixer"}, + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", NULL, "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int uda1380_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(uda1380_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &uda1380_dapm_widgets[i]); + + /* set up audio path interconnects */ + for (i = 0; audio_map[i][0] != NULL; i++) + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int uda1380_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + /* set up DAI based upon fmt */ + + iface = uda1380_read_reg_cache (codec, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_I2S; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +/* + * Flush reg cache + * We can only write the interpolator and decimator registers + * when the DAI is being clocked by the CPU DAI. It's up to the + * machine and cpu DAI driver to do this before we are called. + */ +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg, reg_start, reg_end, clk; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg_start = UDA1380_MVOL; + reg_end = UDA1380_MIXER; + } else { + reg_start = UDA1380_DEC; + reg_end = UDA1380_AGC; + } + + /* FIXME disable DAC_CLK */ + clk = uda1380_read_reg_cache (codec, 00); + uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + + for (reg = reg_start; reg <= reg_end; reg++ ) { + dbg("reg %x val %x\n",reg, uda1380_read_reg_cache (codec, reg)); + uda1380_write(codec, reg, uda1380_read_reg_cache (codec, reg)); + } + + /* FIXME enable DAC_CLK */ + uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + + return 0; +} + +static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(codec, UDA1380_CLK, clk); +} + +static int uda1380_mute(struct snd_soc_codec_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & 0xbfff; + + /* FIXME: mute(codec,0) is called when the magician clock is already + * set to WSPLL, but for some unknown reason writing to interpolator + * registers works only when clocked by SYSCLK */ + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); + if (mute) + uda1380_write(codec, UDA1380_DEEMP, mute_reg | 0x4000); + else + uda1380_write(codec, UDA1380_DEEMP, mute_reg); + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static int uda1380_dapm_event(struct snd_soc_codec *codec, int event) +{ + int pm = uda1380_read_reg_cache(codec, UDA1380_PM); + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + /* enable internal bias */ + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except internal bias */ + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, inactive */ + uda1380_write(codec, UDA1380_PM, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_codec_dai uda1380_dai[] = { +{ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{/* playback only - dual interface */ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* capture only - dual interface*/ + .name = "UDA1380", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .set_fmt = uda1380_set_dai_fmt, + }, +}, +}; +EXPORT_SYMBOL_GPL(uda1380_dai); + +static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int uda1380_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + uda1380_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the UDA1380 driver + * register the mixer and dsp interfaces with the kernel + */ +static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->dapm_event = uda1380_dapm_event; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache_size = sizeof(uda1380_reg); + codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + uda1380_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if(ret < 0) { + printk(KERN_ERR "uda1380: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + /* set clock input */ + switch (dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write(codec, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK); + break; + } + + /* uda1380 init */ + uda1380_add_controls(codec); + uda1380_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "uda1380: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *uda1380_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver uda1380_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int uda1380_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = uda1380_socdev; + struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL){ + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = uda1380_init(socdev, setup->dac_clk); + if (ret < 0) { + printk(KERN_ERR "failed to initialise UDA1380\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int uda1380_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec* codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int uda1380_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, uda1380_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_UDA1380, + .attach_adapter = uda1380_i2c_attach, + .detach_client = uda1380_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "UDA1380", + .driver = &uda1380_i2c_driver, +}; +#endif + +static int uda1380_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct uda1380_setup_data *setup; + struct snd_soc_codec* codec; + int ret = 0; + + printk(KERN_INFO "UDA1380 Audio Codec %s", UDA1380_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + uda1380_socdev = socdev; +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec* codec = socdev->codec; + + if (codec->control_data) + uda1380_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&uda1380_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h new file mode 100644 index 0000000..f9d885c --- /dev/null +++ b/sound/soc/codecs/uda1380.h @@ -0,0 +1,89 @@ +/* + * Audio support for Philips UDA1380 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2005 Giorgio Padrin giorgio@mandarinlogiq.org + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +struct uda1380_setup_data { + unsigned short i2c_address; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ +#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ +#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ + +extern struct snd_soc_codec_dai uda1380_dai[3]; +extern struct snd_soc_codec_device soc_codec_dev_uda1380; + +#endif /* _UDA1380_H */
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com Signed-off-by: Nicola Perrino nicola.perrino@atlab.it Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- sound/soc/pxa/Kconfig | 4 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/pxa2xx-ssp.c | 666 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/pxa/pxa2xx-ssp.h | 43 +++ 4 files changed, 715 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/pxa2xx-ssp.c create mode 100644 sound/soc/pxa/pxa2xx-ssp.h
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index a83e229..ca825e6 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -18,6 +18,10 @@ config SND_PXA2XX_SOC_AC97 config SND_PXA2XX_SOC_I2S tristate
+config SND_PXA2XX_SOC_SSP + tristate + select PXA_SSP + config SND_PXA2XX_SOC_CORGI tristate "SoC Audio support for Sharp Zaurus SL-C7x0" depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 78e0d6b..c631651 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -2,10 +2,12 @@ snd-soc-pxa2xx-objs := pxa2xx-pcm.o snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o +snd-soc-pxa2xx-ssp-objs := pxa2xx-ssp.o
obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o +obj-$(CONFIG_SND_PXA2XX_SOC_SSP) += snd-soc-pxa2xx-ssp.o
# PXA Machine Support snd-soc-corgi-objs := corgi.o diff --git a/sound/soc/pxa/pxa2xx-ssp.c b/sound/soc/pxa/pxa2xx-ssp.c new file mode 100644 index 0000000..0d483fe --- /dev/null +++ b/sound/soc/pxa/pxa2xx-ssp.c @@ -0,0 +1,666 @@ +/* + * pxa2xx-ssp.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 12th Aug 2005 Initial version. + * + * TODO: + * o Test network mode for > 16bit sample size + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/audio.h> +#include <asm/arch/ssp.h> + +#include "pxa2xx-pcm.h" +#include "pxa2xx-ssp.h" + +#define PXA_SSP_DEBUG 0 + +#if PXA_SSP_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +/* + * SSP audio private data + */ +struct ssp_priv { + unsigned int sysclk; +}; + +static struct ssp_priv ssp_clk[3]; +static struct ssp_dev ssp[3]; +#ifdef CONFIG_PM +static struct ssp_state ssp_state[3]; +#endif + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_mono_out = { + .name = "SSP1 PCM Mono out", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRTXSSDR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_mono_in = { + .name = "SSP1 PCM Mono in", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRRXSSDR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_stereo_out = { + .name = "SSP1 PCM Stereo out", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRTXSSDR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp1_pcm_stereo_in = { + .name = "SSP1 PCM Stereo in", + .dev_addr = __PREG(SSDR_P1), + .drcmr = &DRCMRRXSSDR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_mono_out = { + .name = "SSP2 PCM Mono out", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRTXSS2DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_mono_in = { + .name = "SSP2 PCM Mono in", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRRXSS2DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_stereo_out = { + .name = "SSP2 PCM Stereo out", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRTXSS2DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp2_pcm_stereo_in = { + .name = "SSP2 PCM Stereo in", + .dev_addr = __PREG(SSDR_P2), + .drcmr = &DRCMRRXSS2DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_mono_out = { + .name = "SSP3 PCM Mono out", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRTXSS3DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_mono_in = { + .name = "SSP3 PCM Mono in", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRRXSS3DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH2, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_stereo_out = { + .name = "SSP3 PCM Stereo out", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRTXSS3DR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params pxa2xx_ssp3_pcm_stereo_in = { + .name = "SSP3 PCM Stereo in", + .dev_addr = __PREG(SSDR_P3), + .drcmr = &DRCMRRXSS3DR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST16 | DCMD_WIDTH4, +}; + +static struct pxa2xx_pcm_dma_params *ssp_dma_params[3][4] = { + {&pxa2xx_ssp1_pcm_mono_out, &pxa2xx_ssp1_pcm_mono_in, + &pxa2xx_ssp1_pcm_stereo_out,&pxa2xx_ssp1_pcm_stereo_in,}, + {&pxa2xx_ssp2_pcm_mono_out, &pxa2xx_ssp2_pcm_mono_in, + &pxa2xx_ssp2_pcm_stereo_out, &pxa2xx_ssp2_pcm_stereo_in,}, + {&pxa2xx_ssp3_pcm_mono_out, &pxa2xx_ssp3_pcm_mono_in, + &pxa2xx_ssp3_pcm_stereo_out,&pxa2xx_ssp3_pcm_stereo_in,}, +}; + +static int pxa2xx_ssp_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + if (!rtd->dai->cpu_dai->active) { + ret = ssp_init (&ssp[cpu_dai->id], cpu_dai->id + 1, + SSP_NO_IRQ); + if (ret < 0) + return ret; + ssp_disable(&ssp[cpu_dai->id]); + } + return ret; +} + +static void pxa2xx_ssp_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + + if (!cpu_dai->active) { + ssp_disable(&ssp[cpu_dai->id]); + ssp_exit(&ssp[cpu_dai->id]); + } +} + +#if defined (CONFIG_PXA27x) +static int cken[3] = {CKEN23_SSP1, CKEN3_SSP2, CKEN4_SSP3}; +#else +static int cken[3] = {CKEN3_SSP, CKEN9_NSSP, CKEN10_ASSP}; +#endif + +#ifdef CONFIG_PM + +static int pxa2xx_ssp_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if (!dai->active) + return 0; + + ssp_save_state(&ssp[dai->id], &ssp_state[dai->id]); + pxa_set_cken(cken[dai->id], 0); + return 0; +} + +static int pxa2xx_ssp_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + if (!dai->active) + return 0; + + pxa_set_cken(cken[dai->id], 1); + ssp_restore_state(&ssp[dai->id], &ssp_state[dai->id]); + ssp_enable(&ssp[dai->id]); + + return 0; +} + +#else +#define pxa2xx_ssp_suspend NULL +#define pxa2xx_ssp_resume NULL +#endif + +/* + * Set the SSP ports SYSCLK. + */ +static int pxa2xx_ssp_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + int port = cpu_dai->id + 1; + u32 sscr0 = SSCR0_P(port) & + ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ADC); + + dbg("pxa2xx_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d", + cpu_dai->id, clk_id, freq); + + switch (clk_id) { + case PXA2XX_SSP_CLK_NET_PLL: + sscr0 |= SSCR0_MOD; + case PXA2XX_SSP_CLK_PLL: + /* Internal PLL is fixed on pxa25x and pxa27x */ +#ifdef CONFIG_PXA27x + ssp_clk[cpu_dai->id].sysclk = 13000000; +#else + ssp_clk[cpu_dai->id].sysclk = 1843200; +#endif + break; + case PXA2XX_SSP_CLK_EXT: + ssp_clk[cpu_dai->id].sysclk = freq; + sscr0 |= SSCR0_ECS; + break; + case PXA2XX_SSP_CLK_NET: + ssp_clk[cpu_dai->id].sysclk = freq; + sscr0 |= SSCR0_NCS | SSCR0_MOD; + break; + case PXA2XX_SSP_CLK_AUDIO: + ssp_clk[cpu_dai->id].sysclk = 0; + SSCR0_P(port) |= SSCR0_SerClkDiv(1); + sscr0 |= SSCR0_ADC; + break; + default: + return -ENODEV; + } + + /* the SSP CKEN clock must be disabled when changing SSP clock mode */ + pxa_set_cken(cken[cpu_dai->id], 0); + SSCR0_P(port) |= sscr0; + pxa_set_cken(cken[cpu_dai->id], 1); + return 0; +} + +/* + * Set the SSP clock dividers. + */ +static int pxa2xx_ssp_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + int port = cpu_dai->id + 1; + + switch (div_id) { + case PXA2XX_SSP_AUDIO_DIV_ACDS: + SSACD_P(port) &= ~ 0x7; + SSACD_P(port) |= SSACD_ACDS(div); + break; + case PXA2XX_SSP_AUDIO_DIV_SCDB: + SSACD_P(port) &= ~0x8; + if (div == PXA2XX_SSP_CLK_SCDB_1) + SSACD_P(port) |= SSACD_SCDB; + break; + case PXA2XX_SSP_DIV_SCR: + SSCR0_P(port) &= ~SSCR0_SCR; + SSCR0_P(port) |= SSCR0_SerClkDiv(div); + break; + default: + return -ENODEV; + } + + return 0; +} + +/* + * Configure the PLL frequency pxa27x and (afaik - pxa320 only) + */ +static int pxa2xx_ssp_set_dai_pll(struct snd_soc_cpu_dai *cpu_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + int port = cpu_dai->id + 1; + + SSACD_P(port) &= ~0x70; + switch (freq_out) { + case 5622000: + break; + case 11345000: + SSACD_P(port) |= (0x1 << 4); + break; + case 12235000: + SSACD_P(port) |= (0x2 << 4); + break; + case 14857000: + SSACD_P(port) |= (0x3 << 4); + break; + case 32842000: + SSACD_P(port) |= (0x4 << 4); + break; + case 48000000: + SSACD_P(port) |= (0x5 << 4); + break; + } + return 0; +} + +/* + * Set the active slots in TDM/Network mode + */ +static int pxa2xx_ssp_set_dai_tdm_slot(struct snd_soc_cpu_dai *cpu_dai, + unsigned int mask, int slots) +{ + int port = cpu_dai->id + 1; + + SSCR0_P(port) &= ~SSCR0_SlotsPerFrm(7); + + /* set number of active slots */ + SSCR0_P(port) |= SSCR0_SlotsPerFrm(slots); + + /* set active slot mask */ + SSTSA_P(port) = mask; + SSRSA_P(port) = mask; + return 0; +} + +/* + * Tristate the SSP DAI lines + */ +static int pxa2xx_ssp_set_dai_tristate(struct snd_soc_cpu_dai *cpu_dai, + int tristate) +{ + int port = cpu_dai->id + 1; + + if (tristate) + SSCR1_P(port) &= ~SSCR1_TTE; + else + SSCR1_P(port) |= SSCR1_TTE; + + return 0; +} + +/* + * Set up the SSP DAI format. + * The SSP Port must be inactive before calling this function as the + * physical interface format is changed. + */ +static int pxa2xx_ssp_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + int port = cpu_dai->id + 1; + + /* reset port settings */ + SSCR0_P(port) = 0; + SSCR1_P(port) = 0; + SSPSP_P(port) = 0; + + /* NOTE: I2S emulation is still very much work in progress here */ + + /* FIXME: this is what wince uses for msb */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_MSB) { + SSCR0_P(port) = SSCR0_EDSS | SSCR0_TISSP | SSCR0_DataSize(16); + goto master; + } + + /* check for I2S emulation mode - handle it separately */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) { + /* 8.4.11 */ + + /* Only SSCR0[NCS] or SSCR0[ECS] bit fields settings are optional */ + SSCR0_P(port) = SSCR0_EDSS | SSCR0_PSP | SSCR0_DataSize(16); + + /* set FIFO thresholds */ + SSCR1_P(port) = SSCR1_RxTresh(14) | SSCR1_TxTresh(1); + + /* normal: */ + /* all bit fields must be cleared except: FSRT = 1 and + * SFRMWDTH = 16, DMYSTART=0,1) */ + SSPSP_P(port) = SSPSP_FSRT | SSPSP_SFRMWDTH(16) | SSPSP_DMYSTRT(0); + goto master; + } + + SSCR0_P(port) |= SSCR0_PSP; + SSCR1_P(port) = SSCR1_RxTresh(14) | SSCR1_TxTresh(1) | + SSCR1_TRAIL | SSCR1_RWOT; + +master: + switch(fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + SSCR1_P(port) |= (SSCR1_SCLKDIR | SSCR1_SFRMDIR); + break; + case SND_SOC_DAIFMT_CBM_CFS: + SSCR1_P(port) |= SSCR1_SCLKDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + SSCR1_P(port) |= SSCR1_SFRMDIR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SSPSP_P(port) |= SSPSP_SFRMP | SSPSP_FSRT; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + SSPSP_P(port) |= SSPSP_DMYSTRT(1); + case SND_SOC_DAIFMT_DSP_B: + SSPSP_P(port) |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_MSB: + /* handled above */ + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set the SSP audio DMA parameters and sample size. + * Can be called multiple times by oss emulation. + */ +static int pxa2xx_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int dma = 0, chn = params_channels(params); + int port = cpu_dai->id + 1; + + /* select correct DMA params */ + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + dma = 1; /* capture DMA offset is 1,3 */ + if (chn == 2) + dma += 2; /* stereo DMA offset is 2, mono is 0 */ + cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma]; + + dbg("pxa2xx_ssp_hw_params: dma %d", dma); + + /* we can only change the settings if the port is not in use */ + if (SSCR0_P(port) & SSCR0_SSE) + return 0; + + /* clear selected SSP bits */ + SSCR0_P(port) &= ~(SSCR0_DSS | SSCR0_EDSS); + + /* bit size */ + switch(params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + SSCR0_P(port) |= SSCR0_DataSize(16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + SSCR0_P(port) |=(SSCR0_EDSS | SSCR0_DataSize(8)); + /* we must be in network mode (2 slots) for 24 bit stereo */ + break; + case SNDRV_PCM_FORMAT_S32_LE: + SSCR0_P(port) |= (SSCR0_EDSS | SSCR0_DataSize(16)); + /* we must be in network mode (2 slots) for 32 bit stereo */ + break; + } + + dbg("SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x", + SSCR0_P(port), SSCR1_P(port), + SSTO_P(port), SSPSP_P(port), + SSSR_P(port), SSACD_P(port)); + + return 0; +} + +static int pxa2xx_ssp_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + int port = cpu_dai->id + 1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + ssp_enable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) |= SSCR1_TSRE; + else + SSCR1_P(port) |= SSCR1_RSRE; + SSSR_P(port) |= SSSR_P(port); + break; + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) |= SSCR1_TSRE; + else + SSCR1_P(port) |= SSCR1_RSRE; + ssp_enable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) &= ~SSCR1_TSRE; + else + SSCR1_P(port) &= ~SSCR1_RSRE; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + ssp_disable(&ssp[cpu_dai->id]); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SSCR1_P(port) &= ~SSCR1_TSRE; + else + SSCR1_P(port) &= ~SSCR1_RSRE; + break; + + default: + ret = -EINVAL; + } + + dbg("SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x SSPSP 0x%08x SSSR 0x%08x", + SSCR0_P(port), SSCR1_P(port), + SSTO_P(port), SSPSP_P(port), + SSSR_P(port)); + + return ret; +} + +#define PXA2XX_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define PXA2XX_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_cpu_dai pxa_ssp_dai[] = { + { .name = "pxa2xx-ssp1", + .id = 0, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, + { .name = "pxa2xx-ssp2", + .id = 1, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, + { .name = "pxa2xx-ssp3", + .id = 2, + .type = SND_SOC_DAI_PCM, + .suspend = pxa2xx_ssp_suspend, + .resume = pxa2xx_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = PXA2XX_SSP_RATES, + .formats = PXA2XX_SSP_FORMATS,}, + .ops = { + .startup = pxa2xx_ssp_startup, + .shutdown = pxa2xx_ssp_shutdown, + .trigger = pxa2xx_ssp_trigger, + .hw_params = pxa2xx_ssp_hw_params,}, + .dai_ops = { + .set_sysclk = pxa2xx_ssp_set_dai_sysclk, + .set_clkdiv = pxa2xx_ssp_set_dai_clkdiv, + .set_pll = pxa2xx_ssp_set_dai_pll, + .set_fmt = pxa2xx_ssp_set_dai_fmt, + .set_tdm_slot = pxa2xx_ssp_set_dai_tdm_slot, + .set_tristate = pxa2xx_ssp_set_dai_tristate, + }, + }, +}; +EXPORT_SYMBOL_GPL(pxa_ssp_dai); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("pxa2xx SSP/PCM SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/pxa2xx-ssp.h b/sound/soc/pxa/pxa2xx-ssp.h new file mode 100644 index 0000000..67ef003 --- /dev/null +++ b/sound/soc/pxa/pxa2xx-ssp.h @@ -0,0 +1,43 @@ +/* + * linux/sound/arm/pxa2xx-ssp.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PXA2XX_SSP_H +#define _PXA2XX_SSP_H + +/* pxa2xx DAI SSP ID's */ +#define PXA2XX_DAI_SSP1 0 +#define PXA2XX_DAI_SSP2 1 +#define PXA2XX_DAI_SSP3 2 + +/* SSP clock sources */ +#define PXA2XX_SSP_CLK_PLL 0 +#define PXA2XX_SSP_CLK_EXT 1 +#define PXA2XX_SSP_CLK_NET 2 +#define PXA2XX_SSP_CLK_AUDIO 3 +#define PXA2XX_SSP_CLK_NET_PLL 4 + +/* SSP audio dividers */ +#define PXA2XX_SSP_AUDIO_DIV_ACDS 0 +#define PXA2XX_SSP_AUDIO_DIV_SCDB 1 +#define PXA2XX_SSP_DIV_SCR 2 + +/* SSP ACDS audio dividers values */ +#define PXA2XX_SSP_CLK_AUDIO_DIV_1 0 +#define PXA2XX_SSP_CLK_AUDIO_DIV_2 1 +#define PXA2XX_SSP_CLK_AUDIO_DIV_4 2 +#define PXA2XX_SSP_CLK_AUDIO_DIV_8 3 +#define PXA2XX_SSP_CLK_AUDIO_DIV_16 4 +#define PXA2XX_SSP_CLK_AUDIO_DIV_32 5 + +/* SSP divider bypass */ +#define PXA2XX_SSP_CLK_SCDB_4 0 +#define PXA2XX_SSP_CLK_SCDB_1 1 + +extern struct snd_soc_cpu_dai pxa_ssp_dai[3]; + +#endif
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Nicola Perrino nicola.perrino@atlab.it Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- sound/soc/pxa/Kconfig | 10 ++ sound/soc/pxa/Makefile | 3 +- sound/soc/pxa/amesom_tlv320.c | 211 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletions(-) create mode 100644 sound/soc/pxa/amesom_tlv320.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index ca825e6..bcb3aa0 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -57,3 +57,13 @@ config SND_PXA2XX_SOC_TOSA help Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa). + +config SND_PXA2XX_SOC_AMESOM_TLV320 + tristate "SoC SSP Audio support for AMESOM - TLV320AIC24k" + depends on SND_PXA2XX_SOC && MACH_AMESOM + select SND_PXA2XX_SOC_I2S + select SND_PXA2XX_SOC_SSP + select SND_SOC_TLV320 + help + Say Y if you want to add support for SoC audio on Amesom + with the tlv320. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index c631651..931bdc7 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -14,9 +14,10 @@ snd-soc-corgi-objs := corgi.o snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o +snd-soc-amesom-tlv320-objs := amesom_tlv320.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o - +obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o diff --git a/sound/soc/pxa/amesom_tlv320.c b/sound/soc/pxa/amesom_tlv320.c new file mode 100644 index 0000000..6aa1c2c --- /dev/null +++ b/sound/soc/pxa/amesom_tlv320.c @@ -0,0 +1,211 @@ +/* + * amesom_tlv320.c -- SoC audio for Amesom + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2006 Atlab srl. + * + * Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com + * Nicola Perrino nicola.perrino@atlab.it + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 5th Dec 2006 Initial version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/audio.h> + +#include "../codecs/tlv320.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + + +/* + * SSP2 GPIO's + */ + +#define GPIO11_SSP2RX_MD (11 | GPIO_ALT_FN_2_IN) +#define GPIO13_SSP2TX_MD (13 | GPIO_ALT_FN_1_OUT) +#define GPIO50_SSP2CLKS_MD (50 | GPIO_ALT_FN_3_IN) +#define GPIO14_SSP2FRMS_MD (14 | GPIO_ALT_FN_2_IN) +#define GPIO50_SSP2CLKM_MD (50 | GPIO_ALT_FN_3_OUT) +#define GPIO14_SSP2FRMM_MD (14 | GPIO_ALT_FN_2_OUT) + + +static struct snd_soc_machine amesom; + + +static int amesom_probe(struct platform_device *pdev) +{ + return 0; +} + +static int amesom_remove(struct platform_device *pdev) +{ + return 0; +} + +static int tlv320_voice_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void tlv320_voice_shutdown(struct snd_pcm_substream *substream) +{ + return; +} + +/* + * Tlv320 uses SSP port for playback. + */ +static int tlv320_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + //printk("tlv320_voice_hw_params enter\n"); + switch(params_rate(params)) { + case 8000: + //printk("tlv320_voice_hw_params 8000\n"); + break; + case 16000: + //printk("tlv320_voice_hw_params 16000\n"); + break; + default: + break; + } + + // CODEC MASTER, SSP SLAVE + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the SSP system clock as input (unused) */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_NET_PLL, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set SSP slots */ + //ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x1, slots); + ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x3, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int tlv320_voice_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_soc_ops tlv320_voice_ops = { + .startup = tlv320_voice_startup, + .shutdown = tlv320_voice_shutdown, + .hw_params = tlv320_voice_hw_params, + .hw_free = tlv320_voice_hw_free, +}; + + +static struct snd_soc_dai_link amesom_dai[] = { +{ + .name = "TLV320", + .stream_name = "TLV320 Voice", + .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP2], + .codec_dai = &tlv320_dai[TLV320_DAI_MODE1_VOICE], + .ops = &tlv320_voice_ops, +}, +}; + +static struct snd_soc_machine amesom = { + .name = "Amesom", + .probe = amesom_probe, + .remove = amesom_remove, + .dai_link = amesom_dai, + .num_links = ARRAY_SIZE(amesom_dai), +}; + +static struct tlv320_setup_data amesom_tlv320_setup = { +#ifdef TLV320AIC24K //codec2 + .i2c_address = 0x41, +#else // TLV320AIC14k + .i2c_address = 0x40, +#endif +}; + +static struct snd_soc_device amesom_snd_devdata = { + .machine = &amesom, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_tlv320, + .codec_data = &amesom_tlv320_setup, +}; + +static struct platform_device *amesom_snd_device; + +static int __init amesom_init(void) +{ + int ret; + + amesom_snd_device = platform_device_alloc("soc-audio", -1); + if (!amesom_snd_device) + return -ENOMEM; + + platform_set_drvdata(amesom_snd_device, &amesom_snd_devdata); + amesom_snd_devdata.dev = &amesom_snd_device->dev; + ret = platform_device_add(amesom_snd_device); + + if (ret) + platform_device_put(amesom_snd_device); + + + /* SSP port 2 slave */ + pxa_gpio_mode(GPIO11_SSP2RX_MD); + pxa_gpio_mode(GPIO13_SSP2TX_MD); + pxa_gpio_mode(GPIO50_SSP2CLKS_MD); + pxa_gpio_mode(GPIO14_SSP2FRMS_MD); + + return ret; +} + +static void __exit amesom_exit(void) +{ + platform_device_unregister(amesom_snd_device); +} + +module_init(amesom_init); +module_exit(amesom_exit); + +/* Module information */ +MODULE_AUTHOR("Nicola Perrino"); +MODULE_DESCRIPTION("ALSA SoC TLV320 Amesom"); +MODULE_LICENSE("GPL");
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com --- sound/soc/pxa/Kconfig | 11 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/magician.c | 539 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 552 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/magician.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index bcb3aa0..3682f38 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -58,6 +58,17 @@ config SND_PXA2XX_SOC_TOSA Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa).
+config SND_PXA2XX_SOC_MAGICIAN + tristate "SoC Audio support for HTC Magician" + depends on SND_PXA2XX_SOC + select SND_PXA2XX_SOC_I2S + select SND_PXA2XX_SOC_SSP + select SND_SOC_UDA1380 + help + Say Y if you want to add support for SoC audio on the + HTC Magician. + + config SND_PXA2XX_SOC_AMESOM_TLV320 tristate "SoC SSP Audio support for AMESOM - TLV320AIC24k" depends on SND_PXA2XX_SOC && MACH_AMESOM diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 931bdc7..1faa751 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -15,9 +15,11 @@ snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o snd-soc-amesom-tlv320-objs := amesom_tlv320.o +snd-soc-magician-objs := magician.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o +obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o \ No newline at end of file diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c new file mode 100644 index 0000000..7eb671c --- /dev/null +++ b/sound/soc/pxa/magician.c @@ -0,0 +1,539 @@ +/* + * SoC audio for HTC Magician + * + * Copyright (c) 2006 Philipp Zabel philipp.zabel@gmail.com + * + * based on spitz.c, + * Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com + * Richard Purdie richard@openedhand.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/hardware/scoop.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/hardware.h> +#include <asm/arch/magician.h> +#include <asm/arch/magician_cpld.h> +#include <asm/mach-types.h> +#include "../codecs/uda1380.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h" + +#define MAGICIAN_HP_ON 0 +#define MAGICIAN_HP_OFF 1 + +#define MAGICIAN_SPK_ON 0 +#define MAGICIAN_SPK_OFF 1 + +#define MAGICIAN_MIC 0 +#define MAGICIAN_MIC_EXT 1 + +/* + * SSP GPIO's + */ +#define GPIO23_SSPSCLK_MD (23 | GPIO_ALT_FN_2_OUT) +#define GPIO24_SSPSFRM_MD (24 | GPIO_ALT_FN_2_OUT) +#define GPIO25_SSPTXD_MD (25 | GPIO_ALT_FN_2_OUT) + +static int magician_hp_func = MAGICIAN_HP_OFF; +static int magician_spk_func = MAGICIAN_SPK_ON; +static int magician_in_sel = MAGICIAN_MIC; + +extern struct platform_device magician_cpld; + +static void magician_ext_control(struct snd_soc_codec *codec) +{ + snd_soc_dapm_set_endpoint(codec, "Speaker", + (magician_spk_func == MAGICIAN_SPK_ON)); + + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", + (magician_hp_func == MAGICIAN_HP_ON)); + + switch (magician_in_sel) { + case MAGICIAN_MIC: + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + break; + case MAGICIAN_MIC_EXT: + snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); + break; + } + snd_soc_dapm_sync_endpoints(codec); +} + +static int magician_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->codec; + + /* check the jack status at stream startup */ + magician_ext_control(codec); + + return 0; +} + +/* + * Magician uses SSP port for playback. + */ +static int magician_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int acps, acds, div4; + int ret = 0; + + /* + * Rate = SSPSCLK / (word size(16)) + * SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1) + */ + switch (params_rate(params)) { + case 8000: + acps = 32842000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_32; /* wrong - 32 bits/sample */ + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 11025: + acps = 5622000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_8; /* 16 bits/sample, 1 slot */ + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 22050: + acps = 5622000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 44100: + acps = 11345000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + case 48000: + acps = 12235000; + acds = PXA2XX_SSP_CLK_AUDIO_DIV_4; + div4 = PXA2XX_SSP_CLK_SCDB_4; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set audio clock as clock source */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_AUDIO, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set the SSP audio system clock ACDS divider */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, + PXA2XX_SSP_AUDIO_DIV_ACDS, acds); + if (ret < 0) + return ret; + + /* set the SSP audio system clock SCDB divider4 */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, + PXA2XX_SSP_AUDIO_DIV_SCDB, div4); + if (ret < 0) + return ret; + + /* set SSP audio pll clock */ + ret = cpu_dai->dai_ops.set_pll(cpu_dai, 0, 0, acps); + if (ret < 0) + return ret; + + return 0; +} + +/* + * We have to enable the SSP port early so the UDA1380 can flush + * it's register cache. The UDA1380 can only write it's interpolator and + * decimator registers when the link is running. + */ +static int magician_playback_prepare(struct snd_pcm_substream *substream) +{ + /* enable SSP clock - is this needed ? */ + SSCR0_P(1) |= SSCR0_SSE; + + /* FIXME: ENABLE I2S */ + SACR0 |= SACR0_BCKD; + SACR0 |= SACR0_ENB; + pxa_set_cken(CKEN8_I2S, 1); + + return 0; +} + +static int magician_playback_hw_free(struct snd_pcm_substream *substream) +{ + /* FIXME: DISABLE I2S */ + SACR0 &= ~SACR0_ENB; + SACR0 &= ~SACR0_BCKD; + pxa_set_cken(CKEN8_I2S, 0); + return 0; +} + +/* + * Magician uses I2S for capture. + */ +static int magician_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the I2S system clock as output */ + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +/* + * We have to enable the I2S port early so the UDA1380 can flush + * it's register cache. The UDA1380 can only write it's interpolator and + * decimator registers when the link is running. + */ +static int magician_capture_prepare(struct snd_pcm_substream *substream) +{ + SACR0 |= SACR0_ENB; + return 0; +} + +static struct snd_soc_ops magician_capture_ops = { + .startup = magician_startup, + .hw_params = magician_capture_hw_params, + .prepare = magician_capture_prepare, +}; + +static struct snd_soc_ops magician_playback_ops = { + .startup = magician_startup, + .hw_params = magician_playback_hw_params, + .prepare = magician_playback_prepare, + .hw_free = magician_playback_hw_free, +}; + +static int magician_get_jack(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_hp_func; + return 0; +} + +static int magician_set_hp(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_hp_func == ucontrol->value.integer.value[0]) + return 0; + + magician_hp_func = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_spk(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_spk_func; + return 0; +} + +static int magician_set_spk(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_spk_func == ucontrol->value.integer.value[0]) + return 0; + + magician_spk_func = ucontrol->value.integer.value[0]; + magician_ext_control(codec); + return 1; +} + +static int magician_get_input(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + ucontrol->value.integer.value[0] = magician_in_sel; + return 0; +} + +static int magician_set_input(struct snd_kcontrol * kcontrol, + struct snd_ctl_elem_value * ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (magician_in_sel == ucontrol->value.integer.value[0]) + return 0; + + magician_in_sel = ucontrol->value.integer.value[0]; + + switch (magician_in_sel) { + case MAGICIAN_MIC: + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL0); + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL1); + break; + case MAGICIAN_MIC_EXT: + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL0); + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_IN_SEL1); + } + + return 1; +} + +static int magician_spk_power(struct snd_soc_dapm_widget *w, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_SPK_POWER); + else + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_SPK_POWER); + return 0; +} + +static int magician_hp_power(struct snd_soc_dapm_widget *w, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + magician_egpio_enable(&magician_cpld, + EGPIO_NR_MAGICIAN_EP_POWER); + else + magician_egpio_disable(&magician_cpld, + EGPIO_NR_MAGICIAN_EP_POWER); + return 0; +} + +static int magician_mic_bias(struct snd_soc_dapm_widget *w, int event) +{ +// if (SND_SOC_DAPM_EVENT_ON(event)) +// magician_egpio_enable(&magician_cpld, +// EGPIO_NR_MAGICIAN_MIC_POWER); +// else +// magician_egpio_disable(&magician_cpld, +// EGPIO_NR_MAGICIAN_MIC_POWER); + return 0; +} + +/* magician machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power), + SND_SOC_DAPM_SPK("Speaker", magician_spk_power), + SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias), + SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias), +}; + +/* magician machine audio_map */ +static const char *audio_map[][3] = { + + /* Headphone connected to VOUTL, VOUTR */ + {"Headphone Jack", NULL, "VOUTL"}, + {"Headphone Jack", NULL, "VOUTR"}, + + /* Speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* Mics are connected to VINM */ + {"VINM", NULL, "Headset Mic"}, + {"VINM", NULL, "Call Mic"}, + + {NULL, NULL, NULL}, +}; + +static const char *hp_function[] = { "On", "Off" }; +static const char *spk_function[] = { "On", "Off" }; +static const char *input_select[] = { "Call Mic", "Headset Mic" }; +static const struct soc_enum magician_enum[] = { + SOC_ENUM_SINGLE_EXT(4, hp_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, input_select), +}; + +static const struct snd_kcontrol_new uda1380_magician_controls[] = { + SOC_ENUM_EXT("Headphone Switch", magician_enum[0], magician_get_jack, + magician_set_hp), + SOC_ENUM_EXT("Speaker Switch", magician_enum[1], magician_get_spk, + magician_set_spk), + SOC_ENUM_EXT("Input Select", magician_enum[2], magician_get_input, + magician_set_input), +}; + +/* + * Logic for a uda1380 as connected on a HTC Magician + */ +static int magician_uda1380_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* NC codec pins */ + snd_soc_dapm_set_endpoint(codec, "VOUTLHP", 0); + snd_soc_dapm_set_endpoint(codec, "VOUTRHP", 0); + + /* FIXME: is anything connected here? */ + snd_soc_dapm_set_endpoint(codec, "VINL", 0); + snd_soc_dapm_set_endpoint(codec, "VINR", 0); + + /* Add magician specific controls */ + for (i = 0; i < ARRAY_SIZE(uda1380_magician_controls); i++) { + if ((err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_magician_controls[i], + codec, NULL))) < 0) + return err; + } + + /* Add magician specific widgets */ + for (i = 0; i < ARRAY_SIZE(uda1380_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &uda1380_dapm_widgets[i]); + } + + /* Set up magician specific audio path interconnects */ + for (i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +/* magician digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link magician_dai[] = { +{ + .name = "uda1380", + .stream_name = "UDA1380 Playback", + .cpu_dai = &pxa_ssp_dai[0], + .codec_dai = &uda1380_dai[UDA1380_DAI_PLAYBACK], + .init = magician_uda1380_init, + .ops = &magician_playback_ops, +}, +{ + .name = "uda1380", + .stream_name = "UDA1380 Capture", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &uda1380_dai[UDA1380_DAI_CAPTURE], + .ops = &magician_capture_ops, +} +}; + +/* magician audio machine driver */ +static struct snd_soc_machine snd_soc_machine_magician = { + .name = "Magician", + .dai_link = magician_dai, + .num_links = ARRAY_SIZE(magician_dai), +}; + +/* magician audio private data */ +static struct uda1380_setup_data magician_uda1380_setup = { + .i2c_address = 0x18, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +/* magician audio subsystem */ +static struct snd_soc_device magician_snd_devdata = { + .machine = &snd_soc_machine_magician, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_uda1380, + .codec_data = &magician_uda1380_setup, +}; + +static struct platform_device *magician_snd_device; + +static int __init magician_init(void) +{ + int ret; + + if (!machine_is_magician()) + return -ENODEV; + + magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER); + + /* we may need to have the clock running here - pH5 */ + magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET); + udelay(5); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET); + + /* correct place? we'll need it to talk to the uda1380 */ + request_module("i2c-pxa"); + + magician_snd_device = platform_device_alloc("soc-audio", -1); + if (!magician_snd_device) + return -ENOMEM; + + platform_set_drvdata(magician_snd_device, &magician_snd_devdata); + magician_snd_devdata.dev = &magician_snd_device->dev; + ret = platform_device_add(magician_snd_device); + + if (ret) + platform_device_put(magician_snd_device); + + pxa_gpio_mode(GPIO23_SSPSCLK_MD); + pxa_gpio_mode(GPIO24_SSPSFRM_MD); + pxa_gpio_mode(GPIO25_SSPTXD_MD); + + return ret; +} + +static void __exit magician_exit(void) +{ + platform_device_unregister(magician_snd_device); + + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_SPK_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_EP_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_MIC_POWER); + magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER); +} + +module_init(magician_init); +module_exit(magician_exit); + +MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC Magician"); +MODULE_LICENSE("GPL");
From: Philipp Zabel philipp.zabel@gmail.com
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com --- sound/soc/codecs/uda1380.c | 255 ++++++++++++++++++++++++++++++++++++++------ 1 files changed, 222 insertions(+), 33 deletions(-)
diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 0fa9832..71960f0 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -5,6 +5,10 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * + * Copyright (c) 2007 Philipp Zabel philipp.zabel@gmail.com + * Improved support for DAPM and audio routing/mixing capabilities, + * added TLV support. + * * Modified by Richard Purdie richard@openedhand.com to fit into SoC * codec model. * @@ -28,6 +32,7 @@ #include <sound/info.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include <sound/tlv.h>
#include "uda1380.h"
@@ -111,14 +116,14 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, */ if (!codec->active && (reg >= UDA1380_MVOL)) return 0; - dbg("uda hw write %x val %x\n", reg, value); + dbg("uda hw write %x val %x", reg, value); if (codec->hw_write(codec->control_data, data, 3) == 3) { unsigned int val; i2c_master_send(codec->control_data, data, 1); i2c_master_recv(codec->control_data, data, 2); val = (data[0]<<8) | data[1]; if (val != value) { - dbg("READ BACK VAL %x\n", (data[0]<<8) | data[1]); + dbg("READ BACK VAL %x", (data[0]<<8) | data[1]); return -EIO; } return 0; @@ -131,37 +136,202 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, /* declarations of ALSA reg_elem_REAL controls */ static const char *uda1380_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz", "96kHz"}; -static const char *uda1380_input_sel[] = {"Line", "Mic"}; -static const char *uda1380_output_sel[] = {"Direct", "Mixer"}; +static const char *uda1380_input_sel[] = { "Line", "Mic + Line R", "Line L", "Mic" }; +static const char *uda1380_output_sel[] = {"DAC", "Analog Mixer"}; static const char *uda1380_spf_mode[] = {"Flat", "Minimum1", "Minimum2", "Maximum"}; +static const char *uda1380_capture_sel[] = {"ADC", "Digital Mixer"}; +static const char *uda1380_sel_ns[] = { "3rd-order", "5th-order" }; +static const char *uda1380_mix_control[] = { "off", "PCM only", "before sound processing", "after sound processing" }; +static const char *uda1380_sdet_setting[] = { "3200", "4800", "9600", "19200" }; +static const char *uda1380_os_setting[] = { "single-speed", "double-speed (no mixing)", "quad-speed (no mixing)" }; + +static const struct soc_enum uda1380_deemp_enum[] = { + SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp), +}; +static const struct soc_enum uda1380_input_sel_enum = + SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */ +static const struct soc_enum uda1380_output_sel_enum = + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */ +static const struct soc_enum uda1380_spf_enum = + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */ +static const struct soc_enum uda1380_capture_sel_enum = + SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */ +static const struct soc_enum uda1380_sel_ns_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */ +static const struct soc_enum uda1380_mix_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */ +static const struct soc_enum uda1380_sdet_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */ +static const struct soc_enum uda1380_os_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */ + +/** + * snd_soc_info_volsw - single mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int max = (kcontrol->private_value >> 16) & 0xff; + int min = (kcontrol->private_value >> 24) & 0xff; + + /* 00000000 (0) ...(-0.25 dB)... 11111000 (-78 dB), 11111100 -INF */ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = min; + uinfo->value.integer.max = max; + return 0; +} + +/** + * snd_soc_get_volsw_sgn - single mixer get callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to get the value of a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int min = (kcontrol->private_value >> 24) & 0xff; + + ucontrol->value.integer.value[0] = + (signed char)(snd_soc_read(codec, reg) & 0xff); + ucontrol->value.integer.value[1] = + (signed char)((snd_soc_read(codec, reg) >> 8) & 0xff); + + return 0; +} + +/** + * snd_soc_put_volsw_sgn - single mixer put callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to set the value of a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int min = (kcontrol->private_value >> 24) & 0xff; + int err; + unsigned short val, val2, val_mask; + + val = (signed char)ucontrol->value.integer.value[0] & 0xff; + val2 = (signed char)ucontrol->value.integer.value[1] & 0xff; + val |= val2<<8; + err = snd_soc_update_bits(codec, reg, val_mask, val); + return err; +} + +DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); /* from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) */ + +static const unsigned int mvol_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0,15, TLV_DB_SCALE_ITEM(-8200, 100, 1), /* from -78 dB in 1 dB steps (3 dB steps, really) */ + 16,43, TLV_DB_SCALE_ITEM(-6600, 50, 0), /* from -66 dB in 0.5 dB steps (2 dB steps, really) */ + 44,252, TLV_DB_SCALE_ITEM(-5200, 25, 0), /* from -52 dB in 0.25 dB steps */ +};
-static const struct soc_enum uda1380_enum[] = { - SOC_ENUM_DOUBLE(UDA1380_DEEMP, 0, 8, 5, uda1380_deemp), - SOC_ENUM_SINGLE(UDA1380_ADC, 3, 2, uda1380_input_sel), - SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode), - SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel), /* R02_EN_AVC */ +static const unsigned int vc_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0,7, TLV_DB_SCALE_ITEM(-7800, 150, 1), /* from -72 dB in 1.5 dB steps (6 dB steps really) */ + 8,15, TLV_DB_SCALE_ITEM(-6600, 75, 0), /* from -66 dB in 0.75 dB steps (3 dB steps really) */ + 16,43, TLV_DB_SCALE_ITEM(-6000, 50, 0), /* from -60 dB in 0.5 dB steps (2 dB steps really) */ + 44,228, TLV_DB_SCALE_ITEM(-4600, 25, 0),/* from -46 dB in 0.25 dB steps */ };
+DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); /* from 0 dB to 6 dB in 2 dB steps, if SPF mode != flat */ +DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); /* from 0 dB to 24 dB in 2 dB steps, if SPF mode == maximum */ + /* (SPF mode == flat cuts off at 18 dB max */ + +static const unsigned int dec_tlv[] = { + TLV_DB_RANGE_HEAD(1), + -128,48, TLV_DB_SCALE_ITEM(-6350, 50, 1), + /* 0011000 (48 dB) ...(0.5 dB)... 10000001 (-63 dB) 10000000 -INF */ +}; + +DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); /* from 0 dB to 24 dB in 3 dB steps */ +DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); /* from 0 dB to 30 dB in 2 dB steps */ + + +#define SOC_DOUBLE_S8_TLV(xname, reg, min, max, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ + .put = snd_soc_put_volsw_s8, \ + .private_value = (reg) | (((signed char)max) << 16) | (((signed char)min) << 24) } + static const struct snd_kcontrol_new uda1380_snd_controls[] = { - SOC_DOUBLE("Playback Volume", UDA1380_MVOL, 0, 8, 255, 1), - SOC_DOUBLE("Mixer Volume", UDA1380_MIXVOL, 0, 8, 255, 1), - SOC_ENUM("Sound Processing Filter Mode", uda1380_enum[2]), - SOC_DOUBLE("Treble Volume", UDA1380_MODE, 4, 12, 3, 0), - SOC_DOUBLE("Bass Volume", UDA1380_MODE, 0, 8, 15, 0), - SOC_ENUM("Playback De-emphasis", uda1380_enum[0]), - SOC_DOUBLE("Capture Volume", UDA1380_DEC, 0, 8, 127, 0), - SOC_DOUBLE("Line Capture Volume", UDA1380_PGA, 0, 8, 15, 0), - SOC_SINGLE("Mic Capture Volume", UDA1380_PGA, 8, 11, 0), - SOC_DOUBLE("Playback Switch", UDA1380_DEEMP, 3, 11, 1, 1), - SOC_SINGLE("Capture Switch", UDA1380_PGA, 15, 1, 0), - SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), - SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), + SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */ + SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */ + SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */ + SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */ + SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */ + SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */ + SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */ +/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */ + SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */ + SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */ + SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */ + SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */ + SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ + SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ + SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ + SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ + SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ + SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ + SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ + SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */ +/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */ + SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */ + SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */ + SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */ + SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */ + SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */ + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */ + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */ + /* -5.5, -8, -11.5, -14 dBFS */ SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), - SOC_SINGLE("Silence", UDA1380_MIXER, 7, 1, 0), - SOC_SINGLE("Silence Detection", UDA1380_MIXER, 6, 1, 0), };
+/** + * uda1380_snd_soc_cnew + * + * temporary copy of snd_soc_cnew that doesn't overwrite .access + */ +struct snd_kcontrol *uda1380_snd_soc_cnew(const struct snd_kcontrol_new *_template, + void *data, char *long_name) +{ + struct snd_kcontrol_new template; + + memcpy(&template, _template, sizeof(template)); + if (long_name) + template.name = long_name; + template.index = 0; + + return snd_ctl_new1(&template, data); +} + /* add non dapm controls */ static int uda1380_add_controls(struct snd_soc_codec *codec) { @@ -169,7 +339,7 @@ static int uda1380_add_controls(struct snd_soc_codec *codec)
for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { err = snd_ctl_add(codec->card, - snd_soc_cnew(&uda1380_snd_controls[i],codec, NULL)); + uda1380_snd_soc_cnew(&uda1380_snd_controls[i],codec, NULL)); if (err < 0) return err; } @@ -179,17 +349,24 @@ static int uda1380_add_controls(struct snd_soc_codec *codec)
/* Input mux */ static const struct snd_kcontrol_new uda1380_input_mux_control = - SOC_DAPM_ENUM("Input Select", uda1380_enum[1]); + SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
/* Output mux */ static const struct snd_kcontrol_new uda1380_output_mux_control = - SOC_DAPM_ENUM("Output Select", uda1380_enum[3]); + SOC_DAPM_ENUM("Route", uda1380_output_sel_enum); + +/* Capture mux */ +static const struct snd_kcontrol_new uda1380_capture_mux_control = + SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum); +
static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &uda1380_input_mux_control), SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, &uda1380_output_mux_control), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &uda1380_capture_mux_control), SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), @@ -218,8 +395,10 @@ static const char *audio_map[][3] = { {"Analog Mixer", NULL, "VINL"}, {"Analog Mixer", NULL, "DAC"},
- {"Output Mux", "Direct", "DAC"}, - {"Output Mux", "Mixer", "Analog Mixer"}, + {"Output Mux", "DAC", "DAC"}, + {"Output Mux", "Analog Mixer", "Analog Mixer"}, + + /* {"DAC", "Digital Mixer", "I2S" } */
/* headphone driver */ {"VOUTLHP", NULL, "HeadPhone Driver"}, @@ -228,10 +407,13 @@ static const char *audio_map[][3] = { /* input mux */ {"Left ADC", NULL, "Input Mux"}, {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Mic + Line R", "Mic LNA"}, + {"Input Mux", "Line L", "Left PGA"}, {"Input Mux", "Line", "Left PGA"},
/* right input */ - {"Right ADC", NULL, "Right PGA"}, + {"Right ADC", "Mic + Line R", "Right PGA"}, + {"Right ADC", "Line", "Right PGA"},
/* inputs */ {"Mic LNA", NULL, "VINM"}, @@ -307,6 +489,13 @@ static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) } else { reg_start = UDA1380_DEC; reg_end = UDA1380_AGC; + /* FIXME - wince values */ +// uda1380_write(codec, UDA1380_PM, uda1380_read_reg_cache (codec, UDA1380_PM) | R02_PON_LNA | R02_PON_DAC); + uda1380_write(codec, UDA1380_PM, 0x851f); +// uda1380_write_reg_cache(codec, UDA1380_DEC, 0x0000); +// uda1380_write_reg_cache(codec, UDA1380_PGA, uda1380_read_reg_cache (codec, UDA1380_PM) & ~R21_MT_ADC); /* unmute */ +// uda1380_write_reg_cache(codec, UDA1380_ADC, 0x0001/*0x090d*/); + // ADC_POL_INV=0 VGA_CTRL=9(1001:18dB) gain SEL_LNA=1 SEL_MIC=1 EN_DCFIL=1 }
/* FIXME disable DAC_CLK */ @@ -314,7 +503,7 @@ static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK);
for (reg = reg_start; reg <= reg_end; reg++ ) { - dbg("reg %x val %x\n",reg, uda1380_read_reg_cache (codec, reg)); + dbg("flush reg %x val %x:",reg, uda1380_read_reg_cache (codec, reg)); uda1380_write(codec, reg, uda1380_read_reg_cache (codec, reg)); }
@@ -387,7 +576,7 @@ static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) static int uda1380_mute(struct snd_soc_codec_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; - u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & 0xbfff; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM;
/* FIXME: mute(codec,0) is called when the magician clock is already * set to WSPLL, but for some unknown reason writing to interpolator @@ -395,7 +584,7 @@ static int uda1380_mute(struct snd_soc_codec_dai *codec_dai, int mute) u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); if (mute) - uda1380_write(codec, UDA1380_DEEMP, mute_reg | 0x4000); + uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); else uda1380_write(codec, UDA1380_DEEMP, mute_reg); uda1380_write(codec, UDA1380_CLK, clk);
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8510.c | 855 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8510.h | 103 ++++++ 5 files changed, 965 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/wm8510.c create mode 100644 sound/soc/codecs/wm8510.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 85472a1..fbbedbd 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -124,6 +124,7 @@ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ #define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */ #define I2C_DRIVERID_UDA1380 98 /* Philips UDA1380 audio codec */ +#define I2C_DRIVERID_WM8510 99 /* Wolfson WM8510 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4c05f60..4e8add6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -14,6 +14,10 @@ config SND_SOC_UDA1380 tristate depends on SND_SOC
+config SND_SOC_WM8510 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bce9fe0..a60778f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -2,6 +2,7 @@ snd-soc-ac97-objs := ac97.o snd-soc-ak4535-objs := ak4535.o snd-soc-tlv320-objs := tlv320.o snd-soc-uda1380-objs := uda1380.o +snd-soc-wm8510-objs := wm8510.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -13,6 +14,7 @@ obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_TLV320) += snd-soc-tlv320.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c new file mode 100644 index 0000000..85ee1ad --- /dev/null +++ b/sound/soc/codecs/wm8510.c @@ -0,0 +1,855 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood liam.girdwood@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8510.h" + +#define AUDIO_NAME "wm8510" +#define WM8510_VERSION "0.6" + +/* + * Debug + */ + +#define WM8510_DEBUG 0 + +#ifdef WM8510_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +struct snd_soc_codec_device soc_codec_dev_wm8510; + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0000, +}; + +/* + * read wm8510 register cache + */ +static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8510_RESET) + return 0; + if (reg >= WM8510_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8510 register cache + */ +static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8510_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8510 register space + */ +static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8510 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8510_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) + +static const char *wm8510_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = {"ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), +SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 0), +}; + +/* add non dapm controls */ +static int wm8510_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8510_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 1), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 1), +}; + +/* AUX Input boost vol */ +static const struct snd_kcontrol_new wm8510_aux_boost_controls = +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0); + +/* Mic Input boost vol */ +static const struct snd_kcontrol_new wm8510_mic_boost_controls = +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0); + +/* Capture boost switch */ +static const struct snd_kcontrol_new wm8510_capture_boost_controls = +SOC_DAPM_SINGLE("Capture Boost Switch", WM8510_INPPGA, 6, 1, 0); + +/* Aux In to PGA */ +static const struct snd_kcontrol_new wm8510_aux_capture_boost_controls = +SOC_DAPM_SINGLE("Aux Capture Boost Switch", WM8510_INPPGA, 2, 1, 0); + +/* Mic P In to PGA */ +static const struct snd_kcontrol_new wm8510_micp_capture_boost_controls = +SOC_DAPM_SINGLE("Mic P Capture Boost Switch", WM8510_INPPGA, 0, 1, 0); + +/* Mic N In to PGA */ +static const struct snd_kcontrol_new wm8510_micn_capture_boost_controls = +SOC_DAPM_SINGLE("Mic N Capture Boost Switch", WM8510_INPPGA, 1, 1, 0); + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", WM8510_POWER2, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0, + &wm8510_aux_boost_controls, 1), +SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0, + &wm8510_mic_boost_controls, 1), +SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0, + &wm8510_capture_boost_controls), + +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const char *audio_map[][3] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"Boost Mixer", NULL, "ADC"}, + {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"}, + {"Aux Boost", "Aux Volume", "Boost Mixer"}, + {"Capture Boost", "Capture Switch", "Boost Mixer"}, + {"Mic Boost", "Mic Volume", "Boost Mixer"}, + + /* Inputs */ + {"MICP", NULL, "Mic Boost"}, + {"MICN", NULL, "Mic PGA"}, + {"Mic PGA", NULL, "Capture Boost"}, + {"AUX", NULL, "Aux Input"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8510_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8510_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8510_dapm_widgets[i]); + + /* set up audio path audio_mapnects */ + for (i = 0; audio_map[i][0] != NULL; i++) + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_set_dai_pll(struct snd_soc_codec_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*8, freq_in); + + wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); + wm8510_write(codec, WM8510_PLLK1, (pll_div.k >> 9) && 0x1ff); + wm8510_write(codec, WM8510_PLLK1, pll_div.k && 0x1ff); + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg | 0x020); + return 0; + +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_GPIO & 0x1cf); + wm8510_write(codec, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK & 0x1f); + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = wm8510_read_reg_cache(codec, WM8510_ADC & 0x1f7); + wm8510_write(codec, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = wm8510_read_reg_cache(codec, WM8510_DAC & 0x1f7); + wm8510_write(codec, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK & 0x1e3); + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; + u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + break; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + + if (mute) + wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + else + wm8510_write(codec, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_dapm_event(struct snd_soc_codec *codec, int event) +{ + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, clk and osc on, dac unmute, active */ + wm8510_write(codec, WM8510_POWER1, 0x1ff); + wm8510_write(codec, WM8510_POWER2, 0x1ff); + wm8510_write(codec, WM8510_POWER3, 0x1ff); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, dac mute, inactive */ + + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8510_write(codec, WM8510_POWER1, 0x0); + wm8510_write(codec, WM8510_POWER2, 0x0); + wm8510_write(codec, WM8510_POWER3, 0x0); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8510_dai = { + .name = "WM8510 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = { + .hw_params = wm8510_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8510_dai); + +static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8510_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8510_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8510 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8510_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8510"; + codec->owner = THIS_MODULE; + codec->read = wm8510_read_reg_cache; + codec->write = wm8510_write; + codec->dapm_event = wm8510_dapm_event; + codec->dai = &wm8510_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(wm8510_reg); + codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8510_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8510_add_controls(codec); + wm8510_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8510_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8510 2 wire address is 0x1a + */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8510_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8510_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct wm8510_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8510_init(socdev); + if (ret < 0) { + err("failed to initialise WM8510\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8510_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8510_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8510_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "WM8510 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8510, + .attach_adapter = wm8510_i2c_attach, + .detach_client = wm8510_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8510", + .driver = &wm8510_i2c_driver, +}; +#endif + +static int wm8510_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8510_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + info("WM8510 Audio Codec %s", WM8510_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8510_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8510_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8510_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8510_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8510 = { + .probe = wm8510_probe, + .remove = wm8510_remove, + .suspend = wm8510_suspend, + .resume = wm8510_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h new file mode 100644 index 0000000..c862e7b --- /dev/null +++ b/sound/soc/codecs/wm8510.h @@ -0,0 +1,103 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8510_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8510; + +#endif
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org --- include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8711.c | 707 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8711.h | 42 +++ 5 files changed, 756 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/wm8711.c create mode 100644 sound/soc/codecs/wm8711.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index fbbedbd..7765c6c 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -125,6 +125,7 @@ #define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */ #define I2C_DRIVERID_UDA1380 98 /* Philips UDA1380 audio codec */ #define I2C_DRIVERID_WM8510 99 /* Wolfson WM8510 audio codec */ +#define I2C_DRIVERID_WM8711 100 /* Wolfson WM8711 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4e8add6..9a0ec07 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -18,6 +18,10 @@ config SND_SOC_WM8510 tristate depends on SND_SOC
+config SND_SOC_WM8711 + tristate + depends on SND_SOC + config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a60778f..b6f8578 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,6 +3,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-tlv320-objs := tlv320.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8711-objs := wm8711.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -15,6 +16,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_TLV320) += snd-soc-tlv320.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c new file mode 100644 index 0000000..8631297 --- /dev/null +++ b/sound/soc/codecs/wm8711.c @@ -0,0 +1,707 @@ +/* + * wm8711.c -- WM8711 ALSA SoC Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur linux@wolfsonmicro.com + * + * Based on wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8711.h" + +#define AUDIO_NAME "wm8711" +#define WM8711_VERSION "0.3" + +/* + * Debug + */ + +#define WM8711_DEBUG 0 + +#ifdef WM8711_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +struct snd_soc_codec_device soc_codec_dev_wm8711; + +/* codec private data */ +struct wm8711_priv { + unsigned int sysclk; +}; + +/* + * wm8711 register cache + * We can't read the WM8711 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 wm8711_reg[WM8711_CACHEREGNUM] = { + 0x0079, 0x0079, 0x000a, 0x0008, + 0x009f, 0x000a, 0x0000, 0x0000 +}; + +/* + * read wm8711 register cache + */ +static inline unsigned int wm8711_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8711_RESET) + return 0; + if (reg >= WM8711_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8711 register cache + */ +static inline void wm8711_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8711_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8711 register space + */ +static int wm8711_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8711_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8711_reset(c) wm8711_write(c, WM8711_RESET, 0) + +static const struct snd_kcontrol_new wm8711_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V, + 7, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8711_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8711_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8711_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const snd_kcontrol_new_t wm8711_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1, + &wm8711_output_mixer_controls[0], + ARRAY_SIZE(wm8711_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +}; + +static const char *intercon[][3] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8711_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8711_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8711_dapm_widgets[i]); + + /* set up audio path interconnects */ + for (i = 0; intercon[i][0] != NULL; i++) + snd_soc_dapm_connect_input(codec, intercon[i][0], + intercon[i][1], intercon[i][2]); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8711_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8711_priv *wm8711 = codec->private_data; + u16 iface = wm8711_read_reg_cache(codec, WM8711_IFACE) & 0xfffc; + int i = get_coeff(wm8711->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + wm8711_write(codec, WM8711_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + wm8711_write(codec, WM8711_IFACE, iface); + return 0; +} + +static int wm8711_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + wm8711_write(codec, WM8711_ACTIVE, 0x0001); + return 0; +} + +static void wm8711_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + wm8711_write(codec, WM8711_ACTIVE, 0x0); + } +} + +static int wm8711_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8711_read_reg_cache(codec, WM8711_APDIGI) & 0xfff7; + + if (mute) + wm8711_write(codec, WM8711_APDIGI, mute_reg | 0x8); + else + wm8711_write(codec, WM8711_APDIGI, mute_reg); + + return 0; +} + +static int wm8711_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8711_priv *wm8711 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8711->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8711_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8711_write(codec, WM8711_IFACE, iface); + return 0; +} + + +static int wm8711_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 reg = wm8711_read_reg_cache(codec, WM8711_PWR) & 0xff7f; + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* vref/mid, osc on, dac unmute */ + wm8711_write(codec, WM8711_PWR, reg); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + case SNDRV_CTL_POWER_D2: /* partial On */ + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* everything off except vref/vmid, */ + wm8711_write(codec, WM8711_PWR, reg | 0x0040); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + /* everything off, dac mute, inactive */ + wm8711_write(codec, WM8711_ACTIVE, 0x0); + wm8711_write(codec, WM8711_PWR, 0xffff); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8711_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8711_dai = { + .name = "WM8711", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8711_RATES, + .formats = WM8711_FORMATS,}, + .ops = { + .prepare = wm8711_pcm_prepare, + .hw_params = wm8711_hw_params, + .shutdown = wm8711_shutdown, + }, + .dai_ops = { + .digital_mute = wm8711_mute, + .set_sysclk = wm8711_set_dai_sysclk, + .set_fmt = wm8711_set_dai_fmt, + }, +}; +EXPORT_SYMBOL_GPL(wm8711_dai); + +static int wm8711_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8711_write(codec, WM8711_ACTIVE, 0x0); + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8711_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8711_dapm_event(codec, codec->suspend_dapm_state); + return 0; +} + +/* + * initialise the WM8711 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8711_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8711"; + codec->owner = THIS_MODULE; + codec->read = wm8711_read_reg_cache; + codec->write = wm8711_write; + codec->dapm_event = wm8711_dapm_event; + codec->dai = &wm8711_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(wm8711_reg); + codec->reg_cache = kmemdup(wm8711_reg, sizeof(wm8711_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8711_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8711: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* set the update bits */ + reg = wm8711_read_reg_cache(codec, WM8711_LOUT1V); + wm8711_write(codec, WM8711_LOUT1V, reg | 0x0100); + reg = wm8711_read_reg_cache(codec, WM8711_ROUT1V); + wm8711_write(codec, WM8711_ROUT1V, reg | 0x0100); + + wm8711_add_controls(codec); + wm8711_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8711: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8711_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8711 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8711_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8711_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8711_socdev; + struct wm8711_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8711_init(socdev); + if (ret < 0) { + err("failed to initialise WM8711\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8711_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8711_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8711_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8711_i2c_driver = { + .driver = { + .name = "WM8711 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8711, + .attach_adapter = wm8711_i2c_attach, + .detach_client = wm8711_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8711", + .driver = &wm8711_i2c_driver, +}; +#endif + +static int wm8711_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8711_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8711_priv *wm8711; + int ret = 0; + + info("WM8711 Audio Codec %s", WM8711_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL); + if (wm8711 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8711; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8711_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8711_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8711_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8711_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8711_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8711 = { + .probe = wm8711_probe, + .remove = wm8711_remove, + .suspend = wm8711_suspend, + .resume = wm8711_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8711); + +MODULE_DESCRIPTION("ASoC WM8711 driver"); +MODULE_AUTHOR("Mike Arthur"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8711.h b/sound/soc/codecs/wm8711.h new file mode 100644 index 0000000..05ef13f --- /dev/null +++ b/sound/soc/codecs/wm8711.h @@ -0,0 +1,42 @@ +/* + * wm8711.h -- WM8711 Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur linux@wolfsonmicro.com + * + * Based on wm8731.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8711_H +#define _WM8711_H + +/* WM8711 register space */ + +#define WM8711_LOUT1V 0x02 +#define WM8711_ROUT1V 0x03 +#define WM8711_APANA 0x04 +#define WM8711_APDIGI 0x05 +#define WM8711_PWR 0x06 +#define WM8711_IFACE 0x07 +#define WM8711_SRATE 0x08 +#define WM8711_ACTIVE 0x09 +#define WM8711_RESET 0x0f + +#define WM8711_CACHEREGNUM 8 + +#define WM8711_SYSCLK 0 +#define WM8711_DAI 0 + +struct wm8711_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8711_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8711; + +#endif
At Tue, 20 Nov 2007 09:25:46 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org
This one has no sign-off of the original author...
- Author: Mike Arthur linux@wolfsonmicro.com
Takashi
At Tue, 20 Nov 2007 09:25:44 +0000, Mark Brown wrote:
From: Philipp Zabel philipp.zabel@gmail.com
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com
Apparently this patch does much more than the subject says. If so, please add a proper changelog here, not only in the comment lines.
+/**
- snd_soc_info_volsw - single mixer info callback
- @kcontrol: mixer control
- @uinfo: control element information
- Callback to provide information about a single mixer control.
- Returns 0 for success.
- */
+int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+{
- int max = (kcontrol->private_value >> 16) & 0xff;
- int min = (kcontrol->private_value >> 24) & 0xff;
- /* 00000000 (0) ...(-0.25 dB)... 11111000 (-78 dB), 11111100 -INF */
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- uinfo->count = 2;
- uinfo->value.integer.min = min;
- uinfo->value.integer.max = max;
- return 0;
+}
This can be static, no? The other two, too.
+DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); /* from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) */
Define as static. DECLARE_TLV_DB_SCALE() itself doesn't contain static prefix.
+static const unsigned int mvol_tlv[] = {
- TLV_DB_RANGE_HEAD(3),
- 0,15, TLV_DB_SCALE_ITEM(-8200, 100, 1), /* from -78 dB in 1 dB steps (3 dB steps, really) */
- 16,43, TLV_DB_SCALE_ITEM(-6600, 50, 0), /* from -66 dB in 0.5 dB steps (2 dB steps, really) */
- 44,252, TLV_DB_SCALE_ITEM(-5200, 25, 0), /* from -52 dB in 0.25 dB steps */
+};
-static const struct soc_enum uda1380_enum[] = {
- SOC_ENUM_DOUBLE(UDA1380_DEEMP, 0, 8, 5, uda1380_deemp),
- SOC_ENUM_SINGLE(UDA1380_ADC, 3, 2, uda1380_input_sel),
- SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode),
- SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel), /* R02_EN_AVC */
+static const unsigned int vc_tlv[] = {
- TLV_DB_RANGE_HEAD(4),
- 0,7, TLV_DB_SCALE_ITEM(-7800, 150, 1), /* from -72 dB in 1.5 dB steps (6 dB steps really) */
- 8,15, TLV_DB_SCALE_ITEM(-6600, 75, 0), /* from -66 dB in 0.75 dB steps (3 dB steps really) */
- 16,43, TLV_DB_SCALE_ITEM(-6000, 50, 0), /* from -60 dB in 0.5 dB steps (2 dB steps really) */
- 44,228, TLV_DB_SCALE_ITEM(-4600, 25, 0),/* from -46 dB in 0.25 dB steps */
};
+DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); /* from 0 dB to 6 dB in 2 dB steps, if SPF mode != flat */ +DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); /* from 0 dB to 24 dB in 2 dB steps, if SPF mode == maximum */
/* (SPF mode == flat cuts off at 18 dB max */
Ditto.
+static const unsigned int dec_tlv[] = {
- TLV_DB_RANGE_HEAD(1),
- -128,48, TLV_DB_SCALE_ITEM(-6350, 50, 1),
- /* 0011000 (48 dB) ...(0.5 dB)... 10000001 (-63 dB) 10000000 -INF */
+};
+DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); /* from 0 dB to 24 dB in 3 dB steps */ +DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); /* from 0 dB to 30 dB in 2 dB steps */
Ditto.
Takashi
At Tue, 20 Nov 2007 09:25:43 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Philipp Zabel philipp.zabel@gmail.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com
Hmm... Some patches seem to have inconsistent From: and the author attribute. For exmaple, this module has
MODULE_AUTHOR("Philipp Zabel");
while Liam is in From header here (and it's a broken address :)
Otherwise it looks OK except for a few coding style issues. To be sure, try checkpatch.pl.
Takashi
sound/soc/pxa/Kconfig | 11 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/magician.c | 539 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 552 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/magician.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index bcb3aa0..3682f38 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -58,6 +58,17 @@ config SND_PXA2XX_SOC_TOSA Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa).
+config SND_PXA2XX_SOC_MAGICIAN
- tristate "SoC Audio support for HTC Magician"
- depends on SND_PXA2XX_SOC
- select SND_PXA2XX_SOC_I2S
- select SND_PXA2XX_SOC_SSP
- select SND_SOC_UDA1380
- help
Say Y if you want to add support for SoC audio on the
HTC Magician.
config SND_PXA2XX_SOC_AMESOM_TLV320 tristate "SoC SSP Audio support for AMESOM - TLV320AIC24k" depends on SND_PXA2XX_SOC && MACH_AMESOM diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 931bdc7..1faa751 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -15,9 +15,11 @@ snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o snd-soc-amesom-tlv320-objs := amesom_tlv320.o +snd-soc-magician-objs := magician.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o +obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o \ No newline at end of file diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c new file mode 100644 index 0000000..7eb671c --- /dev/null +++ b/sound/soc/pxa/magician.c @@ -0,0 +1,539 @@ +/*
- SoC audio for HTC Magician
- Copyright (c) 2006 Philipp Zabel philipp.zabel@gmail.com
- based on spitz.c,
- Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com
Richard Purdie <richard@openedhand.com>
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- */
+#include <linux/module.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h>
+#include <asm/hardware/scoop.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/hardware.h> +#include <asm/arch/magician.h> +#include <asm/arch/magician_cpld.h> +#include <asm/mach-types.h> +#include "../codecs/uda1380.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h"
+#define MAGICIAN_HP_ON 0 +#define MAGICIAN_HP_OFF 1
+#define MAGICIAN_SPK_ON 0 +#define MAGICIAN_SPK_OFF 1
+#define MAGICIAN_MIC 0 +#define MAGICIAN_MIC_EXT 1
+/*
- SSP GPIO's
- */
+#define GPIO23_SSPSCLK_MD (23 | GPIO_ALT_FN_2_OUT) +#define GPIO24_SSPSFRM_MD (24 | GPIO_ALT_FN_2_OUT) +#define GPIO25_SSPTXD_MD (25 | GPIO_ALT_FN_2_OUT)
+static int magician_hp_func = MAGICIAN_HP_OFF; +static int magician_spk_func = MAGICIAN_SPK_ON; +static int magician_in_sel = MAGICIAN_MIC;
+extern struct platform_device magician_cpld;
+static void magician_ext_control(struct snd_soc_codec *codec) +{
- snd_soc_dapm_set_endpoint(codec, "Speaker",
(magician_spk_func == MAGICIAN_SPK_ON));
- snd_soc_dapm_set_endpoint(codec, "Headphone Jack",
(magician_hp_func == MAGICIAN_HP_ON));
- switch (magician_in_sel) {
- case MAGICIAN_MIC:
snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0);
snd_soc_dapm_set_endpoint(codec, "Call Mic", 1);
break;
- case MAGICIAN_MIC_EXT:
snd_soc_dapm_set_endpoint(codec, "Call Mic", 0);
snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1);
break;
- }
- snd_soc_dapm_sync_endpoints(codec);
+}
+static int magician_startup(struct snd_pcm_substream *substream) +{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec *codec = rtd->socdev->codec;
- /* check the jack status at stream startup */
- magician_ext_control(codec);
- return 0;
+}
+/*
- Magician uses SSP port for playback.
- */
+static int magician_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
- struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
- unsigned int acps, acds, div4;
- int ret = 0;
- /*
* Rate = SSPSCLK / (word size(16))
* SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1)
*/
- switch (params_rate(params)) {
- case 8000:
acps = 32842000;
acds = PXA2XX_SSP_CLK_AUDIO_DIV_32; /* wrong - 32 bits/sample */
div4 = PXA2XX_SSP_CLK_SCDB_4;
break;
- case 11025:
acps = 5622000;
acds = PXA2XX_SSP_CLK_AUDIO_DIV_8; /* 16 bits/sample, 1 slot */
div4 = PXA2XX_SSP_CLK_SCDB_4;
break;
- case 22050:
acps = 5622000;
acds = PXA2XX_SSP_CLK_AUDIO_DIV_4;
div4 = PXA2XX_SSP_CLK_SCDB_4;
break;
- case 44100:
acps = 11345000;
acds = PXA2XX_SSP_CLK_AUDIO_DIV_4;
div4 = PXA2XX_SSP_CLK_SCDB_4;
break;
- case 48000:
acps = 12235000;
acds = PXA2XX_SSP_CLK_AUDIO_DIV_4;
div4 = PXA2XX_SSP_CLK_SCDB_4;
break;
- }
- /* set codec DAI configuration */
- ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
- if (ret < 0)
return ret;
- /* set cpu DAI configuration */
- ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
- if (ret < 0)
return ret;
- /* set audio clock as clock source */
- ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_AUDIO, 0,
SND_SOC_CLOCK_OUT);
- if (ret < 0)
return ret;
- /* set the SSP audio system clock ACDS divider */
- ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai,
PXA2XX_SSP_AUDIO_DIV_ACDS, acds);
- if (ret < 0)
return ret;
- /* set the SSP audio system clock SCDB divider4 */
- ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai,
PXA2XX_SSP_AUDIO_DIV_SCDB, div4);
- if (ret < 0)
return ret;
- /* set SSP audio pll clock */
- ret = cpu_dai->dai_ops.set_pll(cpu_dai, 0, 0, acps);
- if (ret < 0)
return ret;
- return 0;
+}
+/*
- We have to enable the SSP port early so the UDA1380 can flush
- it's register cache. The UDA1380 can only write it's interpolator and
- decimator registers when the link is running.
- */
+static int magician_playback_prepare(struct snd_pcm_substream *substream) +{
- /* enable SSP clock - is this needed ? */
- SSCR0_P(1) |= SSCR0_SSE;
- /* FIXME: ENABLE I2S */
- SACR0 |= SACR0_BCKD;
- SACR0 |= SACR0_ENB;
- pxa_set_cken(CKEN8_I2S, 1);
- return 0;
+}
+static int magician_playback_hw_free(struct snd_pcm_substream *substream) +{
- /* FIXME: DISABLE I2S */
- SACR0 &= ~SACR0_ENB;
- SACR0 &= ~SACR0_BCKD;
- pxa_set_cken(CKEN8_I2S, 0);
- return 0;
+}
+/*
- Magician uses I2S for capture.
- */
+static int magician_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
- struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
- int ret = 0;
- /* set codec DAI configuration */
- ret = codec_dai->dai_ops.set_fmt(codec_dai,
SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
- if (ret < 0)
return ret;
- /* set cpu DAI configuration */
- ret = cpu_dai->dai_ops.set_fmt(cpu_dai,
SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
- if (ret < 0)
return ret;
- /* set the I2S system clock as output */
- ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
SND_SOC_CLOCK_OUT);
- if (ret < 0)
return ret;
- return 0;
+}
+/*
- We have to enable the I2S port early so the UDA1380 can flush
- it's register cache. The UDA1380 can only write it's interpolator and
- decimator registers when the link is running.
- */
+static int magician_capture_prepare(struct snd_pcm_substream *substream) +{
- SACR0 |= SACR0_ENB;
- return 0;
+}
+static struct snd_soc_ops magician_capture_ops = {
- .startup = magician_startup,
- .hw_params = magician_capture_hw_params,
- .prepare = magician_capture_prepare,
+};
+static struct snd_soc_ops magician_playback_ops = {
- .startup = magician_startup,
- .hw_params = magician_playback_hw_params,
- .prepare = magician_playback_prepare,
- .hw_free = magician_playback_hw_free,
+};
+static int magician_get_jack(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- ucontrol->value.integer.value[0] = magician_hp_func;
- return 0;
+}
+static int magician_set_hp(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- if (magician_hp_func == ucontrol->value.integer.value[0])
return 0;
- magician_hp_func = ucontrol->value.integer.value[0];
- magician_ext_control(codec);
- return 1;
+}
+static int magician_get_spk(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- ucontrol->value.integer.value[0] = magician_spk_func;
- return 0;
+}
+static int magician_set_spk(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- if (magician_spk_func == ucontrol->value.integer.value[0])
return 0;
- magician_spk_func = ucontrol->value.integer.value[0];
- magician_ext_control(codec);
- return 1;
+}
+static int magician_get_input(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- ucontrol->value.integer.value[0] = magician_in_sel;
- return 0;
+}
+static int magician_set_input(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_value * ucontrol)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- if (magician_in_sel == ucontrol->value.integer.value[0])
return 0;
- magician_in_sel = ucontrol->value.integer.value[0];
- switch (magician_in_sel) {
- case MAGICIAN_MIC:
magician_egpio_disable(&magician_cpld,
EGPIO_NR_MAGICIAN_IN_SEL0);
magician_egpio_enable(&magician_cpld,
EGPIO_NR_MAGICIAN_IN_SEL1);
break;
- case MAGICIAN_MIC_EXT:
magician_egpio_disable(&magician_cpld,
EGPIO_NR_MAGICIAN_IN_SEL0);
magician_egpio_disable(&magician_cpld,
EGPIO_NR_MAGICIAN_IN_SEL1);
- }
- return 1;
+}
+static int magician_spk_power(struct snd_soc_dapm_widget *w, int event) +{
- if (SND_SOC_DAPM_EVENT_ON(event))
magician_egpio_enable(&magician_cpld,
EGPIO_NR_MAGICIAN_SPK_POWER);
- else
magician_egpio_disable(&magician_cpld,
EGPIO_NR_MAGICIAN_SPK_POWER);
- return 0;
+}
+static int magician_hp_power(struct snd_soc_dapm_widget *w, int event) +{
- if (SND_SOC_DAPM_EVENT_ON(event))
magician_egpio_enable(&magician_cpld,
EGPIO_NR_MAGICIAN_EP_POWER);
- else
magician_egpio_disable(&magician_cpld,
EGPIO_NR_MAGICIAN_EP_POWER);
- return 0;
+}
+static int magician_mic_bias(struct snd_soc_dapm_widget *w, int event) +{ +// if (SND_SOC_DAPM_EVENT_ON(event)) +// magician_egpio_enable(&magician_cpld, +// EGPIO_NR_MAGICIAN_MIC_POWER); +// else +// magician_egpio_disable(&magician_cpld, +// EGPIO_NR_MAGICIAN_MIC_POWER);
- return 0;
+}
+/* magician machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
- SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power),
- SND_SOC_DAPM_SPK("Speaker", magician_spk_power),
- SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias),
- SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias),
+};
+/* magician machine audio_map */ +static const char *audio_map[][3] = {
- /* Headphone connected to VOUTL, VOUTR */
- {"Headphone Jack", NULL, "VOUTL"},
- {"Headphone Jack", NULL, "VOUTR"},
- /* Speaker connected to VOUTL, VOUTR */
- {"Speaker", NULL, "VOUTL"},
- {"Speaker", NULL, "VOUTR"},
- /* Mics are connected to VINM */
- {"VINM", NULL, "Headset Mic"},
- {"VINM", NULL, "Call Mic"},
- {NULL, NULL, NULL},
+};
+static const char *hp_function[] = { "On", "Off" }; +static const char *spk_function[] = { "On", "Off" }; +static const char *input_select[] = { "Call Mic", "Headset Mic" }; +static const struct soc_enum magician_enum[] = {
- SOC_ENUM_SINGLE_EXT(4, hp_function),
- SOC_ENUM_SINGLE_EXT(2, spk_function),
- SOC_ENUM_SINGLE_EXT(2, input_select),
+};
+static const struct snd_kcontrol_new uda1380_magician_controls[] = {
- SOC_ENUM_EXT("Headphone Switch", magician_enum[0], magician_get_jack,
magician_set_hp),
- SOC_ENUM_EXT("Speaker Switch", magician_enum[1], magician_get_spk,
magician_set_spk),
- SOC_ENUM_EXT("Input Select", magician_enum[2], magician_get_input,
magician_set_input),
+};
+/*
- Logic for a uda1380 as connected on a HTC Magician
- */
+static int magician_uda1380_init(struct snd_soc_codec *codec) +{
- int i, err;
- /* NC codec pins */
- snd_soc_dapm_set_endpoint(codec, "VOUTLHP", 0);
- snd_soc_dapm_set_endpoint(codec, "VOUTRHP", 0);
- /* FIXME: is anything connected here? */
- snd_soc_dapm_set_endpoint(codec, "VINL", 0);
- snd_soc_dapm_set_endpoint(codec, "VINR", 0);
- /* Add magician specific controls */
- for (i = 0; i < ARRAY_SIZE(uda1380_magician_controls); i++) {
if ((err = snd_ctl_add(codec->card,
snd_soc_cnew(&uda1380_magician_controls[i],
codec, NULL))) < 0)
return err;
- }
- /* Add magician specific widgets */
- for (i = 0; i < ARRAY_SIZE(uda1380_dapm_widgets); i++) {
snd_soc_dapm_new_control(codec, &uda1380_dapm_widgets[i]);
- }
- /* Set up magician specific audio path interconnects */
- for (i = 0; audio_map[i][0] != NULL; i++) {
snd_soc_dapm_connect_input(codec, audio_map[i][0],
audio_map[i][1], audio_map[i][2]);
- }
- snd_soc_dapm_sync_endpoints(codec);
- return 0;
+}
+/* magician digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link magician_dai[] = { +{
- .name = "uda1380",
- .stream_name = "UDA1380 Playback",
- .cpu_dai = &pxa_ssp_dai[0],
- .codec_dai = &uda1380_dai[UDA1380_DAI_PLAYBACK],
- .init = magician_uda1380_init,
- .ops = &magician_playback_ops,
+}, +{
- .name = "uda1380",
- .stream_name = "UDA1380 Capture",
- .cpu_dai = &pxa_i2s_dai,
- .codec_dai = &uda1380_dai[UDA1380_DAI_CAPTURE],
- .ops = &magician_capture_ops,
+} +};
+/* magician audio machine driver */ +static struct snd_soc_machine snd_soc_machine_magician = {
- .name = "Magician",
- .dai_link = magician_dai,
- .num_links = ARRAY_SIZE(magician_dai),
+};
+/* magician audio private data */ +static struct uda1380_setup_data magician_uda1380_setup = {
- .i2c_address = 0x18,
- .dac_clk = UDA1380_DAC_CLK_WSPLL,
+};
+/* magician audio subsystem */ +static struct snd_soc_device magician_snd_devdata = {
- .machine = &snd_soc_machine_magician,
- .platform = &pxa2xx_soc_platform,
- .codec_dev = &soc_codec_dev_uda1380,
- .codec_data = &magician_uda1380_setup,
+};
+static struct platform_device *magician_snd_device;
+static int __init magician_init(void) +{
- int ret;
- if (!machine_is_magician())
return -ENODEV;
- magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER);
- /* we may need to have the clock running here - pH5 */
- magician_egpio_enable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET);
- udelay(5);
- magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_RESET);
- /* correct place? we'll need it to talk to the uda1380 */
- request_module("i2c-pxa");
- magician_snd_device = platform_device_alloc("soc-audio", -1);
- if (!magician_snd_device)
return -ENOMEM;
- platform_set_drvdata(magician_snd_device, &magician_snd_devdata);
- magician_snd_devdata.dev = &magician_snd_device->dev;
- ret = platform_device_add(magician_snd_device);
- if (ret)
platform_device_put(magician_snd_device);
- pxa_gpio_mode(GPIO23_SSPSCLK_MD);
- pxa_gpio_mode(GPIO24_SSPSFRM_MD);
- pxa_gpio_mode(GPIO25_SSPTXD_MD);
- return ret;
+}
+static void __exit magician_exit(void) +{
- platform_device_unregister(magician_snd_device);
- magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_SPK_POWER);
- magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_EP_POWER);
- magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_MIC_POWER);
- magician_egpio_disable(&magician_cpld, EGPIO_NR_MAGICIAN_CODEC_POWER);
+}
+module_init(magician_init); +module_exit(magician_exit);
+MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC Magician");
+MODULE_LICENSE("GPL");
1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Nov 20, 2007 at 12:03:54PM +0100, Takashi Iwai wrote:
Hmm... Some patches seem to have inconsistent From: and the author attribute. For exmaple, this module has
MODULE_AUTHOR("Philipp Zabel");
while Liam is in From header here (and it's a broken address :)
Yes, there's quite a few things look a bit bogus here. I've gone through and reviewed things - hopefully the meta information should be more joined up for the next submission.
On Nov 20, 2007 3:20 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Nov 20, 2007 at 12:03:54PM +0100, Takashi Iwai wrote:
Hmm... Some patches seem to have inconsistent From: and the author attribute. For exmaple, this module has
MODULE_AUTHOR("Philipp Zabel");
while Liam is in From header here (and it's a broken address :)
Yes, there's quite a few things look a bit bogus here. I've gone through and reviewed things - hopefully the meta information should be more joined up for the next submission.
I'm only just about to push basic HTC Magician machine support to LAK, so I'm not sure if this driver should still be held back a bit. I only sent it to Liam directly by mail (actually I created it with his help in a sort of email ping-pong), so that's how his address ended up in the From: header.
regards Philipp
On Tue, Nov 20, 2007 at 03:34:40PM +0100, pHilipp Zabel wrote:
I'm only just about to push basic HTC Magician machine support to LAK, so I'm not sure if this driver should still be held back a bit. I only
I've no strong feeling either way so if you're unsure if it should go I'll drop it from the patch series for now. Could you take care of submitting the ASoC changes when everything else is ready, please?
On Nov 20, 2007 4:43 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Nov 20, 2007 at 03:34:40PM +0100, pHilipp Zabel wrote:
I'm only just about to push basic HTC Magician machine support to LAK, so I'm not sure if this driver should still be held back a bit. I only
I've no strong feeling either way so if you're unsure if it should go I'll drop it from the patch series for now. Could you take care of submitting the ASoC changes when everything else is ready, please?
I will. That will also give me the opportunity make the driver use clock and GPIO API before submitting.
regards Philipp
At Tue, 20 Nov 2007 09:25:42 +0000, Mark Brown wrote:
--- /dev/null +++ b/sound/soc/pxa/amesom_tlv320.c
(snip)
+static struct snd_soc_machine amesom;
Looks superfluous.
+/*
- Tlv320 uses SSP port for playback.
- */
+static int tlv320_voice_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
- struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
- int ret = 0;
- //printk("tlv320_voice_hw_params enter\n");
Avoid C++ style comments.
- switch(params_rate(params)) {
Add space between switch and the parenthesis.
Takashi
Mark,
I suggest to do the same naming changes in the machine part like I 've suggested for tlv32024K codec driver: rename all tlv320 places to tlv320aic2x and "aic2x_"
Also I suggest to name all static functions in the machine ASoC part to begin from ameson_ and not the codec one.
Regards, Vladimir
Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Nicola Perrino nicola.perrino@atlab.it Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com
sound/soc/pxa/Kconfig | 10 ++ sound/soc/pxa/Makefile | 3 +- sound/soc/pxa/amesom_tlv320.c | 211 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletions(-) create mode 100644 sound/soc/pxa/amesom_tlv320.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index ca825e6..bcb3aa0 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -57,3 +57,13 @@ config SND_PXA2XX_SOC_TOSA help Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa).
+config SND_PXA2XX_SOC_AMESOM_TLV320
- tristate "SoC SSP Audio support for AMESOM - TLV320AIC24k"
- depends on SND_PXA2XX_SOC && MACH_AMESOM
- select SND_PXA2XX_SOC_I2S
- select SND_PXA2XX_SOC_SSP
- select SND_SOC_TLV320
- help
Say Y if you want to add support for SoC audio on Amesom
with the tlv320.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index c631651..931bdc7 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -14,9 +14,10 @@ snd-soc-corgi-objs := corgi.o snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-spitz-objs := spitz.o +snd-soc-amesom-tlv320-objs := amesom_tlv320.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
+obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o diff --git a/sound/soc/pxa/amesom_tlv320.c b/sound/soc/pxa/amesom_tlv320.c new file mode 100644 index 0000000..6aa1c2c --- /dev/null +++ b/sound/soc/pxa/amesom_tlv320.c @@ -0,0 +1,211 @@ +/*
- amesom_tlv320.c -- SoC audio for Amesom
- Copyright 2005 Wolfson Microelectronics PLC.
- Copyright 2006 Atlab srl.
- Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com
Nicola Perrino <nicola.perrino@atlab.it>
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- Revision history
- 5th Dec 2006 Initial version.
- */
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h>
+#include <asm/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/audio.h>
+#include "../codecs/tlv320.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-ssp.h"
+/*
- SSP2 GPIO's
- */
+#define GPIO11_SSP2RX_MD (11 | GPIO_ALT_FN_2_IN) +#define GPIO13_SSP2TX_MD (13 | GPIO_ALT_FN_1_OUT) +#define GPIO50_SSP2CLKS_MD (50 | GPIO_ALT_FN_3_IN) +#define GPIO14_SSP2FRMS_MD (14 | GPIO_ALT_FN_2_IN) +#define GPIO50_SSP2CLKM_MD (50 | GPIO_ALT_FN_3_OUT) +#define GPIO14_SSP2FRMM_MD (14 | GPIO_ALT_FN_2_OUT)
+static struct snd_soc_machine amesom;
+static int amesom_probe(struct platform_device *pdev) +{
- return 0;
+}
+static int amesom_remove(struct platform_device *pdev) +{
- return 0;
+}
+static int tlv320_voice_startup(struct snd_pcm_substream *substream) +{
- return 0;
+}
+static void tlv320_voice_shutdown(struct snd_pcm_substream *substream) +{
- return;
+}
+/*
- Tlv320 uses SSP port for playback.
- */
+static int tlv320_voice_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
- struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
- int ret = 0;
- //printk("tlv320_voice_hw_params enter\n");
- switch(params_rate(params)) {
- case 8000:
//printk("tlv320_voice_hw_params 8000\n");
break;
- case 16000:
//printk("tlv320_voice_hw_params 16000\n");
break;
- default:
break;
- }
- // CODEC MASTER, SSP SLAVE
- /* set codec DAI configuration */
- ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_MSB |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
return ret;
- /* set cpu DAI configuration */
- ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_MSB |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
return ret;
- /* set the SSP system clock as input (unused) */
- ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_SSP_CLK_NET_PLL, 0,
SND_SOC_CLOCK_IN);
- if (ret < 0)
return ret;
- /* set SSP slots */
- //ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x1, slots);
- ret = cpu_dai->dai_ops.set_tdm_slot(cpu_dai, 0x3, 1);
- if (ret < 0)
return ret;
- return 0;
+}
+static int tlv320_voice_hw_free(struct snd_pcm_substream *substream) +{
- return 0;
+}
+static struct snd_soc_ops tlv320_voice_ops = {
- .startup = tlv320_voice_startup,
- .shutdown = tlv320_voice_shutdown,
- .hw_params = tlv320_voice_hw_params,
- .hw_free = tlv320_voice_hw_free,
+};
+static struct snd_soc_dai_link amesom_dai[] = { +{
- .name = "TLV320",
- .stream_name = "TLV320 Voice",
- .cpu_dai = &pxa_ssp_dai[PXA2XX_DAI_SSP2],
- .codec_dai = &tlv320_dai[TLV320_DAI_MODE1_VOICE],
- .ops = &tlv320_voice_ops,
+}, +};
+static struct snd_soc_machine amesom = {
- .name = "Amesom",
- .probe = amesom_probe,
- .remove = amesom_remove,
- .dai_link = amesom_dai,
- .num_links = ARRAY_SIZE(amesom_dai),
+};
+static struct tlv320_setup_data amesom_tlv320_setup = { +#ifdef TLV320AIC24K //codec2
- .i2c_address = 0x41,
+#else // TLV320AIC14k
- .i2c_address = 0x40,
+#endif +};
+static struct snd_soc_device amesom_snd_devdata = {
- .machine = &amesom,
- .platform = &pxa2xx_soc_platform,
- .codec_dev = &soc_codec_dev_tlv320,
- .codec_data = &amesom_tlv320_setup,
+};
+static struct platform_device *amesom_snd_device;
+static int __init amesom_init(void) +{
- int ret;
- amesom_snd_device = platform_device_alloc("soc-audio", -1);
- if (!amesom_snd_device)
return -ENOMEM;
- platform_set_drvdata(amesom_snd_device, &amesom_snd_devdata);
- amesom_snd_devdata.dev = &amesom_snd_device->dev;
- ret = platform_device_add(amesom_snd_device);
- if (ret)
platform_device_put(amesom_snd_device);
- /* SSP port 2 slave */
- pxa_gpio_mode(GPIO11_SSP2RX_MD);
- pxa_gpio_mode(GPIO13_SSP2TX_MD);
- pxa_gpio_mode(GPIO50_SSP2CLKS_MD);
- pxa_gpio_mode(GPIO14_SSP2FRMS_MD);
- return ret;
+}
+static void __exit amesom_exit(void) +{
- platform_device_unregister(amesom_snd_device);
+}
+module_init(amesom_init); +module_exit(amesom_exit);
+/* Module information */ +MODULE_AUTHOR("Nicola Perrino"); +MODULE_DESCRIPTION("ALSA SoC TLV320 Amesom"); +MODULE_LICENSE("GPL");
On Tue, Nov 20, 2007 at 03:31:30PM +0300, Vladimir A. Barinov wrote:
I suggest to do the same naming changes in the machine part like I 've suggested for tlv32024K codec driver: rename all tlv320 places to tlv320aic2x and "aic2x_"
Also I suggest to name all static functions in the machine ASoC part to begin from ameson_ and not the codec one.
That all seems sensible. Nicola, could you look into this please?
Thanks.
At Tue, 20 Nov 2007 09:25:39 +0000, Mark Brown wrote:
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 5ced329..f6a1ddf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -122,6 +122,7 @@ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ +#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
Where is 96? :-)
--- /dev/null +++ b/sound/soc/codecs/tlv320.c
(snip)
+//#define TLV320_DEBUG 0
Avoid C++ style comments. Use C-style /**/ instead.
+#define TLV320_VOICE_BITS \
- (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
Double-check if it's really S24_LE or S24_3LE.
+static int caps_charge = 2000; +static int setting = 1; +module_param(caps_charge, int, 0); +module_param(setting, int, 0);
No description for setting variable? Also, this should be bool instead of int.
+/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x01, /* CONTROL REG 1 */
0x02, /* CONTROL REG 2 */
0x03, /* CONTROL REG 3A */
Spaces and tabs are mixed up. Use tabs please.
+static inline unsigned int tlv320_read_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg)
+{
- u8 *cache = codec->reg_cache;
- if (reg > ARRAY_SIZE(tlv320_reg_addr))
Shouldn't it be "reg >= ARRAY_SIZE(tlv_320_reg_addr)" ?
+static inline void tlv320_write_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int value)
+{
- u8 *cache = codec->reg_cache;
- if (reg > ARRAY_SIZE(tlv320_reg_addr))
return;
Ditto.
+static int tlv320_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+{
- if (tlv320_reg_addr[reg] > 0x06)
return -1;
- tlv320_write_reg_cache (codec, reg, value);
- return i2c_smbus_write_byte_data(codec->control_data, tlv320_reg_addr[reg], value);
+/*
- write block tlv320
- */
+static int tlv320_write_block (struct snd_soc_codec *codec,
- const u8 *data, unsigned int len)
+{
- int ret = -1;
- int i;
- for (i=0; i<len; i++) {
dbg("addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], data[i]);
if ((ret = tlv320_write(codec, i, data[i])) < 0)
Avoid if ((xxx = yyy()) < 0) style. This isn't recommended any more. Split to two lines, xxx = yyy(); if (xxx < 0) ...
+static int tlv320_init(struct snd_soc_device *socdev) +{
...
- /* register pcms */
- ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
SNDRV_DEFAULT_IDX1 and SNDRV_DEFAULT_STR1 are for card index and name string, not for PCM instance. Pass 0 and any suitable string here.
- queue_delayed_work(tlv320_workq,
&codec->delayed_work, msecs_to_jiffies(caps_charge));
Remove this unless you really implement the workq stuff. (Ditto for workq creation.) Creation of a workq isn't no cost even if it's not used.
Also, there are some other coding style issues. Try checkpatch.pl to find out them.
Furthermore, if this driver supports only I2C right now, it'd be better to add kconfig dependency to CONFIG_I2C.
Takashi
On Tue, Nov 20, 2007 at 11:53:25AM +0100, Takashi Iwai wrote:
Mark Brown wrote:
+#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
Where is 96? :-)
Sorry, I had another codec queued for addition which I dropped from the series. I'll fix this when I resubmit.
+#define TLV320_VOICE_BITS \
- (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
Double-check if it's really S24_LE or S24_3LE.
Nicola, could you please look into this and also the other issues that Takashi raised?
Hi Takashi, Mark,
On Tue, 20 Nov 2007 11:53:25 +0100, Takashi Iwai wrote:
At Tue, 20 Nov 2007 09:25:39 +0000, Mark Brown wrote:
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 5ced329..f6a1ddf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -122,6 +122,7 @@ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ +#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
Where is 96? :-)
You don't seem to use these driver IDs anywhere? These IDs are optional, so if you have no need for them, it's much easier to just not define them.
Note that the i2c_driver.id field is planed for removal anyway.
At Tue, 20 Nov 2007 22:34:42 +0100, Jean Delvare wrote:
Hi Takashi, Mark,
On Tue, 20 Nov 2007 11:53:25 +0100, Takashi Iwai wrote:
At Tue, 20 Nov 2007 09:25:39 +0000, Mark Brown wrote:
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 5ced329..f6a1ddf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -122,6 +122,7 @@ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ +#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
Where is 96? :-)
You don't seem to use these driver IDs anywhere? These IDs are optional, so if you have no need for them, it's much easier to just not define them.
Note that the i2c_driver.id field is planed for removal anyway.
Thanks, that's good to know. Mark, could you get rid of them in your next patch series?
Takashi
On Wed, Nov 21, 2007 at 10:48:32AM +0100, Takashi Iwai wrote:
Jean Delvare wrote:
You don't seem to use these driver IDs anywhere? These IDs are optional, so if you have no need for them, it's much easier to just not define them.
Thanks, that's good to know. Mark, could you get rid of them in your next patch series?
Yes, I will do so.
Hi Mark,
The TLV320 name doesn't define the specific device, it just names the TI brand and voltage params. There is a certain gradation of TLV320 codecs and they are quite different inside. They are:
1) TLV320AIC2X family that currently includes aic20, aic20K, aic21, aic24, aic24K, aic25 audio chips
2) TLV320AIC3X family that currently includes aic31, aic3106, aic32, aic33 and possible other audio chips
3) for aic2x there are some chips that has diferent internal structure and couldn't be put in 1) family: {aic22} - quite a different by internal structure {aic23} - quite a different by internal structure {aic26} - quite a different by internal structure {aic28, aic29} - these are also similar by register set
So you shouldn't name the tlv320aic24K codec driver as "tlv320". You should create one named like tlv320aic2x and in the driver name callback starting with "aic2x_".
Regards, Vladimir
Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
Signed-off-by: Nicola Perrino nicola.perrino@atlab.it Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Cc: i2c@lm-sensors.org
include/linux/i2c-id.h | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tlv320.c | 603 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tlv320.h | 111 +++++++++ 5 files changed, 721 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/tlv320.c create mode 100644 sound/soc/codecs/tlv320.h
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 5ced329..f6a1ddf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -122,6 +122,7 @@ #define I2C_DRIVERID_VP27SMPX 93 /* Panasonic VP27s tuner internal MPX */ #define I2C_DRIVERID_CS4270 94 /* Cirrus Logic 4270 audio codec */ #define I2C_DRIVERID_AK4535 95 /* AK4525 audio codec */ +#define I2C_DRIVERID_TLV320 97 /* TLV 320 audio codec */
#define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8244107..2608499 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -6,6 +6,10 @@ config SND_SOC_AK4535 tristate depends on SND_SOC
+config SND_SOC_TLV320
- tristate
- depends on SND_SOC
config SND_SOC_WM8731 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 6859145..3cce68b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,5 +1,6 @@ snd-soc-ac97-objs := ac97.o snd-soc-ak4535-objs := ak4535.o +snd-soc-tlv320-objs := tlv320.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -9,6 +10,7 @@ snd-soc-cs4270-objs := cs4270.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_TLV320) += snd-soc-tlv320.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o diff --git a/sound/soc/codecs/tlv320.c b/sound/soc/codecs/tlv320.c new file mode 100644 index 0000000..58c7c3e --- /dev/null +++ b/sound/soc/codecs/tlv320.c @@ -0,0 +1,603 @@ +/*
- tlv320.c -- TLV 320 ALSA Soc Audio driver
- Copyright 2005 Wolfson Microelectronics PLC.
- Copyright 2006 Atlab srl.
- Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com
Nicola Perrino <nicola.perrino@atlab.it>
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- */
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h>
+#include "tlv320.h"
+#define AUDIO_NAME "tlv320" +#define TLV320_VERSION "0.1"
+/*
- Debug
- */
+//#define TLV320_DEBUG 0
+#ifdef TLV320_DEBUG +#define dbg(format, arg...) \
- printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
+#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) \
- printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
+#define info(format, arg...) \
- printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
+#define warn(format, arg...) \
- printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
+#define TLV320_VOICE_RATES \
- (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
- SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000)
+#define TLV320_VOICE_BITS \
- (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+static int caps_charge = 2000; +static int setting = 1; +module_param(caps_charge, int, 0); +module_param(setting, int, 0); +MODULE_PARM_DESC(caps_charge, "TLV320 cap charge time (msecs)");
+static struct workqueue_struct *tlv320_workq = NULL; +//static struct work_struct tlv320_dapm_work;
+/* codec private data */ +struct tlv320_priv {
- unsigned int sysclk;
- unsigned int pcmclk;
+};
+#ifdef TLV320AIC24K +/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x01, /* CONTROL REG 1 */
0x02, /* CONTROL REG 2 */
0x03, /* CONTROL REG 3A */
0x03, /* CONTROL REG 3B */
0x03, /* CONTROL REG 3C */
0x03, /* CONTROL REG 3D */
0x04, /* CONTROL REG 4 */
0x04, /* CONTROL REG 4 Bis */
0x05, /* CONTROL REG 5A */
0x05, /* CONTROL REG 5B */
0x05, /* CONTROL REG 5C */
0x05, /* CONTROL REG 5D */
0x06, /* CONTROL REG 6A */
0x06, /* CONTROL REG 6B */
+};
+/*
- DATA case digital SET1:
SSP -> DAC -> OUT
IN -> ADC -> SSP
IN = HNSI (MIC)
OUT = HDSO (SPKG)
- Usage: playback, capture streams
- */
+static const unsigned char tlv320_reg_data_init_set1[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x49, /* CONTROL REG 1 */
0x20, /* CONTROL REG 2 */
0x01, /* CONTROL REG 3A */
0x40, /* CONTROL REG 3B */
0x81, /* CONTROL REG 3C */
0xc0, /* CONTROL REG 3D */
0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */
0x88,//0x90, /* CONTROL REG 4 Bis */
0x00, /* CONTROL REG 5A */
0x40,//(0dB) /* CONTROL REG 5B */
0xbf, /* CONTROL REG 5C */
0xc0, /* CONTROL REG 5D */
0x02,//(HNSI) /* CONTROL REG 6A */
0x81 //(HDSO) /* CONTROL REG 6B */
+};
+/*
- DATA case digital SET2:
SSP -> DAC -> OUT
IN -> ADC -> SSP
IN = HDSI (PHONE IN)
OUT = HNSO (PHONE OUT)
- Usage: playback, capture streams
- */
+static const unsigned char tlv320_reg_data_init_set2[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x49, /* CONTROL REG 1 */
0x20, /* CONTROL REG 2 */
0x01, /* CONTROL REG 3A */
0x40, /* CONTROL REG 3B */
0x81, /* CONTROL REG 3C */
0xc0, /* CONTROL REG 3D */
0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */
0x88,//0x90, /* CONTROL REG 4 Bis */
0x00, /* CONTROL REG 5A */
0x52,//(-27dB) /* CONTROL REG 5B */
0xbf, /* CONTROL REG 5C */
0xc0, /* CONTROL REG 5D */
0x01,//(PHONE IN) /* CONTROL REG 6A */
0x82 //(PHONE OUT) /* CONTROL REG 6B */
+};
+/*
- DATA case analog:
ADC, DAC, SSP off
Headset input to output (HDSI2O -> 1)
Handset input to output (HNSI2O -> 1)
- Usage: room monitor
- */
+static const unsigned char tlv320_reg_data_init_set3[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x08, /* CONTROL REG 1 */
0x20, /* CONTROL REG 2 */
0x11, /* CONTROL REG 3A */
0x40, /* CONTROL REG 3B */
0x80, /* CONTROL REG 3C */
0xc0, /* CONTROL REG 3D */
0x00, /* CONTROL REG 4 */
0x00, /* CONTROL REG 5A */
0x40, /* CONTROL REG 5B */
0x80, /* CONTROL REG 5C */
0xc0, /* CONTROL REG 5D */
0x60, /* CONTROL REG 6A */
0x80 /* CONTROL REG 6B */
+};
+#else // TLV320AIC14k
+/* ADDR table */ +static const unsigned char tlv320_reg_addr[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x01, /* CONTROL REG 1 */
0x02, /* CONTROL REG 2 */
0x03, /* CONTROL REG 3 */
0x04, /* CONTROL REG 4 */
0x04, /* CONTROL REG 4 Bis */
0x05, /* CONTROL REG 5A */
0x05, /* CONTROL REG 5B */
0x05, /* CONTROL REG 5C */
0x05, /* CONTROL REG 5D */
0x06 /* CONTROL REG 6 */
+};
+/*
- DATA case digital:
SSP -> DAC -> OUT
IN -> ADC -> SSP
- Usage: playback, capture streams
- */
+static const unsigned char tlv320_reg_data_init_set1[] = {
- 0x00, /* CONTROL REG 0 No Operation */
- 0x41, /* CONTROL REG 1 */
0x20, /* CONTROL REG 2 */
0x09, /* CONTROL REG 3 */
0x02,//0x42(16khz),//0x10, /* CONTROL REG 4 */
0x88,//0x90, /* CONTROL REG 4 Bis */
0x2A, /* CONTROL REG 5A */
0x6A, /* CONTROL REG 5B */
0xbc, /* CONTROL REG 5C */
0xc0, /* CONTROL REG 5D */
0x00 /* CONTROL REG 6 */
+}; +#endif +/*
- read tlv320 register cache
- */
+static inline unsigned int tlv320_read_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg)
+{
- u8 *cache = codec->reg_cache;
- if (reg > ARRAY_SIZE(tlv320_reg_addr))
return -1;
- return cache[reg];
+}
+/*
- write tlv320 register cache
- */
+static inline void tlv320_write_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int value)
+{
- u8 *cache = codec->reg_cache;
- if (reg > ARRAY_SIZE(tlv320_reg_addr))
return;
- cache[reg] = value;
+}
+/*
- read tlv320
- */
+static int tlv320_read (struct snd_soc_codec *codec, u8 reg) +{
- return i2c_smbus_read_byte_data(codec->control_data, reg);
+}
+/*
- write tlv320
- */
+static int tlv320_write(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+{
- if (tlv320_reg_addr[reg] > 0x06)
return -1;
- tlv320_write_reg_cache (codec, reg, value);
- return i2c_smbus_write_byte_data(codec->control_data, tlv320_reg_addr[reg], value);
+}
+/*
- write block tlv320
- */
+static int tlv320_write_block (struct snd_soc_codec *codec,
- const u8 *data, unsigned int len)
+{
- int ret = -1;
- int i;
- for (i=0; i<len; i++) {
dbg("addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], data[i]);
if ((ret = tlv320_write(codec, i, data[i])) < 0)
break;
- }
- return ret;
+}
+static int tlv320_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
unsigned int fmt)
+{
- dbg("tlv320_set_dai_fmt enter");
- return 0;
+}
+/*
- Set PCM DAI bit size and sample rate.
- */
+static int tlv320_pcm_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+{
- dbg("tlv320_pcm_hw_params enter");
- return 0;
+}
+static int tlv320_config_pcm_sysclk(struct snd_soc_codec_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
+{
- dbg("tlv320_config_pcm_sysclk enter");
- return 0;
+}
+/*
- Voice over PCM DAI
- */
+struct snd_soc_codec_dai tlv320_dai[] = { +{ .name = "TLV320 Voice",
- .id = 1,
- .playback = {
.stream_name = "Voice Playback",
.channels_min = 1,
.channels_max = 2,
.rates = TLV320_VOICE_RATES,
.formats = TLV320_VOICE_BITS,},
- .capture = {
.stream_name = "Voice Capture",
.channels_min = 1,
.channels_max = 2,
.rates = TLV320_VOICE_RATES,
.formats = TLV320_VOICE_BITS,},
- .ops = {
.hw_params = tlv320_pcm_hw_params,},
- .dai_ops = {
.digital_mute = NULL,
.set_fmt = tlv320_set_dai_fmt,
.set_clkdiv = NULL,
.set_pll = NULL,
.set_sysclk = tlv320_config_pcm_sysclk,
- },
+},
+}; +EXPORT_SYMBOL_GPL(tlv320_dai);
+static void tlv320_work(struct work_struct *work) +{ +#if 0
- struct snd_soc_codec *codec =
container_of(work, struct snd_soc_codec, delayed_work.work);
- //wm8753_dapm_event(codec, codec->dapm_state);
+#endif +}
+/*
- initialise the TLV320 driver
- register the mixer and dsp interfaces with the kernel
- */
+static int tlv320_init(struct snd_soc_device *socdev) +{
- struct snd_soc_codec *codec = socdev->codec;
- int ret = 0;
- codec->name = "TLV320";
- codec->owner = THIS_MODULE;
- codec->read = tlv320_read_reg_cache;
- codec->write = tlv320_write;
- codec->dai = tlv320_dai;
- codec->num_dai = ARRAY_SIZE(tlv320_dai);
- codec->reg_cache_size = sizeof(tlv320_reg_addr);
- codec->reg_cache =
kmemdup(tlv320_reg_addr, sizeof(tlv320_reg_addr), GFP_KERNEL);
- if (codec->reg_cache == NULL)
return -ENOMEM;
- /* register pcms */
- ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
- if (ret < 0) {
kfree(codec->reg_cache);
return ret;
- }
- queue_delayed_work(tlv320_workq,
&codec->delayed_work, msecs_to_jiffies(caps_charge));
- ret = snd_soc_register_card(socdev);
- if (ret < 0) {
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
- }
- return ret;
+}
+/* If the i2c layer weren't so broken, we could pass this kind of data
- around */
+static struct snd_soc_device *tlv320_socdev;
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD;
+static struct i2c_driver tlv320_i2c_driver; +static struct i2c_client client_template;
+static int tlv320_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{
- struct snd_soc_device *socdev = tlv320_socdev;
- struct tlv320_setup_data *setup = socdev->codec_data;
- struct snd_soc_codec *codec = socdev->codec;
- struct i2c_client *i2c;
- int ret, len;
const unsigned char *data;
- if (addr != setup->i2c_address)
return -ENODEV;
- client_template.adapter = adap;
- client_template.addr = addr;
- i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
- if (i2c == NULL){
kfree(codec);
return -ENOMEM;
- }
- i2c_set_clientdata(i2c, codec);
- codec->control_data = i2c;
- ret = i2c_attach_client(i2c);
- if (ret < 0) {
err("failed to attach codec at addr %x\n", addr);
goto err;
- }
- ret = tlv320_init(socdev);
- if (ret < 0) {
err("failed to initialise TLV320\n");
goto err;
- }
- switch(setting) {
- case 1:
data = tlv320_reg_data_init_set1;
len = sizeof(tlv320_reg_data_init_set1);
break;
- case 2:
data = tlv320_reg_data_init_set2;
len = sizeof(tlv320_reg_data_init_set2);
break;
- case 3:
data = tlv320_reg_data_init_set3;
len = sizeof(tlv320_reg_data_init_set3);
break;
- default:
data = tlv320_reg_data_init_set1;
len = sizeof(tlv320_reg_data_init_set1);
break;
- }
- ret = tlv320_write_block(codec, data, len);
- if (ret < 0) {
err("attach error: init status %d\n", ret);
- } else {
info("attach: chip tlv320 at address 0x%02x",
tlv320_read(codec, 0x02) << 1);
- }
//tlv320_write(codec, CODEC_REG6B, 0x80);
+#if 0
- int value;
- int i;
- for (i=0; i<len; i++) {
value = tlv320_read(codec, tlv320_reg_addr[i]);
dbg("read addr = 0x%02x, data = 0x%02x", tlv320_reg_addr[i], value);
mdelay(10);
- }
+#endif
- return ret;
+err:
- kfree(codec);
- kfree(i2c);
- return ret;
+}
+static int tlv320_i2c_detach(struct i2c_client *client) +{
- struct snd_soc_codec *codec = i2c_get_clientdata(client);
- i2c_detach_client(client);
- kfree(codec->reg_cache);
- kfree(client);
- return 0;
+}
+static int tlv320_i2c_attach(struct i2c_adapter *adap) +{
- return i2c_probe(adap, &addr_data, tlv320_codec_probe);
+}
+/* tlv320 i2c codec control layer */ +static struct i2c_driver tlv320_i2c_driver = {
- .driver = {
.name = "tlv320 I2C Codec",
.owner = THIS_MODULE,
- },
- .id = I2C_DRIVERID_TLV320,
- .attach_adapter = tlv320_i2c_attach,
- .detach_client = tlv320_i2c_detach,
- .command = NULL,
+};
+static struct i2c_client client_template = {
- .name = "tlv320",
- .driver = &tlv320_i2c_driver,
+}; +#endif
+static int tlv320_probe(struct platform_device *pdev) +{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct tlv320_setup_data *setup;
- struct snd_soc_codec *codec;
- int ret = 0;
- struct tlv320_priv *tlv320;
- info("TLV320 Audio Codec %s", TLV320_VERSION);
- setup = socdev->codec_data;
- codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (codec == NULL)
return -ENOMEM;
- tlv320 = kzalloc(sizeof(struct tlv320_priv), GFP_KERNEL);
- if (tlv320 == NULL) {
kfree(codec);
return -ENOMEM;
- }
- codec->private_data = tlv320;
- socdev->codec = codec;
- mutex_init(&codec->mutex);
- INIT_LIST_HEAD(&codec->dapm_widgets);
- INIT_LIST_HEAD(&codec->dapm_paths);
- tlv320_socdev = socdev;
- INIT_DELAYED_WORK(&codec->delayed_work, tlv320_work);
- tlv320_workq = create_workqueue("tlv320");
- if (tlv320_workq == NULL) {
kfree(codec);
return -ENOMEM;
- }
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
- if (setup->i2c_address) {
normal_i2c[0] = setup->i2c_address;
codec->hw_write = (hw_write_t)i2c_master_send;
ret = i2c_add_driver(&tlv320_i2c_driver);
if (ret != 0)
printk(KERN_ERR "can't add i2c driver");
- }
+#else
/* Add other interfaces here */
+#endif
- return ret;
+}
+/* power down chip */ +static int tlv320_remove(struct platform_device *pdev) +{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
- if (tlv320_workq)
destroy_workqueue(tlv320_workq);
- snd_soc_free_pcms(socdev);
- snd_soc_dapm_free(socdev);
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
- i2c_del_driver(&tlv320_i2c_driver);
+#endif
- kfree(codec->private_data);
- kfree(codec);
- return 0;
+}
+struct snd_soc_codec_device soc_codec_dev_tlv320 = {
- .probe = tlv320_probe,
- .remove = tlv320_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320);
+MODULE_DESCRIPTION("ASoC TLV320 driver"); +MODULE_AUTHOR("Nicola Perrino"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320.h b/sound/soc/codecs/tlv320.h new file mode 100644 index 0000000..bbcd53d --- /dev/null +++ b/sound/soc/codecs/tlv320.h @@ -0,0 +1,111 @@ +/*
- tlv320.h -- TLV 320 ALSA Soc Audio driver
- Copyright 2005 Wolfson Microelectronics PLC.
- Copyright 2006 Atlab srl.
- Authors: Liam Girdwood liam.girdwood@wolfsonmicro.com
Nicola Perrino <nicola.perrino@atlab.it>
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- */
+#ifndef _TLV320_H +#define _TLV320_H
+#define TLV320AIC24K
+/* TLV320 register space */ +#define CODEC_NOOP 0x00 +#define CODEC_REG1 0x01 +#define CODEC_REG2 0x02 +#define CODEC_REG3A 0x03 +#define CODEC_REG3B 0x04 +#define CODEC_REG3C 0x05 +#define CODEC_REG3D 0x06 +#define CODEC_REG4A 0x07 +#define CODEC_REG4B 0x08 +#define CODEC_REG5A 0x09 +#define CODEC_REG5B 0x0a +#define CODEC_REG5C 0x0b +#define CODEC_REG5D 0x0c +#define CODEC_REG6A 0x0d +#define CODEC_REG6B 0x0e
+// Control Register 1 +#define REG1_CONTINUOUS 0x40 +#define REG1_IIR_EN 0x20 +#define REG1_MIC_BIAS_235 0x08 +#define REG1_ANALOG_LOOP_BACK 0x04 +#define REG1_DIGITAL_LOOP_BACK 0x02 +#define REG1_DAC16 0x01
+// Control Register 2 +#define REG2_TURBO_EN 0x80 +#define REG2_FIR_BYPASS 0x40 +#define REG2_GPIO 0x02 +#define REG2_GPIO_1 0x06
+// Control Register 3A +#define REG3_PWDN_ALL 0x30 +#define REG3_PWDN_ADC 0x10 +#define REG3_PWDN_DAC 0x20 +#define REG3_SW_RESET 0x08 +#define REG3_SAMPLING_FACTOR1 0x01 +#define REG3_SAMPLING_FACTOR2 0x02
+// Control Register 3B +#define REG3_8KBP_EN 0x60 +#define REG3_MUTE_OUTP1 0x42 +#define REG3_MUTE_OUTP2 0x48 +#define REG3_MUTE_OUTP3 0x44
+// Control Register 4 +#define REG4_FSDIV_M 0x85 //M=5 +#define REG4_FSDIV_NP 0x08 //N=1, P=8 +//#define REG4_FSDIV_NP 0x01 //N=1, P=8 +#define REG4_FSDIV_NP1 0x02 //N=16, P=2
+// Control Register 5 +#define REG5A_ADC_GAIN 0x02 //3dB +#define REG5A_ADC_MUTE 0x0f //Mute +#define REG5B_DAC_GAIN 0x42 //-3dB +#define REG5B_DAC_MUTE 0x4f //Mute +#define REG5C_SIDETONE_MUTE 0xBF
+// Control Register 6 +#define REG6A_AIC24A_CH1_IN 0x08 //INP1 to ADC +#define REG6B_AIC24A_CH1_OUT 0x82 //OUTP2 to DAC +#define REG6A_AIC24A_CH2_IN 0x02 //INP2 to ADC +#define REG6B_AIC24A_CH2_OUT 0x81 //OUTP3 to DAC
+/* clock inputs */ +#define TLV320_MCLK 0 +#define TLV320_PCMCLK 1
+struct tlv320_setup_data {
- unsigned short i2c_address;
+};
+/* DAI ifmodes */ +/* mode 1 IFMODE = 00 */ +#define TLV320_DAI_MODE1_VOICE 0 +#define TLV320_DAI_MODE1_HIFI 1 +/* mode 2 IFMODE = 01 */ +#define TLV320_DAI_MODE2_VOICE 2 +/* mode 3 IFMODE = 10 */ +#define TLV320_DAI_MODE3_HIFI 3 +/* mode 4 IFMODE = 11 */ +#define TLV320_DAI_MODE4_HIFI 4
+extern struct snd_soc_codec_dai tlv320_dai[5]; +extern struct snd_soc_codec_device soc_codec_dev_tlv320;
+#endif
At Tue, 20 Nov 2007 09:25:38 +0000, Mark Brown wrote:
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e0675ef..8244107 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2,6 +2,10 @@ config SND_SOC_AC97_CODEC tristate depends on SND_SOC
+config SND_SOC_AK4535
- tristate
- depends on SND_SOC
Unless you add another interface support, this driver seems only for I2C. Then let's add dependency on I2C (which will avoid a possible unresolved symbol error).
--- /dev/null +++ b/sound/soc/codecs/ak4535.c
ak4535.c has some obvious coding-style issues. Try to run scripts/checkpatch.pl and fix the reported parts.
Takashi
On Tue, Nov 20, 2007 at 11:40:25AM +0100, Takashi Iwai wrote:
Mark Brown wrote:
+config SND_SOC_AK4535
- tristate
- depends on SND_SOC
Unless you add another interface support, this driver seems only for I2C. Then let's add dependency on I2C (which will avoid a possible unresolved symbol error).
The idiom used by the rest of the ASoC codecs appears to be to omit the bus dependencies entirely from the codec drivers and rely on the machine driver pulling in the appropriate bus driver. For the sake of consistency I'd rather leave this just now and then separately go through and address this for all drivers at once - does that seem sensible to you?
--- /dev/null +++ b/sound/soc/codecs/ak4535.c
ak4535.c has some obvious coding-style issues. Try to run scripts/checkpatch.pl and fix the reported parts.
I've fixed up the mechanical issues for next submission.
At Tue, 20 Nov 2007 15:05:30 +0000, Mark Brown wrote:
On Tue, Nov 20, 2007 at 11:40:25AM +0100, Takashi Iwai wrote:
Mark Brown wrote:
+config SND_SOC_AK4535
- tristate
- depends on SND_SOC
Unless you add another interface support, this driver seems only for I2C. Then let's add dependency on I2C (which will avoid a possible unresolved symbol error).
The idiom used by the rest of the ASoC codecs appears to be to omit the bus dependencies entirely from the codec drivers and rely on the machine driver pulling in the appropriate bus driver. For the sake of consistency I'd rather leave this just now and then separately go through and address this for all drivers at once - does that seem sensible to you?
Well, the problem is that the dependency with I2C is incomplete right now. We may build ASOC=y and I2C=m, and you'll get unresolved symbols due to call of I2C function from asoc.
I'm not sure what is the proper way to fix this. If the codec driver supports non-I2C and can be built without it, the explicit dependency on I2C is of course wrong.
Takashi
On Tue, Nov 20, 2007 at 03:42:17PM +0100, Takashi Iwai wrote:
Mark Brown wrote:
consistency I'd rather leave this just now and then separately go through and address this for all drivers at once - does that seem sensible to you?
Well, the problem is that the dependency with I2C is incomplete right now. We may build ASOC=y and I2C=m, and you'll get unresolved symbols due to call of I2C function from asoc.
Yes, I agree that this would be an improvement. I'd just like to do it for all the drivers at once rather than have some of the drivers set up one way and some another.
I'm not sure what is the proper way to fix this. If the codec driver supports non-I2C and can be built without it, the explicit dependency on I2C is of course wrong.
Yes, and of course users will still need to ensure that the appropriate bus driver for their platform is availiable on their system, though that could be loaded later. Still, we could make life a little easier for some users so it seems a worthwhile thing to do even though it doesn't solve everything.
At Tue, 20 Nov 2007 15:35:41 +0000, Mark Brown wrote:
On Tue, Nov 20, 2007 at 03:42:17PM +0100, Takashi Iwai wrote:
Mark Brown wrote:
consistency I'd rather leave this just now and then separately go through and address this for all drivers at once - does that seem sensible to you?
Well, the problem is that the dependency with I2C is incomplete right now. We may build ASOC=y and I2C=m, and you'll get unresolved symbols due to call of I2C function from asoc.
Yes, I agree that this would be an improvement. I'd just like to do it for all the drivers at once rather than have some of the drivers set up one way and some another.
Fair enough.
I'm not sure what is the proper way to fix this. If the codec driver supports non-I2C and can be built without it, the explicit dependency on I2C is of course wrong.
Yes, and of course users will still need to ensure that the appropriate bus driver for their platform is availiable on their system, though that could be loaded later. Still, we could make life a little easier for some users so it seems a worthwhile thing to do even though it doesn't solve everything.
Agreed.
Takashi
At Tue, 20 Nov 2007 09:25:37 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
sound/soc/codecs/wm8753.c | 10 ++++++++-- 1 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index efced93..8f2a6d1 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -48,6 +48,7 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include <asm/div64.h>
#include "wm8753.h" @@ -258,6 +259,11 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, return 1; }
+static const unsigned int rec_mix_tlv[] = {
- TLV_DB_RANGE_HEAD(1),
- 0,7, TLV_DB_LINEAR_ITEM(-1500, 600),
+};
You don't need TLV_DB_DB_RANGE_HEAD() for a single range. TLV_DB_RANGE*() is for multiple ranges. Simply use like below: static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -500, 600);
Takashi
static const struct snd_kcontrol_new wm8753_snd_controls[] = { SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0),
@@ -287,8 +293,8 @@ SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8753_enum[2]),
-SOC_DOUBLE("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1), -SOC_SINGLE("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1), +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, rec_mix_tlv),
SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), -- 1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Nov 20, 2007 at 11:15:14AM +0100, Takashi Iwai wrote:
You don't need TLV_DB_DB_RANGE_HEAD() for a single range. TLV_DB_RANGE*() is for multiple ranges. Simply use like below: static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -500, 600);
Thanks, I've fixed this for the next submission.
At Tue, 20 Nov 2007 09:25:36 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
I suppose the author is Liam, right? It's a bit strange that his sign-off appears in the middle, then...
Signed-off-by: Marc Mulcahy marc@levelstar.com Signed-off-by: Graeme Gregory gg@opensource.wolfsonmicro.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
(snip)
+static int mixer_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
+{
...
- if (l & 0x1 || r & 0x1)
I'd suggest to put parentheses around bit-ops.
+static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai,
int div_id, int div)
+{
...
+};
Remove semicolon.
+#define WM9713_PCM_FORMATS \
- (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
SNDRV_PCM_FORMAT_S24_LE)
Is it S24_LE or S24_3LE? I see S24_LE in other codec drivers, but am not pretty sure whether I checked it...
Takashi
Takashi Iwai wrote:
Mark Brown wrote:
+#define WM9713_PCM_FORMATS \
- (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
SNDRV_PCM_FORMAT_S24_LE)
Is it S24_LE or S24_3LE? I see S24_LE in other codec drivers, but am not pretty sure whether I checked it...
Apparently, these are the formats that an AC'97 controller connected with this codec can support.
Samples on the AC'97 bus are always MSB-aligned with at most 20 bits. I don't think it makes much sense to let the codec driver decide what sample formats the controller should support, because the controller will have to convert the samples anyway (from/to 16, 24 or 32 bits). Furthermore, I'm sure there are AC'97 controllers that can handle big- endian samples on the memory bus.
Regards, Clemens
On Tue, Nov 20, 2007 at 01:02:00PM +0100, Clemens Ladisch wrote:
Takashi Iwai wrote:
Is it S24_LE or S24_3LE? I see S24_LE in other codec drivers, but am not pretty sure whether I checked it...
I think it's correct but I'll double check before resubmitting.
Apparently, these are the formats that an AC'97 controller connected with this codec can support.
This define is not used for the AC'97 interface of the chip - it is only used for the PCM interface which the chip supports in addition to its AC'97 interface.
At Tue, 20 Nov 2007 09:25:35 +0000, Mark Brown wrote:
include/sound/soc.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
No author and sign-off? It's trivial so I don't care much, though.
Takashi
diff --git a/include/sound/soc.h b/include/sound/soc.h index e32fa2f..1c22931 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -22,7 +22,7 @@ #include <sound/control.h> #include <sound/ac97_codec.h>
-#define SND_SOC_VERSION "0.13.1" +#define SND_SOC_VERSION "0.13.2"
/*
- Convenience kcontrol builders
-- 1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
At Tue, 20 Nov 2007 09:25:31 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
This one has no sign-off and local user only...
Takashi
This fixes a bug whereby PCMs were not being suspended when the rest of the audio subsystem was suspended.
include/sound/soc.h | 3 +++ sound/soc/soc-core.c | 5 +++++ 2 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/include/sound/soc.h b/include/sound/soc.h index c69b58c..e32fa2f 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -448,6 +448,9 @@ struct snd_soc_dai_link {
/* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec);
- /* DAI pcm */
- struct snd_pcm *pcm;
};
/* SoC machine */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f3e1b74..7c41212 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -646,6 +646,10 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) dai->dai_ops.digital_mute(dai, 1); }
- /* suspend all pcms */
- for (i = 0; i < machine->num_links; i++)
snd_pcm_suspend_all(machine->dai_link[i].pcm);
- if (machine->suspend_pre) machine->suspend_pre(pdev, state);
@@ -880,6 +884,7 @@ static int soc_new_pcm(struct snd_soc_device *socdev, return ret; }
- dai_link->pcm = pcm; pcm->private_data = rtd; soc_pcm_ops.mmap = socdev->platform->pcm_ops->mmap; soc_pcm_ops.pointer = socdev->platform->pcm_ops->pointer;
-- 1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
At Tue, 20 Nov 2007 09:25:30 +0000, Mark Brown wrote:
From: Liam Girdwood liam@localhost.localdomain
Looks suspicious. Also, sign-off is missing.
Takashi
sound/soc/soc-core.c | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 265bd55..f3e1b74 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -288,6 +288,14 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) {
/* power down the codec to D1 if no longer active */
if (codec->active == 0) {
dbg("pop wq D1 %s %s\n", codec->name,
codec_dai->playback.stream_name);
snd_soc_dapm_device_event(socdev,
SNDRV_CTL_POWER_D1);
}
codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name,
-- 1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
At Tue, 20 Nov 2007 09:25:27 +0000, Mark Brown wrote:
From: Joe Sauer jsauer@vernier.com
Signed-off-by: Joe Sauer jsauer@vernier.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
We have already a patch to fix Mono and Bass/Treble on ALSA HG tree but only Phone volume seems missing. Is it right?
Takashi
sound/soc/codecs/wm9712.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 986b5d5..111c266 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -102,7 +102,7 @@ SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), -SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), @@ -131,7 +131,7 @@ SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
-SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 0), +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), @@ -145,8 +145,8 @@ SOC_ENUM("Bass Control", wm9712_enum[5]), SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), -SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 0), -SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1), SOC_ENUM("Capture Volume Steps", wm9712_enum[6]), -- 1.5.3.5
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Nov 20, 2007 at 11:07:13AM +0100, Takashi Iwai wrote:
We have already a patch to fix Mono and Bass/Treble on ALSA HG tree but only Phone volume seems missing. Is it right?
Yes, that's right - the same fix was made independantly to ALSA and the ASoC tree with these extra changes appearing in the version that was submitted to ASoC.
Mark Brown wrote:
From: Joe Sauer jsauer@vernier.com
Signed-off-by: Joe Sauer jsauer@vernier.com Signed-off-by: Liam Girdwood lg@opensource.wolfsonmicro.com Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
sound/soc/codecs/wm9712.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 986b5d5..111c266 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -102,7 +102,7 @@ SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), -SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
I think there also should be "Mono Playback Switch", otherwise there's no way you can unmute mono playback: + SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), @@ -131,7 +131,7 @@ SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
-SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 0), +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), @@ -145,8 +145,8 @@ SOC_ENUM("Bass Control", wm9712_enum[5]), SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), -SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 0), -SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1), SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
On Tue, Nov 20, 2007 at 02:08:33PM +0200, Mike Rapoport wrote:
I think there also should be "Mono Playback Switch", otherwise there's no way you can unmute mono playback:
- SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
Yes, I'll add that hunk to the patch. It's already in our development branch.
At Tue, 20 Nov 2007 09:25:23 +0000, Mark Brown wrote:
The following patch series merges some of the outstanding changes in the linux-2.6-asoc tree. It updates the core and existing drivers and adds some of the new drivers currently in the tree.
Thanks. Could you add the patch sequence number to each patch at the next time (if the order matters)?
Takashi
On Tue, Nov 20, 2007 at 11:19:41AM +0100, Takashi Iwai wrote:
Thanks. Could you add the patch sequence number to each patch at the next time (if the order matters)?
Yes, sorry - I'll look into that. I have to say that I was surprised when I saw that git-send-email hadn't generated them.
On 11/20/07, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
The following patch series merges some of the outstanding changes in the linux-2.6-asoc tree. It updates the core and existing drivers and adds some of the new drivers currently in the tree.
In asoc v2 soc-core is changed from being a platform driver into subsys_initcall(asoc_bus_init); Is this change going in anytime soon? subsys call is the right way, the alsa core is not a platform device. This will bring asoc into alignment with i2c which already uses subsys_initcall.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Nov 20, 2007 at 09:59:15AM -0500, Jon Smirl wrote:
In asoc v2 soc-core is changed from being a platform driver into subsys_initcall(asoc_bus_init); Is this change going in anytime soon? subsys call is the right way, the alsa core is not a platform device. This will bring asoc into alignment with i2c which already uses subsys_initcall.
I can't give any actual hard timescales for anything but this merge is a first step on the way to doing that. My plan was to try to get the pending ASoC v1 changes merged into mainline so that there is only one place to keep track of when merging into ASoC v2 development.
On 11/20/07, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Nov 20, 2007 at 09:59:15AM -0500, Jon Smirl wrote:
In asoc v2 soc-core is changed from being a platform driver into subsys_initcall(asoc_bus_init); Is this change going in anytime soon? subsys call is the right way, the alsa core is not a platform device. This will bring asoc into alignment with i2c which already uses subsys_initcall.
I can't give any actual hard timescales for anything but this merge is a first step on the way to doing that. My plan was to try to get the pending ASoC v1 changes merged into mainline so that there is only one place to keep track of when merging into ASoC v2 development.
I've got a patch for the PowerPC that removes platform bus, it's of_platform bus on the PowerPC. Removing platform bus exposes all kinds of things that shouldn't be on it, like the ASoC core.
participants (9)
-
Clemens Ladisch
-
Jean Delvare
-
Jon Smirl
-
Mark Brown
-
Mark Brown
-
Mike Rapoport
-
pHilipp Zabel
-
Takashi Iwai
-
Vladimir A. Barinov