summaryrefslogtreecommitdiff
path: root/sound/soc
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc')
-rw-r--r--sound/soc/Kconfig18
-rw-r--r--sound/soc/Makefile2
-rw-r--r--sound/soc/at91/Kconfig10
-rw-r--r--sound/soc/at91/Makefile4
-rw-r--r--sound/soc/at91/at91-ssc.c (renamed from sound/soc/at91/at91-i2s.c)259
-rw-r--r--sound/soc/at91/at91-ssc.h (renamed from sound/soc/at91/at91-i2s.h)14
-rw-r--r--sound/soc/at91/eti_b1_wm8731.c8
-rw-r--r--sound/soc/codecs/Kconfig4
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/ac97.c1
-rw-r--r--sound/soc/codecs/ac97.h1
-rw-r--r--sound/soc/codecs/wm8750.c2
-rw-r--r--sound/soc/codecs/wm8753.c1811
-rw-r--r--sound/soc/codecs/wm8753.h126
-rw-r--r--sound/soc/codecs/wm9712.c7
-rw-r--r--sound/soc/pxa/Kconfig4
-rw-r--r--sound/soc/s3c24xx/Kconfig10
-rw-r--r--sound/soc/s3c24xx/Makefile6
-rw-r--r--sound/soc/s3c24xx/s3c24xx-i2s.c441
-rw-r--r--sound/soc/s3c24xx/s3c24xx-i2s.h37
-rw-r--r--sound/soc/s3c24xx/s3c24xx-pcm.c468
-rw-r--r--sound/soc/s3c24xx/s3c24xx-pcm.h31
-rw-r--r--sound/soc/soc-dapm.c6
23 files changed, 3137 insertions, 135 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index dccaa4be679e..10cffc087181 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -2,31 +2,31 @@
# SoC audio configuration
#
-menu "SoC audio support"
+menu "System on Chip audio support"
depends on SND!=n
config SND_SOC_AC97_BUS
bool
config SND_SOC
- tristate "SoC audio support"
+ tristate "ALSA for SoC audio support"
depends on SND
select SND_PCM
---help---
- If you want SoC support, you should say Y here and also to the
- specific driver for your SoC below. You will also need to select the
- specific codec(s) attached to the SoC
+ If you want ASoC support, you should say Y here and also to the
+ specific driver for your SoC platform below.
+
+ ASoC provides power efficient ALSA support for embedded battery powered
+ SoC based systems like PDA's, Phones and Personal Media Players.
- This SoC audio support can also be built as a module. If so, the module
+ This ASoC audio support can also be built as a module. If so, the module
will be called snd-soc-core.
# All the supported Soc's
-menu "SoC Platforms"
-depends on SND_SOC
source "sound/soc/at91/Kconfig"
source "sound/soc/pxa/Kconfig"
-endmenu
+source "sound/soc/s3c24xx/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 98e6f49dafc2..0ae2e49036f9 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -1,4 +1,4 @@
snd-soc-core-objs := soc-core.o soc-dapm.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
-obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/
+obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/
diff --git a/sound/soc/at91/Kconfig b/sound/soc/at91/Kconfig
index a5b2558916c1..5cb93fd3a407 100644
--- a/sound/soc/at91/Kconfig
+++ b/sound/soc/at91/Kconfig
@@ -1,5 +1,3 @@
-menu "SoC Audio for the Atmel AT91"
-
config SND_AT91_SOC
tristate "SoC Audio for the Atmel AT91 System-on-Chip"
depends on ARCH_AT91 && SND_SOC
@@ -8,13 +6,13 @@ config SND_AT91_SOC
the AT91 SSC interface. You will also need
to select the audio interfaces to support below.
-config SND_AT91_SOC_I2S
+config SND_AT91_SOC_SSC
tristate
config SND_AT91_SOC_ETI_B1_WM8731
- tristate "SoC I2S Audio support for WM8731-based Endrelia ETI-B1 boards"
+ tristate "SoC Audio support for WM8731-based Endrelia ETI-B1 boards"
depends on SND_AT91_SOC && (MACH_ETI_B1 || MACH_ETI_C1)
- select SND_AT91_SOC_I2S
+ select SND_AT91_SOC_SSC
select SND_SOC_WM8731
help
Say Y if you want to add support for SoC audio on WM8731-based
@@ -27,5 +25,3 @@ config SND_AT91_SOC_ETI_SLAVE
help
Say Y if you want to run with the AT91 SSC generating the BCLK
and LRC signals on Endrelia boards.
-
-endmenu
diff --git a/sound/soc/at91/Makefile b/sound/soc/at91/Makefile
index b77b01ab2028..f23da17cc328 100644
--- a/sound/soc/at91/Makefile
+++ b/sound/soc/at91/Makefile
@@ -1,9 +1,9 @@
# AT91 Platform Support
snd-soc-at91-objs := at91-pcm.o
-snd-soc-at91-i2s-objs := at91-i2s.o
+snd-soc-at91-ssc-objs := at91-ssc.o
obj-$(CONFIG_SND_AT91_SOC) += snd-soc-at91.o
-obj-$(CONFIG_SND_AT91_SOC_I2S) += snd-soc-at91-i2s.o
+obj-$(CONFIG_SND_AT91_SOC_SSC) += snd-soc-at91-ssc.o
# AT91 Machine Support
snd-soc-eti-b1-wm8731-objs := eti_b1_wm8731.o
diff --git a/sound/soc/at91/at91-i2s.c b/sound/soc/at91/at91-ssc.c
index 9fc0c0388881..3d4e32cff75e 100644
--- a/sound/soc/at91/at91-i2s.c
+++ b/sound/soc/at91/at91-ssc.c
@@ -1,5 +1,5 @@
/*
- * at91-i2s.c -- ALSA SoC I2S Audio Layer Platform driver
+ * at91-ssc.c -- ALSA SoC AT91 SSC Audio Layer Platform driver
*
* Author: Frank Mandarino <fmandarino@endrelia.com>
* Endrelia Technologies Inc.
@@ -25,6 +25,7 @@
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
@@ -33,10 +34,10 @@
#include <asm/arch/at91_ssc.h>
#include "at91-pcm.h"
-#include "at91-i2s.h"
+#include "at91-ssc.h"
#if 0
-#define DBG(x...) printk(KERN_DEBUG "at91-i2s:" x)
+#define DBG(x...) printk(KERN_DEBUG "at91-ssc:" x)
#else
#define DBG(x...)
#endif
@@ -92,33 +93,33 @@ static struct at91_ssc_mask ssc_rx_mask = {
*/
static struct at91_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
{{
- .name = "SSC0/I2S PCM Stereo out",
+ .name = "SSC0 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
- .name = "SSC0/I2S PCM Stereo in",
+ .name = "SSC0 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
}},
#if NUM_SSC_DEVICES == 3
{{
- .name = "SSC1/I2S PCM Stereo out",
+ .name = "SSC1 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
- .name = "SSC1/I2S PCM Stereo in",
+ .name = "SSC1 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
}},
{{
- .name = "SSC2/I2S PCM Stereo out",
+ .name = "SSC2 PCM out",
.pdc = &pdc_tx_reg,
.mask = &ssc_tx_mask,
},
{
- .name = "SSC1/I2S PCM Stereo in",
+ .name = "SSC2 PCM in",
.pdc = &pdc_rx_reg,
.mask = &ssc_rx_mask,
}},
@@ -151,33 +152,33 @@ static struct at91_ssc_info {
} ssc_info[NUM_SSC_DEVICES] = {
{
.name = "ssc0",
- .lock = SPIN_LOCK_UNLOCKED,
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
.dir_mask = 0,
.initialized = 0,
},
#if NUM_SSC_DEVICES == 3
{
.name = "ssc1",
- .lock = SPIN_LOCK_UNLOCKED,
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
.dir_mask = 0,
.initialized = 0,
},
{
.name = "ssc2",
- .lock = SPIN_LOCK_UNLOCKED,
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
.dir_mask = 0,
.initialized = 0,
},
#endif
};
-static unsigned int at91_i2s_sysclk;
+static unsigned int at91_ssc_sysclk;
/*
* SSC interrupt handler. Passes PDC interrupts to the DMA
* interrupt handler in the PCM driver.
*/
-static irqreturn_t at91_i2s_interrupt(int irq, void *dev_id)
+static irqreturn_t at91_ssc_interrupt(int irq, void *dev_id)
{
struct at91_ssc_info *ssc_p = dev_id;
struct at91_pcm_dma_params *dma_params;
@@ -209,13 +210,13 @@ static irqreturn_t at91_i2s_interrupt(int irq, void *dev_id)
/*
* Startup. Only that one substream allowed in each direction.
*/
-static int at91_i2s_startup(struct snd_pcm_substream *substream)
+static int at91_ssc_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
int dir_mask;
- DBG("i2s_startup: SSC_SR=0x%08lx\n",
+ DBG("ssc_startup: SSC_SR=0x%08lx\n",
at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR));
dir_mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0x1 : 0x2;
@@ -234,7 +235,7 @@ static int at91_i2s_startup(struct snd_pcm_substream *substream)
* Shutdown. Clear DMA parameters and shutdown the SSC if there
* are no other substreams open.
*/
-static void at91_i2s_shutdown(struct snd_pcm_substream *substream)
+static void at91_ssc_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
@@ -281,7 +282,7 @@ static void at91_i2s_shutdown(struct snd_pcm_substream *substream)
/*
* Record the SSC system clock rate.
*/
-static int at91_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai,
+static int at91_ssc_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
/*
@@ -291,7 +292,7 @@ static int at91_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai,
*/
switch (clk_id) {
case AT91_SYSCLK_MCK:
- at91_i2s_sysclk = freq;
+ at91_ssc_sysclk = freq;
break;
default:
return -EINVAL;
@@ -303,14 +304,11 @@ static int at91_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai,
/*
* Record the DAI format for use in hw_params().
*/
-static int at91_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai,
+static int at91_ssc_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai,
unsigned int fmt)
{
struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
- if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S)
- return -EINVAL;
-
ssc_p->daifmt = fmt;
return 0;
}
@@ -318,7 +316,7 @@ static int at91_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai,
/*
* Record SSC clock dividers for use in hw_params().
*/
-static int at91_i2s_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
+static int at91_ssc_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
int div_id, int div)
{
struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
@@ -355,7 +353,7 @@ static int at91_i2s_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
/*
* Configure the SSC.
*/
-static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
+static int at91_ssc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
@@ -391,20 +389,50 @@ static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
channels = params_channels(params);
/*
+ * Determine sample size in bits and the PDC increment.
+ */
+ switch(params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ bits = 8;
+ dma_params->pdc_xfer_size = 1;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bits = 16;
+ dma_params->pdc_xfer_size = 2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bits = 24;
+ dma_params->pdc_xfer_size = 4;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bits = 32;
+ dma_params->pdc_xfer_size = 4;
+ break;
+ default:
+ printk(KERN_WARNING "at91-ssc: unsupported PCM format");
+ return -EINVAL;
+ }
+
+ /*
* The SSC only supports up to 16-bit samples in I2S format, due
- * to the size of the Frame Mode Register FSLEN field. Also, I2S
- * implies signed data.
+ * to the size of the Frame Mode Register FSLEN field.
*/
- bits = 16;
- dma_params->pdc_xfer_size = 2;
+ if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
+ && bits > 16) {
+ printk(KERN_WARNING
+ "at91-ssc: sample size %d is too large for I2S\n", bits);
+ return -EINVAL;
+ }
/*
* Compute SSC register settings.
*/
- switch (ssc_p->daifmt) {
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (ssc_p->daifmt
+ & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
/*
- * SSC provides BCLK and LRC clocks.
+ * I2S format, SSC provides BCLK and LRC clocks.
*
* The SSC transmit and receive clocks are generated from the
* MCK divider, and the BCLK signal is output on the SSC TK line.
@@ -441,10 +469,9 @@ static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
| (((bits - 1) << 0) & AT91_SSC_DATALEN);
break;
- case SND_SOC_DAIFMT_CBM_CFM:
-
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
/*
- * CODEC supplies BCLK and LRC clocks.
+ * I2S format, CODEC supplies BCLK and LRC clocks.
*
* The SSC transmit clock is obtained from the BCLK signal on
* on the TK line, and the SSC receive clock is generated from the
@@ -490,10 +517,51 @@ static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
| (((bits - 1) << 0) & AT91_SSC_DATALEN);
break;
- case SND_SOC_DAIFMT_CBS_CFM:
- case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks.
+ *
+ * The SSC transmit and receive clocks are generated from the
+ * MCK divider, and the BCLK signal is output on the SSC TK line.
+ */
+ rcmr = (( ssc_p->rcmr_period << 24) & AT91_SSC_PERIOD)
+ | (( 1 << 16) & AT91_SSC_STTDLY)
+ | (( AT91_SSC_START_RISING_RF ) & AT91_SSC_START)
+ | (( AT91_SSC_CK_RISING ) & AT91_SSC_CKI)
+ | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO)
+ | (( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS);
+
+ rfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE)
+ | (( AT91_SSC_FSOS_POSITIVE ) & AT91_SSC_FSOS)
+ | (( 0 << 16) & AT91_SSC_FSLEN)
+ | (((channels - 1) << 8) & AT91_SSC_DATNB)
+ | (( 1 << 7) & AT91_SSC_MSBF)
+ | (( 0 << 5) & AT91_SSC_LOOP)
+ | (((bits - 1) << 0) & AT91_SSC_DATALEN);
+
+ tcmr = (( ssc_p->tcmr_period << 24) & AT91_SSC_PERIOD)
+ | (( 1 << 16) & AT91_SSC_STTDLY)
+ | (( AT91_SSC_START_RISING_RF ) & AT91_SSC_START)
+ | (( AT91_SSC_CK_RISING ) & AT91_SSC_CKI)
+ | (( AT91_SSC_CKO_CONTINUOUS ) & AT91_SSC_CKO)
+ | (( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS);
+
+ tfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE)
+ | (( 0 << 23) & AT91_SSC_FSDEN)
+ | (( AT91_SSC_FSOS_POSITIVE ) & AT91_SSC_FSOS)
+ | (( 0 << 16) & AT91_SSC_FSLEN)
+ | (((channels - 1) << 8) & AT91_SSC_DATNB)
+ | (( 1 << 7) & AT91_SSC_MSBF)
+ | (( 0 << 5) & AT91_SSC_DATDEF)
+ | (((bits - 1) << 0) & AT91_SSC_DATALEN);
+
+
+
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
default:
- printk(KERN_WARNING "at91-i2s: unsupported DAI format 0x%x.\n",
+ printk(KERN_WARNING "at91-ssc: unsupported DAI format 0x%x.\n",
ssc_p->daifmt);
return -EINVAL;
break;
@@ -518,9 +586,9 @@ static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_TNPR, 0);
at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_TNCR, 0);
- if ((ret = request_irq(ssc_p->ssc.pid, at91_i2s_interrupt,
+ if ((ret = request_irq(ssc_p->ssc.pid, at91_ssc_interrupt,
0, ssc_p->name, ssc_p)) < 0) {
- printk(KERN_WARNING "at91-i2s: request_irq failure\n");
+ printk(KERN_WARNING "at91-ssc: request_irq failure\n");
DBG("Stopping pid %d clock\n", ssc_p->ssc.pid);
at91_sys_write(AT91_PMC_PCER, 1<<ssc_p->ssc.pid);
@@ -546,7 +614,7 @@ static int at91_i2s_hw_params(struct snd_pcm_substream *substream,
}
-static int at91_i2s_prepare(struct snd_pcm_substream *substream)
+static int at91_ssc_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
@@ -566,7 +634,7 @@ static int at91_i2s_prepare(struct snd_pcm_substream *substream)
#ifdef CONFIG_PM
-static int at91_i2s_suspend(struct platform_device *pdev,
+static int at91_ssc_suspend(struct platform_device *pdev,
struct snd_soc_cpu_dai *cpu_dai)
{
struct at91_ssc_info *ssc_p;
@@ -594,7 +662,7 @@ static int at91_i2s_suspend(struct platform_device *pdev,
return 0;
}
-static int at91_i2s_resume(struct platform_device *pdev,
+static int at91_ssc_resume(struct platform_device *pdev,
struct snd_soc_cpu_dai *cpu_dai)
{
struct at91_ssc_info *ssc_p;
@@ -620,102 +688,105 @@ static int at91_i2s_resume(struct platform_device *pdev,
}
#else
-#define at91_i2s_suspend NULL
-#define at91_i2s_resume NULL
+#define at91_ssc_suspend NULL
+#define at91_ssc_resume NULL
#endif
-#define AT91_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+#define AT91_SSC_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)
-struct snd_soc_cpu_dai at91_i2s_dai[NUM_SSC_DEVICES] = {
- { .name = "at91_ssc0/i2s",
+#define AT91_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_cpu_dai at91_ssc_dai[NUM_SSC_DEVICES] = {
+ { .name = "at91-ssc0",
.id = 0,
- .type = SND_SOC_DAI_I2S,
- .suspend = at91_i2s_suspend,
- .resume = at91_i2s_resume,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
.playback = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.capture = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.ops = {
- .startup = at91_i2s_startup,
- .shutdown = at91_i2s_shutdown,
- .prepare = at91_i2s_prepare,
- .hw_params = at91_i2s_hw_params,},
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
.dai_ops = {
- .set_sysclk = at91_i2s_set_dai_sysclk,
- .set_fmt = at91_i2s_set_dai_fmt,
- .set_clkdiv = at91_i2s_set_dai_clkdiv,},
+ .set_sysclk = at91_ssc_set_dai_sysclk,
+ .set_fmt = at91_ssc_set_dai_fmt,
+ .set_clkdiv = at91_ssc_set_dai_clkdiv,},
.private_data = &ssc_info[0].ssc,
},
#if NUM_SSC_DEVICES == 3
- { .name = "at91_ssc1/i2s",
+ { .name = "at91-ssc1",
.id = 1,
- .type = SND_SOC_DAI_I2S,
- .suspend = at91_i2s_suspend,
- .resume = at91_i2s_resume,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
.playback = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.capture = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.ops = {
- .startup = at91_i2s_startup,
- .shutdown = at91_i2s_shutdown,
- .prepare = at91_i2s_prepare,
- .hw_params = at91_i2s_hw_params,},
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
.dai_ops = {
- .set_sysclk = at91_i2s_set_dai_sysclk,
- .set_fmt = at91_i2s_set_dai_fmt,
- .set_clkdiv = at91_i2s_set_dai_clkdiv,},
+ .set_sysclk = at91_ssc_set_dai_sysclk,
+ .set_fmt = at91_ssc_set_dai_fmt,
+ .set_clkdiv = at91_ssc_set_dai_clkdiv,},
.private_data = &ssc_info[1].ssc,
},
- { .name = "at91_ssc2/i2s",
+ { .name = "at91-ssc2",
.id = 2,
- .type = SND_SOC_DAI_I2S,
- .suspend = at91_i2s_suspend,
- .resume = at91_i2s_resume,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
.playback = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.capture = {
.channels_min = 1,
.channels_max = 2,
- .rates = AT91_I2S_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
.ops = {
- .startup = at91_i2s_startup,
- .shutdown = at91_i2s_shutdown,
- .prepare = at91_i2s_prepare,
- .hw_params = at91_i2s_hw_params,},
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
.dai_ops = {
- .set_sysclk = at91_i2s_set_dai_sysclk,
- .set_fmt = at91_i2s_set_dai_fmt,
- .set_clkdiv = at91_i2s_set_dai_clkdiv,},
+ .set_sysclk = at91_ssc_set_dai_sysclk,
+ .set_fmt = at91_ssc_set_dai_fmt,
+ .set_clkdiv = at91_ssc_set_dai_clkdiv,},
.private_data = &ssc_info[2].ssc,
},
#endif
};
-EXPORT_SYMBOL_GPL(at91_i2s_dai);
+EXPORT_SYMBOL_GPL(at91_ssc_dai);
/* Module information */
MODULE_AUTHOR("Frank Mandarino, fmandarino@endrelia.com, www.endrelia.com");
-MODULE_DESCRIPTION("AT91 I2S ASoC Interface");
+MODULE_DESCRIPTION("AT91 SSC ASoC Interface");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/at91/at91-i2s.h b/sound/soc/at91/at91-ssc.h
index f8a875ba0ccc..b188f973df9f 100644
--- a/sound/soc/at91/at91-i2s.h
+++ b/sound/soc/at91/at91-ssc.h
@@ -1,5 +1,5 @@
/*
- * at91-i2s.h - ALSA I2S interface for the Atmel AT91 SoC
+ * at91-ssc.h - ALSA SSC interface for the Atmel AT91 SoC
*
* Author: Frank Mandarino <fmandarino@endrelia.com>
* Endrelia Technologies Inc.
@@ -10,18 +10,18 @@
* published by the Free Software Foundation.
*/
-#ifndef _AT91_I2S_H
-#define _AT91_I2S_H
+#ifndef _AT91_SSC_H
+#define _AT91_SSC_H
-/* I2S system clock ids */
+/* SSC system clock ids */
#define AT91_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
-/* I2S divider ids */
+/* SSC divider ids */
#define AT91SSC_CMR_DIV 0 /* MCK divider for BCLK */
#define AT91SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
#define AT91SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
-extern struct snd_soc_cpu_dai at91_i2s_dai[];
+extern struct snd_soc_cpu_dai at91_ssc_dai[];
-#endif /* _AT91_I2S_H */
+#endif /* _AT91_SSC_H */
diff --git a/sound/soc/at91/eti_b1_wm8731.c b/sound/soc/at91/eti_b1_wm8731.c
index 8179df3bb2f3..820a676c56bf 100644
--- a/sound/soc/at91/eti_b1_wm8731.c
+++ b/sound/soc/at91/eti_b1_wm8731.c
@@ -40,7 +40,7 @@
#include "../codecs/wm8731.h"
#include "at91-pcm.h"
-#include "at91-i2s.h"
+#include "at91-ssc.h"
#if 0
#define DBG(x...) printk(KERN_INFO "eti_b1_wm8731: " x)
@@ -248,15 +248,15 @@ static int eti_b1_wm8731_init(struct snd_soc_codec *codec)
static struct snd_soc_dai_link eti_b1_dai = {
.name = "WM8731",
- .stream_name = "WM8731",
- .cpu_dai = &at91_i2s_dai[1],
+ .stream_name = "WM8731 PCM",
+ .cpu_dai = &at91_ssc_dai[1],
.codec_dai = &wm8731_dai,
.init = eti_b1_wm8731_init,
.ops = &eti_b1_ops,
};
static struct snd_soc_machine snd_soc_machine_eti_b1 = {
- .name = "ETI_B1",
+ .name = "ETI_B1_WM8731",
.dai_link = &eti_b1_dai,
.num_links = 1,
};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ec2a2787957a..e5fb437b86e8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -10,6 +10,10 @@ config SND_SOC_WM8750
tristate
depends on SND_SOC
+config SND_SOC_WM8753
+ tristate
+ depends on SND_SOC
+
config SND_SOC_WM9712
tristate
depends on SND_SOC
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 3249a6e4f1d0..e39a747a17cf 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -1,9 +1,11 @@
snd-soc-ac97-objs := ac97.o
snd-soc-wm8731-objs := wm8731.o
snd-soc-wm8750-objs := wm8750.o
+snd-soc-wm8753-objs := wm8753.o
snd-soc-wm9712-objs := wm9712.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.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
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c
index 55bc55eb6e24..0cdef971cbd3 100644
--- a/sound/soc/codecs/ac97.c
+++ b/sound/soc/codecs/ac97.c
@@ -60,6 +60,7 @@ static struct snd_soc_codec_dai ac97_dai = {
.ops = {
.prepare = ac97_prepare,},
};
+EXPORT_SYMBOL_GPL(ac97_dai);
static unsigned int ac97_read(struct snd_soc_codec *codec,
unsigned int reg)
diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h
index 930ddfc2321a..2bf6d69fd069 100644
--- a/sound/soc/codecs/ac97.h
+++ b/sound/soc/codecs/ac97.h
@@ -14,5 +14,6 @@
#define __LINUX_SND_SOC_AC97_H
extern struct snd_soc_codec_device soc_codec_dev_ac97;
+extern struct snd_soc_codec_dai ac97_dai;
#endif
diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c
index 7073e8e294fc..28684eeda738 100644
--- a/sound/soc/codecs/wm8750.c
+++ b/sound/soc/codecs/wm8750.c
@@ -808,7 +808,7 @@ static int wm8750_init(struct snd_soc_device *socdev)
codec->dai = &wm8750_dai;
codec->num_dai = 1;
codec->reg_cache_size = sizeof(wm8750_reg);
- codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KRENEL);
+ codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL);
if (codec->reg_cache == NULL)
return -ENOMEM;
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
new file mode 100644
index 000000000000..efced934566d
--- /dev/null
+++ b/sound/soc/codecs/wm8753.c
@@ -0,0 +1,1811 @@
+/*
+ * wm8753.c -- WM8753 ALSA Soc Audio driver
+ *
+ * Copyright 2003 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.
+ *
+ * Notes:
+ * The WM8753 is a low power, high quality stereo codec with integrated PCM
+ * codec designed for portable digital telephony applications.
+ *
+ * Dual DAI:-
+ *
+ * This driver support 2 DAI PCM's. This makes the default PCM available for
+ * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for
+ * voice.
+ *
+ * Please note that the voice PCM can be connected directly to a Bluetooth
+ * codec or GSM modem and thus cannot be read or written to, although it is
+ * available to be configured with snd_hw_params(), etc and kcontrols in the
+ * normal alsa manner.
+ *
+ * Fast DAI switching:-
+ *
+ * The driver can now fast switch between the DAI configurations via a
+ * an alsa kcontrol. This allows the PCM to remain open.
+ *
+ */
+
+#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 <asm/div64.h>
+
+#include "wm8753.h"
+
+#define AUDIO_NAME "wm8753"
+#define WM8753_VERSION "0.16"
+
+/*
+ * Debug
+ */
+
+#define WM8753_DEBUG 0
+
+#ifdef WM8753_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)
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
+ unsigned int mode);
+
+/* codec private data */
+struct wm8753_priv {
+ unsigned int sysclk;
+ unsigned int pcmclk;
+};
+
+/*
+ * wm8753 register cache
+ * We can't read the WM8753 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8753_reg[] = {
+ 0x0008, 0x0000, 0x000a, 0x000a,
+ 0x0033, 0x0000, 0x0007, 0x00ff,
+ 0x00ff, 0x000f, 0x000f, 0x007b,
+ 0x0000, 0x0032, 0x0000, 0x00c3,
+ 0x00c3, 0x00c0, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0055,
+ 0x0005, 0x0050, 0x0055, 0x0050,
+ 0x0055, 0x0050, 0x0055, 0x0079,
+ 0x0079, 0x0079, 0x0079, 0x0079,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0097, 0x0097, 0x0000, 0x0004,
+ 0x0000, 0x0083, 0x0024, 0x01ba,
+ 0x0000, 0x0083, 0x0024, 0x01ba,
+ 0x0000, 0x0000
+};
+
+/*
+ * read wm8753 register cache
+ */
+static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1))
+ return -1;
+ return cache[reg - 1];
+}
+
+/*
+ * write wm8753 register cache
+ */
+static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 1 || reg > 0x3f)
+ return;
+ cache[reg - 1] = value;
+}
+
+/*
+ * write to the WM8753 register space
+ */
+static int wm8753_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;
+
+ wm8753_write_reg_cache (codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8753_reset(c) wm8753_write(c, WM8753_RESET, 0)
+
+/*
+ * WM8753 Controls
+ */
+static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8753_base_filter[] =
+ {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz",
+ "100Hz @ 8kHz", "200Hz @ 8kHz"};
+static const char *wm8753_treble[] = {"8kHz", "4kHz"};
+static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"};
+static const char *wm8753_3d_func[] = {"Capture", "Playback"};
+static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};
+static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"};
+static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"};
+static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2",
+ "Line 1", "Line 2"};
+static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"};
+static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"};
+static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"};
+static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"};
+static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2",
+ "Right PGA"};
+static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right",
+ "Left + Right"};
+static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"};
+static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"};
+static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"};
+static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"};
+static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left",
+ "Analogue Mix Right", "Digital Mono Mix"};
+static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k",
+ "82Hz @ 8kHz", "170Hz @ 8kHz"};
+static const char *wm8753_adc_filter[] = {"HiFi", "Voice"};
+static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"};
+static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"};
+static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC",
+ "Channel Swap"};
+
+static const struct soc_enum wm8753_enum[] = {
+SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base),
+SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter),
+SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble),
+SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func),
+SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type),
+SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func),
+SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc),
+SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc),
+SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp),
+SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix),
+SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3),
+SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc),
+SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp),
+SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter),
+SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel),
+SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode),
+SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel),
+};
+
+
+static int wm8753_get_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL);
+
+ ucontrol->value.integer.value[0] = (mode & 0xc) >> 2;
+ return 0;
+}
+
+static int wm8753_set_dai(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL);
+
+ if (((mode &0xc) >> 2) == ucontrol->value.integer.value[0])
+ return 0;
+
+ mode &= 0xfff3;
+ mode |= (ucontrol->value.integer.value[0] << 2);
+
+ wm8753_write(codec, WM8753_IOCTL, mode);
+ wm8753_set_dai_mode(codec, ucontrol->value.integer.value[0]);
+ return 1;
+}
+
+static const struct snd_kcontrol_new wm8753_snd_controls[] = {
+SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0),
+
+SOC_DOUBLE_R("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0),
+
+SOC_DOUBLE_R("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, 0, 127, 0),
+SOC_DOUBLE_R("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, 127, 0),
+
+SOC_SINGLE("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0),
+
+SOC_DOUBLE_R("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, 1),
+SOC_DOUBLE_R("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, 1),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, 1, 0),
+
+SOC_SINGLE("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1),
+SOC_SINGLE("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1),
+SOC_SINGLE("Mono Voice Playback Volume", WM8753_MOUTM2, 4, 7, 1),
+SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0),
+
+SOC_ENUM("Bass Boost", wm8753_enum[0]),
+SOC_ENUM("Bass Filter", wm8753_enum[1]),
+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_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0),
+SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Capture Filter Select", wm8753_enum[23]),
+SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]),
+SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8753_enum[3]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1),
+SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1),
+SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0),
+
+SOC_ENUM("3D Function", wm8753_enum[5]),
+SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]),
+SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]),
+SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0),
+
+SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0),
+SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0),
+
+SOC_ENUM("De-emphasis", wm8753_enum[8]),
+SOC_ENUM("Playback Mono Mix", wm8753_enum[9]),
+SOC_ENUM("Playback Phase", wm8753_enum[10]),
+
+SOC_SINGLE("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0),
+SOC_SINGLE("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0),
+
+SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),
+
+SOC_ENUM("ADC Data Select", wm8753_enum[27]),
+};
+
+/* add non dapm controls */
+static int wm8753_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8753_snd_controls[i],codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0),
+};
+
+/* Right mixer */
+static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0),
+};
+
+/* Mono mixer */
+static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0),
+};
+
+/* Mono 2 Mux */
+static const struct snd_kcontrol_new wm8753_mono2_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[17]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8753_out3_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[18]);
+
+/* Out 4 Mux */
+static const struct snd_kcontrol_new wm8753_out4_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[19]);
+
+/* ADC Mono Mix */
+static const struct snd_kcontrol_new wm8753_adc_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[22]);
+
+/* Record mixer */
+static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0),
+};
+
+/* Left ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[21]);
+
+/* Right ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[20]);
+
+/* MIC mux */
+static const struct snd_kcontrol_new wm8753_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[16]);
+
+/* ALC mixer */
+static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0),
+SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0),
+SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0),
+};
+
+/* Left Line mux */
+static const struct snd_kcontrol_new wm8753_line_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[14]);
+
+/* Right Line mux */
+static const struct snd_kcontrol_new wm8753_line_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[13]);
+
+/* Mono Line mux */
+static const struct snd_kcontrol_new wm8753_line_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[12]);
+
+/* Line mux and mixer */
+static const struct snd_kcontrol_new wm8753_line_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[11]);
+
+/* Rx mux and mixer */
+static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[15]);
+
+/* Mic Selector Mux */
+static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[25]);
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0),
+SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0,
+ &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)),
+SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0),
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0,
+ &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)),
+SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0,
+ &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)),
+SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0),
+SND_SOC_DAPM_OUTPUT("MONO1"),
+SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls),
+SND_SOC_DAPM_OUTPUT("MONO2"),
+SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls),
+SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls),
+SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0,
+ &wm8753_record_mixer_controls[0],
+ ARRAY_SIZE(wm8753_record_mixer_controls)),
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0),
+SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_left_controls),
+SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_adc_right_controls),
+SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_mux_controls),
+SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0,
+ &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)),
+SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_left_controls),
+SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_right_controls),
+SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_line_mono_controls),
+SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0,
+ &wm8753_line_mux_mix_controls),
+SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0,
+ &wm8753_rx_mux_mix_controls),
+SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0,
+ &wm8753_mic_sel_mux_controls),
+SND_SOC_DAPM_INPUT("LINE1"),
+SND_SOC_DAPM_INPUT("LINE2"),
+SND_SOC_DAPM_INPUT("RXP"),
+SND_SOC_DAPM_INPUT("RXN"),
+SND_SOC_DAPM_INPUT("ACIN"),
+SND_SOC_DAPM_OUTPUT("ACOP"),
+SND_SOC_DAPM_INPUT("MIC1N"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2N"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("VREF"),
+};
+
+static const char *audio_map[][3] = {
+ /* left mixer */
+ {"Left Mixer", "Left Playback Switch", "Left DAC"},
+ {"Left Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Right Playback Switch", "Right DAC"},
+ {"Right Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"},
+
+ /* left out */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono 1 out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* mono 2 out */
+ {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Mono 2 Mux", "Inverted Mono 1", "MONO1"},
+ {"Mono 2 Mux", "Left", "Left Mixer"},
+ {"Mono 2 Mux", "Right", "Right Mixer"},
+ {"Mono Out 2", NULL, "Mono 2 Mux"},
+ {"MONO2", NULL, "Mono Out 2"},
+
+ /* out 3 */
+ {"Out3 Left + Right", NULL, "Left Mixer"},
+ {"Out3 Left + Right", NULL, "Right Mixer"},
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "Left + Right", "Out3 Left + Right"},
+ {"Out3 Mux", "ROUT2", "ROUT2"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* out 4 */
+ {"Out4 Mux", "VREF", "VREF"},
+ {"Out4 Mux", "Capture ST", "Capture ST Mixer"},
+ {"Out4 Mux", "LOUT2", "LOUT2"},
+ {"Out 4", NULL, "Out4 Mux"},
+ {"OUT4", NULL, "Out 4"},
+
+ /* record mixer */
+ {"Playback Mixer", "Left Capture Switch", "Left Mixer"},
+ {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"},
+ {"Playback Mixer", "Right Capture Switch", "Right Mixer"},
+
+ /* Mic/SideTone Mux */
+ {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"},
+ {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"},
+ {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"},
+ {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"},
+
+ /* Capture Left Mux */
+ {"Capture Left Mux", "PGA", "Left Capture Volume"},
+ {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"},
+ {"Capture Left Mux", "Line", "LINE1"},
+
+ /* Capture Right Mux */
+ {"Capture Right Mux", "PGA", "Right Capture Volume"},
+ {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"},
+ {"Capture Right Mux", "Sidetone", "Capture ST Mixer"},
+
+ /* Mono Capture mixer-mux */
+ {"Capture Right Mixer", "Stereo", "Capture Right Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"},
+ {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"},
+ {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"},
+ {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Capture Left Mixer"},
+ {"Right ADC", NULL, "Capture Right Mixer"},
+
+ /* Left Capture Volume */
+ {"Left Capture Volume", NULL, "ACIN"},
+
+ /* Right Capture Volume */
+ {"Right Capture Volume", NULL, "Mic 2 Volume"},
+
+ /* ALC Mixer */
+ {"ALC Mixer", "Line Capture Switch", "Line Mixer"},
+ {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"},
+ {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"},
+ {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"},
+
+ /* Line Left Mux */
+ {"Line Left Mux", "Line 1", "LINE1"},
+ {"Line Left Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Right Mux */
+ {"Line Right Mux", "Line 2", "LINE2"},
+ {"Line Right Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mono Mux */
+ {"Line Mono Mux", "Line Mix", "Line Mixer"},
+ {"Line Mono Mux", "Rx Mix", "Rx Mixer"},
+
+ /* Line Mixer/Mux */
+ {"Line Mixer", "Line 1 + 2", "LINE1"},
+ {"Line Mixer", "Line 1 - 2", "LINE1"},
+ {"Line Mixer", "Line 1 + 2", "LINE2"},
+ {"Line Mixer", "Line 1 - 2", "LINE2"},
+ {"Line Mixer", "Line 1", "LINE1"},
+ {"Line Mixer", "Line 2", "LINE2"},
+
+ /* Rx Mixer/Mux */
+ {"Rx Mixer", "RXP - RXN", "RXP"},
+ {"Rx Mixer", "RXP + RXN", "RXP"},
+ {"Rx Mixer", "RXP - RXN", "RXN"},
+ {"Rx Mixer", "RXP + RXN", "RXN"},
+ {"Rx Mixer", "RXP", "RXP"},
+ {"Rx Mixer", "RXN", "RXN"},
+
+ /* Mic 1 Volume */
+ {"Mic 1 Volume", NULL, "MIC1N"},
+ {"Mic 1 Volume", NULL, "Mic Selection Mux"},
+
+ /* Mic 2 Volume */
+ {"Mic 2 Volume", NULL, "MIC2N"},
+ {"Mic 2 Volume", NULL, "MIC2"},
+
+ /* Mic Selector Mux */
+ {"Mic Selection Mux", "Mic 1", "MIC1"},
+ {"Mic Selection Mux", "Mic 2", "MIC2N"},
+ {"Mic Selection Mux", "Mic 3", "MIC2"},
+
+ /* ACOP */
+ {"ACOP", NULL, "ALC Mixer"},
+
+ /* terminator */
+ {NULL, NULL, NULL},
+};
+
+static int wm8753_add_widgets(struct snd_soc_codec *codec)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++)
+ snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]);
+
+ /* set up the WM8753 audio map */
+ 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;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2: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 target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8753 N value outwith recommended range! N = %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 wm8753_set_dai_pll(struct snd_soc_codec_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg, enable;
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+
+ if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2)
+ return -ENODEV;
+
+ if (pll_id == WM8753_PLL1) {
+ offset = 0;
+ enable = 0x10;
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xffef;
+ } else {
+ offset = 4;
+ enable = 0x8;
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfff7;
+ }
+
+ if (!freq_in || !freq_out) {
+ /* disable PLL */
+ wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0026);
+ wm8753_write(codec, WM8753_CLOCK, reg);
+ return 0;
+ } else {
+ u16 value = 0;
+ struct _pll_div pll_div;
+
+ pll_factors(&pll_div, freq_out * 8, freq_in);
+
+ /* set up N and K PLL divisor ratios */
+ /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
+ value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18);
+ wm8753_write(codec, WM8753_PLL1CTL2 + offset, value);
+
+ /* bits 8:0 = PLL_K[17:9] */
+ value = (pll_div.k & 0x03fe00) >> 9;
+ wm8753_write(codec, WM8753_PLL1CTL3 + offset, value);
+
+ /* bits 8:0 = PLL_K[8:0] */
+ value = pll_div.k & 0x0001ff;
+ wm8753_write(codec, WM8753_PLL1CTL4 + offset, value);
+
+ /* set PLL as input and enable */
+ wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 |
+ (pll_div.div2 << 3));
+ wm8753_write(codec, WM8753_CLOCK, reg | enable);
+ }
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 0x6, 0x0},
+ {11289600, 8000, 0x16, 0x0},
+ {18432000, 8000, 0x7, 0x0},
+ {16934400, 8000, 0x17, 0x0},
+ {12000000, 8000, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 0x18, 0x0},
+ {16934400, 11025, 0x19, 0x0},
+ {12000000, 11025, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 0xa, 0x0},
+ {18432000, 16000, 0xb, 0x0},
+ {12000000, 16000, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 0x1a, 0x0},
+ {16934400, 22050, 0x1b, 0x0},
+ {12000000, 22050, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 0xc, 0x0},
+ {18432000, 32000, 0xd, 0x0},
+ {12000000, 32000, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 0x10, 0x0},
+ {16934400, 44100, 0x11, 0x0},
+ {12000000, 44100, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 0x0, 0x0},
+ {18432000, 48000, 0x1, 0x0},
+ {12000000, 48000, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 0x1e, 0x0},
+ {16934400, 88200, 0x1f, 0x0},
+ {12000000, 88200, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 0xe, 0x0},
+ {18432000, 96000, 0xf, 0x0},
+ {12000000, 96000, 0xe, 0x1},
+};
+
+static 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 -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8753_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 wm8753_priv *wm8753 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ if (clk_id == WM8753_MCLK) {
+ wm8753->sysclk = freq;
+ return 0;
+ } else if (clk_id == WM8753_PCMCLK) {
+ wm8753->pcmclk = freq;
+ return 0;
+ }
+ break;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01ec;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ voice |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ voice |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ voice |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_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;
+ struct wm8753_priv *wm8753 = codec->private_data;
+ u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3;
+ u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ voice |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ voice |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ voice |= 0x000c;
+ break;
+ }
+
+ /* sample rate */
+ if (params_rate(params) * 384 == wm8753->pcmclk)
+ srate |= 0x80;
+ wm8753_write(codec, WM8753_SRATE1, srate);
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ return 0;
+}
+
+/*
+ * Set's PCM dai fmt and BCLK.
+ */
+static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 voice, ioctl;
+
+ voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x011f;
+ ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x015d;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x2;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ voice |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ voice &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ voice |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ voice |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ voice |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_PCM, voice);
+ wm8753_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+static int wm8753_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 WM8753_PCMDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0x003f;
+ wm8753_write(codec, WM8753_CLOCK, reg | div);
+ break;
+ case WM8753_BCLKDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x01c7;
+ wm8753_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ case WM8753_VXCLKDIV:
+ reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x003f;
+ wm8753_write(codec, WM8753_SRATE2, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Set's HiFi DAC format.
+ */
+static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01e0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ hifi |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ hifi |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ hifi |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+/*
+ * Set's I2S DAI format.
+ */
+static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 ioctl, hifi;
+
+ hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x011f;
+ ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x00ae;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ ioctl |= 0x1;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ hifi |= 0x0040;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ /* frame inversion not valid for DSP modes */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ hifi &= ~0x0010;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ hifi |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ hifi |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ hifi |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ wm8753_write(codec, WM8753_IOCTL, ioctl);
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_i2s_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 wm8753_priv *wm8753 = codec->private_data;
+ u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0;
+ u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3;
+ int coeff;
+
+ /* is digital filter coefficient valid ? */
+ coeff = get_coeff(wm8753->sysclk, params_rate(params));
+ if (coeff < 0) {
+ printk(KERN_ERR "wm8753 invalid MCLK or rate\n");
+ return coeff;
+ }
+ wm8753_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) |
+ coeff_div[coeff].usb);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ hifi |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ hifi |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ hifi |= 0x000c;
+ break;
+ }
+
+ wm8753_write(codec, WM8753_HIFI, hifi);
+ return 0;
+}
+
+static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock);
+
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_pcm_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as pcmclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock);
+
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 clock;
+
+ /* set clk source as mclk */
+ clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb;
+ wm8753_write(codec, WM8753_CLOCK, clock | 0x4);
+
+ if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+ return -EINVAL;
+ return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7;
+
+ /* the digital mute covers the HiFi and Voice DAC's on the WM8753.
+ * make sure we check if they are not both active when we mute */
+ if (mute && dai->id == 1) {
+ if (!wm8753_dai[WM8753_DAI_VOICE].playback.active ||
+ !wm8753_dai[WM8753_DAI_HIFI].playback.active)
+ wm8753_write(codec, WM8753_DAC, mute_reg | 0x8);
+ } else {
+ if (mute)
+ wm8753_write(codec, WM8753_DAC, mute_reg | 0x8);
+ else
+ wm8753_write(codec, WM8753_DAC, mute_reg);
+ }
+
+ return 0;
+}
+
+static int wm8753_dapm_event(struct snd_soc_codec *codec, int event)
+{
+ u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e;
+
+ switch (event) {
+ case SNDRV_CTL_POWER_D0: /* full On */
+ /* set vmid to 50k and unmute dac */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SNDRV_CTL_POWER_D1: /* partial On */
+ case SNDRV_CTL_POWER_D2: /* partial On */
+ /* set vmid to 5k for quick power up */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SNDRV_CTL_POWER_D3hot: /* Off, with power */
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
+ break;
+ case SNDRV_CTL_POWER_D3cold: /* Off, without power */
+ wm8753_write(codec, WM8753_PWR1, 0x0001);
+ break;
+ }
+ codec->dapm_state = event;
+ return 0;
+}
+
+#define WM8753_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 WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/*
+ * The WM8753 supports upto 4 different and mutually exclusive DAI
+ * configurations. This gives 2 PCM's available for use, hifi and voice.
+ * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI
+ * is connected between the wm8753 and a BT codec or GSM modem.
+ *
+ * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI
+ * 2. Voice over HIFI DAI - HIFI disabled
+ * 3. Voice disabled - HIFI over HIFI
+ * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture
+ */
+static const struct snd_soc_codec_dai wm8753_all_dai[] = {
+/* DAI HiFi mode 1 */
+{ .name = "WM8753 HiFi",
+ .id = 1,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = { /* dummy for fast DAI switching */
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode1h_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 1 */
+{ .name = "WM8753 Voice",
+ .id = 1,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_pcm_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode1v_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI HiFi mode 2 - dummy */
+{ .name = "WM8753 HiFi",
+ .id = 2,
+},
+/* DAI Voice mode 2 */
+{ .name = "WM8753 Voice",
+ .id = 2,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_pcm_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode2_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI HiFi mode 3 */
+{ .name = "WM8753 HiFi",
+ .id = 3,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode3_4_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 3 - dummy */
+{ .name = "WM8753 Voice",
+ .id = 3,
+},
+/* DAI HiFi mode 4 */
+{ .name = "WM8753 HiFi",
+ .id = 4,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8753_RATES,
+ .formats = WM8753_FORMATS,},
+ .ops = {
+ .hw_params = wm8753_i2s_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8753_mute,
+ .set_fmt = wm8753_mode3_4_set_dai_fmt,
+ .set_clkdiv = wm8753_set_dai_clkdiv,
+ .set_pll = wm8753_set_dai_pll,
+ .set_sysclk = wm8753_set_dai_sysclk,
+ },
+},
+/* DAI Voice mode 4 - dummy */
+{ .name = "WM8753 Voice",
+ .id = 4,
+},
+};
+
+struct snd_soc_codec_dai wm8753_dai[2];
+EXPORT_SYMBOL_GPL(wm8753_dai);
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode)
+{
+ if (mode < 4) {
+ int playback_active, capture_active, codec_active, pop_wait;
+ void *private_data;
+
+ playback_active = wm8753_dai[0].playback.active;
+ capture_active = wm8753_dai[0].capture.active;
+ codec_active = wm8753_dai[0].active;
+ private_data = wm8753_dai[0].private_data;
+ pop_wait = wm8753_dai[0].pop_wait;
+ wm8753_dai[0] = wm8753_all_dai[mode << 1];
+ wm8753_dai[0].playback.active = playback_active;
+ wm8753_dai[0].capture.active = capture_active;
+ wm8753_dai[0].active = codec_active;
+ wm8753_dai[0].private_data = private_data;
+ wm8753_dai[0].pop_wait = pop_wait;
+
+ playback_active = wm8753_dai[1].playback.active;
+ capture_active = wm8753_dai[1].capture.active;
+ codec_active = wm8753_dai[1].active;
+ private_data = wm8753_dai[1].private_data;
+ pop_wait = wm8753_dai[1].pop_wait;
+ wm8753_dai[1] = wm8753_all_dai[(mode << 1) + 1];
+ wm8753_dai[1].playback.active = playback_active;
+ wm8753_dai[1].capture.active = capture_active;
+ wm8753_dai[1].active = codec_active;
+ wm8753_dai[1].private_data = private_data;
+ wm8753_dai[1].pop_wait = pop_wait;
+ }
+ wm8753_dai[0].codec = codec;
+ wm8753_dai[1].codec = codec;
+}
+
+static void wm8753_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8753_dapm_event(codec, codec->dapm_state);
+}
+
+static int wm8753_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;
+
+ /* we only need to suspend if we are a valid card */
+ if(!codec->card)
+ return 0;
+
+ wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
+ return 0;
+}
+
+static int wm8753_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;
+
+ /* we only need to resume if we are a valid card */
+ if(!codec->card)
+ return 0;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) {
+ if (i + 1 == WM8753_RESET)
+ continue;
+ data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+
+ /* charge wm8753 caps */
+ if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) {
+ wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2);
+ codec->dapm_state = SNDRV_CTL_POWER_D0;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(caps_charge));
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8753 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8753_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8753";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8753_read_reg_cache;
+ codec->write = wm8753_write;
+ codec->dapm_event = wm8753_dapm_event;
+ codec->dai = wm8753_dai;
+ codec->num_dai = 2;
+ codec->reg_cache_size = sizeof(wm8753_reg);
+ codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8753_set_dai_mode(codec, 0);
+
+ wm8753_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8753: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2);
+ codec->dapm_state = SNDRV_CTL_POWER_D3hot;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(caps_charge));
+
+ /* set the update bits */
+ reg = wm8753_read_reg_cache(codec, WM8753_LDAC);
+ wm8753_write(codec, WM8753_LDAC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RDAC);
+ wm8753_write(codec, WM8753_RDAC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LADC);
+ wm8753_write(codec, WM8753_LADC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RADC);
+ wm8753_write(codec, WM8753_RADC, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LOUT1V);
+ wm8753_write(codec, WM8753_LOUT1V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_ROUT1V);
+ wm8753_write(codec, WM8753_ROUT1V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LOUT2V);
+ wm8753_write(codec, WM8753_LOUT2V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_ROUT2V);
+ wm8753_write(codec, WM8753_ROUT2V, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_LINVOL);
+ wm8753_write(codec, WM8753_LINVOL, reg | 0x0100);
+ reg = wm8753_read_reg_cache(codec, WM8753_RINVOL);
+ wm8753_write(codec, WM8753_RINVOL, reg | 0x0100);
+
+ wm8753_add_controls(codec);
+ wm8753_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8753: 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;
+}
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static struct snd_soc_device *wm8753_socdev;
+
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+
+/*
+ * WM8753 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 wm8753_i2c_driver;
+static struct i2c_client client_template;
+
+static int wm8753_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = wm8753_socdev;
+ struct wm8753_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 = wm8753_init(socdev);
+ if (ret < 0) {
+ err("failed to initialise WM8753\n");
+ goto err;
+ }
+
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+static int wm8753_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 wm8753_i2c_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, wm8753_codec_probe);
+}
+
+/* corgi i2c codec control layer */
+static struct i2c_driver wm8753_i2c_driver = {
+ .driver = {
+ .name = "WM8753 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .id = I2C_DRIVERID_WM8753,
+ .attach_adapter = wm8753_i2c_attach,
+ .detach_client = wm8753_i2c_detach,
+ .command = NULL,
+};
+
+static struct i2c_client client_template = {
+ .name = "WM8753",
+ .driver = &wm8753_i2c_driver,
+};
+#endif
+
+static int wm8753_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8753_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8753_priv *wm8753;
+ int ret = 0;
+
+ info("WM8753 Audio Codec %s", WM8753_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+ if (wm8753 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8753;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8753_socdev = socdev;
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work);
+
+#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(&wm8753_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+ }
+#else
+ /* Add other interfaces here */
+#endif
+ return ret;
+}
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+ int ret;
+
+ /* cancel any work waiting to be queued. */
+ ret = cancel_delayed_work(dwork);
+
+ /* if there was any work waiting then we run it now and
+ * wait for it's completion */
+ if (ret) {
+ schedule_delayed_work(dwork, 0);
+ flush_scheduled_work();
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8753_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)
+ wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
+ run_delayed_work(&codec->delayed_work);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8753_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8753 = {
+ .probe = wm8753_probe,
+ .remove = wm8753_remove,
+ .suspend = wm8753_suspend,
+ .resume = wm8753_resume,
+};
+
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753);
+
+MODULE_DESCRIPTION("ASoC WM8753 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h
new file mode 100644
index 000000000000..95e2a1f53169
--- /dev/null
+++ b/sound/soc/codecs/wm8753.h
@@ -0,0 +1,126 @@
+/*
+ * wm8753.h -- audio driver for WM8753
+ *
+ * Copyright 2003 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.
+ *
+ */
+
+#ifndef _WM8753_H
+#define _WM8753_H
+
+/* WM8753 register space */
+
+#define WM8753_DAC 0x01
+#define WM8753_ADC 0x02
+#define WM8753_PCM 0x03
+#define WM8753_HIFI 0x04
+#define WM8753_IOCTL 0x05
+#define WM8753_SRATE1 0x06
+#define WM8753_SRATE2 0x07
+#define WM8753_LDAC 0x08
+#define WM8753_RDAC 0x09
+#define WM8753_BASS 0x0a
+#define WM8753_TREBLE 0x0b
+#define WM8753_ALC1 0x0c
+#define WM8753_ALC2 0x0d
+#define WM8753_ALC3 0x0e
+#define WM8753_NGATE 0x0f
+#define WM8753_LADC 0x10
+#define WM8753_RADC 0x11
+#define WM8753_ADCTL1 0x12
+#define WM8753_3D 0x13
+#define WM8753_PWR1 0x14
+#define WM8753_PWR2 0x15
+#define WM8753_PWR3 0x16
+#define WM8753_PWR4 0x17
+#define WM8753_ID 0x18
+#define WM8753_INTPOL 0x19
+#define WM8753_INTEN 0x1a
+#define WM8753_GPIO1 0x1b
+#define WM8753_GPIO2 0x1c
+#define WM8753_RESET 0x1f
+#define WM8753_RECMIX1 0x20
+#define WM8753_RECMIX2 0x21
+#define WM8753_LOUTM1 0x22
+#define WM8753_LOUTM2 0x23
+#define WM8753_ROUTM1 0x24
+#define WM8753_ROUTM2 0x25
+#define WM8753_MOUTM1 0x26
+#define WM8753_MOUTM2 0x27
+#define WM8753_LOUT1V 0x28
+#define WM8753_ROUT1V 0x29
+#define WM8753_LOUT2V 0x2a
+#define WM8753_ROUT2V 0x2b
+#define WM8753_MOUTV 0x2c
+#define WM8753_OUTCTL 0x2d
+#define WM8753_ADCIN 0x2e
+#define WM8753_INCTL1 0x2f
+#define WM8753_INCTL2 0x30
+#define WM8753_LINVOL 0x31
+#define WM8753_RINVOL 0x32
+#define WM8753_MICBIAS 0x33
+#define WM8753_CLOCK 0x34
+#define WM8753_PLL1CTL1 0x35
+#define WM8753_PLL1CTL2 0x36
+#define WM8753_PLL1CTL3 0x37
+#define WM8753_PLL1CTL4 0x38
+#define WM8753_PLL2CTL1 0x39
+#define WM8753_PLL2CTL2 0x3a
+#define WM8753_PLL2CTL3 0x3b
+#define WM8753_PLL2CTL4 0x3c
+#define WM8753_BIASCTL 0x3d
+#define WM8753_ADCTL2 0x3f
+
+struct wm8753_setup_data {
+ unsigned short i2c_address;
+};
+
+#define WM8753_PLL1 0
+#define WM8753_PLL2 1
+
+/* clock inputs */
+#define WM8753_MCLK 0
+#define WM8753_PCMCLK 1
+
+/* clock divider id's */
+#define WM8753_PCMDIV 0
+#define WM8753_BCLKDIV 1
+#define WM8753_VXCLKDIV 2
+
+/* PCM clock dividers */
+#define WM8753_PCM_DIV_1 (0 << 6)
+#define WM8753_PCM_DIV_3 (2 << 6)
+#define WM8753_PCM_DIV_5_5 (3 << 6)
+#define WM8753_PCM_DIV_2 (4 << 6)
+#define WM8753_PCM_DIV_4 (5 << 6)
+#define WM8753_PCM_DIV_6 (6 << 6)
+#define WM8753_PCM_DIV_8 (7 << 6)
+
+/* BCLK clock dividers */
+#define WM8753_BCLK_DIV_1 (0 << 3)
+#define WM8753_BCLK_DIV_2 (1 << 3)
+#define WM8753_BCLK_DIV_4 (2 << 3)
+#define WM8753_BCLK_DIV_8 (3 << 3)
+#define WM8753_BCLK_DIV_16 (4 << 3)
+
+/* VXCLK clock dividers */
+#define WM8753_VXCLK_DIV_1 (0 << 6)
+#define WM8753_VXCLK_DIV_2 (1 << 6)
+#define WM8753_VXCLK_DIV_4 (2 << 6)
+#define WM8753_VXCLK_DIV_8 (3 << 6)
+#define WM8753_VXCLK_DIV_16 (4 << 6)
+
+#define WM8753_DAI_HIFI 0
+#define WM8753_DAI_VOICE 1
+
+extern struct snd_soc_codec_dai wm8753_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_wm8753;
+
+#endif
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
index ee7a691a9ba1..264413a00cac 100644
--- a/sound/soc/codecs/wm9712.c
+++ b/sound/soc/codecs/wm9712.c
@@ -676,14 +676,13 @@ static int wm9712_soc_probe(struct platform_device *pdev)
codec = socdev->codec;
mutex_init(&codec->mutex);
- codec->reg_cache =
- kzalloc(sizeof(u16) * ARRAY_SIZE(wm9712_reg), GFP_KERNEL);
+ codec->reg_cache = kmemdup(wm9712_reg, sizeof(wm9712_reg), GFP_KERNEL);
+
if (codec->reg_cache == NULL) {
ret = -ENOMEM;
goto cache_err;
}
- memcpy(codec->reg_cache, wm9712_reg, sizeof(u16) * ARRAY_SIZE(wm9712_reg));
- codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm9712_reg);
+ codec->reg_cache_size = sizeof(wm9712_reg);
codec->reg_cache_step = 2;
codec->name = "WM9712";
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index b9ab3b8e1d3e..a83e22937c27 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -1,5 +1,3 @@
-menu "SoC Audio for the Intel PXA2xx"
-
config SND_PXA2XX_SOC
tristate "SoC Audio for the Intel PXA2xx chip"
depends on ARCH_PXA && SND_SOC
@@ -55,5 +53,3 @@ config SND_PXA2XX_SOC_TOSA
help
Say Y if you want to add support for SoC audio on Sharp
Zaurus SL-C6000x models (Tosa).
-
-endmenu
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig
new file mode 100644
index 000000000000..044a3712077a
--- /dev/null
+++ b/sound/soc/s3c24xx/Kconfig
@@ -0,0 +1,10 @@
+config SND_S3C24XX_SOC
+ tristate "SoC Audio for the Samsung S3C24XX chips"
+ depends on ARCH_S3C2410 && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the S3C24XX AC97, I2S or SSP interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_S3C24XX_SOC_I2S
+ tristate
diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile
new file mode 100644
index 000000000000..6f0fffcb30f5
--- /dev/null
+++ b/sound/soc/s3c24xx/Makefile
@@ -0,0 +1,6 @@
+# S3c24XX Platform Support
+snd-soc-s3c24xx-objs := s3c24xx-pcm.o
+snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
+
+obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o
+obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o
diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c
new file mode 100644
index 000000000000..8ca314dc8891
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-i2s.c
@@ -0,0 +1,441 @@
+/*
+ * s3c24xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * (c) 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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
+ * 11th Dec 2006 Merged with Simtec driver
+ * 10th Nov 2006 Initial version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/arch/regs-iis.h>
+#include <asm/arch/regs-gpio.h>
+#include <asm/arch/regs-clock.h>
+#include <asm/arch/audio.h>
+#include <asm/dma.h>
+#include <asm/arch/dma.h>
+
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-i2s.h"
+
+#define S3C24XX_I2S_DEBUG 0
+#if S3C24XX_I2S_DEBUG
+#define DBG(x...) printk(KERN_DEBUG x)
+#else
+#define DBG(x...)
+#endif
+
+static struct s3c2410_dma_client s3c24xx_dma_client_out = {
+ .name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c24xx_dma_client_in = {
+ .name = "I2S PCM Stereo in"
+};
+
+static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = {
+ .client = &s3c24xx_dma_client_out,
+ .channel = DMACH_I2S_OUT,
+ .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
+ .dma_size = 2,
+};
+
+static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = {
+ .client = &s3c24xx_dma_client_in,
+ .channel = DMACH_I2S_IN,
+ .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
+ .dma_size = 2,
+};
+
+struct s3c24xx_i2s_info {
+ void __iomem *regs;
+ struct clk *iis_clk;
+};
+static struct s3c24xx_i2s_info s3c24xx_i2s;
+
+static void s3c24xx_snd_txctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
+ iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_TXIDLE;
+ iismod |= S3C2410_IISMOD_TXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_TXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_TXDMA;
+ iiscon |= S3C2410_IISCON_TXIDLE;
+ iiscon &= ~S3C2410_IISCON_TXDMAEN;
+ iismod &= ~S3C2410_IISMOD_TXMODE;
+
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
+}
+
+static void s3c24xx_snd_rxctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
+ iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_RXIDLE;
+ iismod |= S3C2410_IISMOD_RXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_RXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_RXDMA;
+ iiscon |= S3C2410_IISCON_RXIDLE;
+ iiscon &= ~S3C2410_IISCON_RXDMAEN;
+ iismod &= ~S3C2410_IISMOD_RXMODE;
+
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
+}
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c24xx_snd_lrsync(void)
+{
+ u32 iiscon;
+ unsigned long timeout = jiffies + msecs_to_jiffies(5);
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ while (1) {
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ if (iiscon & S3C2410_IISCON_LRINDEX)
+ break;
+
+ if (timeout < jiffies)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c24xx_snd_is_clkmaster(void)
+{
+ DBG("Entered %s\n", __FUNCTION__);
+
+ return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
+}
+
+/*
+ * Set S3C24xx I2S DAI format
+ */
+static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai,
+ unsigned int fmt)
+{
+ u32 iismod;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ DBG("hw_params r: IISMOD: %lx \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iismod |= S3C2410_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2410_IISMOD_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ DBG("hw_params w: IISMOD: %lx \n", iismod);
+ return 0;
+}
+
+static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ u32 iismod;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out;
+ else
+ rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in;
+
+ /* Working copies of register */
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ DBG("hw_params r: IISMOD: %lx\n", iismod);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iismod |= S3C2410_IISMOD_16BIT;
+ break;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ DBG("hw_params w: IISMOD: %lx\n", iismod);
+ return 0;
+}
+
+static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!s3c24xx_snd_is_clkmaster()) {
+ ret = s3c24xx_snd_lrsync();
+ if (ret)
+ goto exit_err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(1);
+ else
+ s3c24xx_snd_txctrl(1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(0);
+ else
+ s3c24xx_snd_txctrl(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/*
+ * Set S3C24xx Clock source
+ */
+static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ iismod &= ~S3C2440_IISMOD_MPLL;
+
+ switch (clk_id) {
+ case S3C24XX_CLKSRC_PCLK:
+ break;
+ case S3C24XX_CLKSRC_MPLL:
+ iismod |= S3C2440_IISMOD_MPLL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ return 0;
+}
+
+/*
+ * Set S3C24xx Clock dividers
+ */
+static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 reg;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ switch (div_id) {
+ case S3C24XX_DIV_MCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_BCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_PRESCALER:
+ writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * To avoid duplicating clock code, allow machine driver to
+ * get the clockrate from here.
+ */
+u32 s3c24xx_i2s_get_clockrate(void)
+{
+ return clk_get_rate(s3c24xx_i2s.iis_clk);
+}
+EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
+
+static int s3c24xx_i2s_probe(struct platform_device *pdev)
+{
+ DBG("Entered %s\n", __FUNCTION__);
+
+ s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
+ if (s3c24xx_i2s.regs == NULL)
+ return -ENXIO;
+
+ s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis");
+ if (s3c24xx_i2s.iis_clk == NULL) {
+ DBG("failed to get iis_clock\n");
+ return -ENODEV;
+ }
+ clk_enable(s3c24xx_i2s.iis_clk);
+
+ /* Configure the I2S pins in correct mode */
+ s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
+ s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
+
+ writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+
+ s3c24xx_snd_txctrl(0);
+ s3c24xx_snd_rxctrl(0);
+
+ return 0;
+}
+
+#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 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+struct snd_soc_cpu_dai s3c24xx_i2s_dai = {
+ .name = "s3c24xx-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .probe = s3c24xx_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .trigger = s3c24xx_i2s_trigger,
+ .hw_params = s3c24xx_i2s_hw_params,},
+ .dai_ops = {
+ .set_fmt = s3c24xx_i2s_set_fmt,
+ .set_clkdiv = s3c24xx_i2s_set_clkdiv,
+ .set_sysclk = s3c24xx_i2s_set_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.h b/sound/soc/s3c24xx/s3c24xx-i2s.h
new file mode 100644
index 000000000000..537b4ecce8a3
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-i2s.h
@@ -0,0 +1,37 @@
+/*
+ * s3c24xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@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
+ * 10th Nov 2006 Initial version.
+ */
+
+#ifndef S3C24XXI2S_H_
+#define S3C24XXI2S_H_
+
+/* clock sources */
+#define S3C24XX_CLKSRC_PCLK 0
+#define S3C24XX_CLKSRC_MPLL 1
+
+/* Clock dividers */
+#define S3C24XX_DIV_MCLK 0
+#define S3C24XX_DIV_BCLK 1
+#define S3C24XX_DIV_PRESCALER 2
+
+/* prescaler */
+#define S3C24XX_PRESCALE(a,b) \
+ (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT))
+
+u32 s3c24xx_i2s_get_clockrate(void);
+
+extern struct snd_soc_cpu_dai s3c24xx_i2s_dai;
+
+#endif /*S3C24XXI2S_H_*/
diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c
new file mode 100644
index 000000000000..21dc6974d6a3
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-pcm.c
@@ -0,0 +1,468 @@
+/*
+ * s3c24xx-pcm.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * (c) 2004-2005 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * 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
+ * 11th Dec 2006 Merged with Simtec driver
+ * 10th Nov 2006 Initial version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/audio.h>
+
+#include "s3c24xx-pcm.h"
+
+#define S3C24XX_PCM_DEBUG 0
+#if S3C24XX_PCM_DEBUG
+#define DBG(x...) printk(KERN_DEBUG x)
+#else
+#define DBG(x...)
+#endif
+
+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,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S8,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128*1024,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = PAGE_SIZE*2,
+ .periods_min = 2,
+ .periods_max = 128,
+ .fifo_size = 32,
+};
+
+struct s3c24xx_runtime_data {
+ spinlock_t lock;
+ int state;
+ unsigned int dma_loaded;
+ unsigned int dma_limit;
+ unsigned int dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+ struct s3c24xx_pcm_dma_params *params;
+};
+
+/* s3c24xx_pcm_enqueue
+ *
+ * place a dma buffer onto the queue for the dma system
+ * to handle.
+*/
+static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ dma_addr_t pos = prtd->dma_pos;
+ int ret;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ while (prtd->dma_loaded < prtd->dma_limit) {
+ unsigned long len = prtd->dma_period;
+
+ DBG("dma_loaded: %d\n",prtd->dma_loaded);
+
+ if ((pos + len) > prtd->dma_end) {
+ len = prtd->dma_end - pos;
+ DBG(KERN_DEBUG "%s: corrected dma len %ld\n",
+ __FUNCTION__, len);
+ }
+
+ ret = s3c2410_dma_enqueue(prtd->params->channel,
+ substream, pos, len);
+
+ if (ret == 0) {
+ prtd->dma_loaded++;
+ pos += prtd->dma_period;
+ if (pos >= prtd->dma_end)
+ pos = prtd->dma_start;
+ } else
+ break;
+ }
+
+ prtd->dma_pos = pos;
+}
+
+static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel,
+ void *dev_id, int size,
+ enum s3c2410_dma_buffresult result)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct s3c24xx_runtime_data *prtd;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
+ return;
+
+ prtd = substream->runtime->private_data;
+
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+
+ spin_lock(&prtd->lock);
+ if (prtd->state & ST_RUNNING) {
+ prtd->dma_loaded--;
+ s3c24xx_pcm_enqueue(substream);
+ }
+
+ spin_unlock(&prtd->lock);
+}
+
+static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct s3c24xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data;
+ unsigned long totbytes = params_buffer_bytes(params);
+ int ret=0;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* prepare DMA */
+ prtd->params = dma;
+
+ DBG("params %p, client %p, channel %d\n", prtd->params,
+ prtd->params->client, prtd->params->channel);
+
+ ret = s3c2410_dma_request(prtd->params->channel,
+ prtd->params->client, NULL);
+
+ if (ret) {
+ DBG(KERN_ERR "failed to get dma channel\n");
+ return ret;
+ }
+
+ /* 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);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ runtime->dma_bytes = totbytes;
+
+ spin_lock_irq(&prtd->lock);
+ prtd->dma_loaded = 0;
+ prtd->dma_limit = runtime->hw.periods_min;
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + totbytes;
+ spin_unlock_irq(&prtd->lock);
+
+ return 0;
+}
+
+static int s3c24xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ /* TODO - do we need to ensure DMA flushed */
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (prtd->params) {
+ s3c2410_dma_free(prtd->params->channel, prtd->params->client);
+ prtd->params = NULL;
+ }
+
+ return 0;
+}
+
+static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!prtd->params)
+ return 0;
+
+ /* flush the DMA channel */
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
+ prtd->dma_loaded = 0;
+ prtd->dma_pos = prtd->dma_start;
+
+ /* enqueue dma buffers */
+ s3c24xx_pcm_enqueue(substream);
+
+ return ret;
+}
+
+static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->state |= ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STARTED);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->state &= ~ST_RUNNING;
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+ s3c24xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+ unsigned long res;
+ dma_addr_t src, dst;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ spin_lock(&prtd->lock);
+ s3c2410_dma_getposition(prtd->params->channel, &src, &dst);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ res = dst - prtd->dma_start;
+ else
+ res = src - prtd->dma_start;
+
+ spin_unlock(&prtd->lock);
+
+ DBG("Pointer %x %x\n",src,dst);
+
+ /* we seem to be getting the odd error from the pcm library due
+ * to out-of-bounds pointers. this is maybe due to the dma engine
+ * not having loaded the new values for the channel before being
+ * callled... (todo - fix )
+ */
+
+ if (res >= snd_pcm_lib_buffer_bytes(substream)) {
+ if (res == snd_pcm_lib_buffer_bytes(substream))
+ res = 0;
+ }
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+ return 0;
+}
+
+static int s3c24xx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct s3c24xx_runtime_data *prtd = runtime->private_data;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ if (prtd)
+ kfree(prtd);
+ else
+ DBG("s3c24xx_pcm_close called with prtd == NULL\n");
+
+ return 0;
+}
+
+static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops s3c24xx_pcm_ops = {
+ .open = s3c24xx_pcm_open,
+ .close = s3c24xx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = s3c24xx_pcm_hw_params,
+ .hw_free = s3c24xx_pcm_hw_free,
+ .prepare = s3c24xx_pcm_prepare,
+ .trigger = s3c24xx_pcm_trigger,
+ .pointer = s3c24xx_pcm_pointer,
+ .mmap = s3c24xx_pcm_mmap,
+};
+
+static int s3c24xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = s3c24xx_pcm_hardware.buffer_bytes_max;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ return 0;
+}
+
+static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 s3c24xx_pcm_dmamask = DMA_32BIT_MASK;
+
+static int s3c24xx_pcm_new(struct snd_card *card,
+ struct snd_soc_codec_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ DBG("Entered %s\n", __FUNCTION__);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &s3c24xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = s3c24xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = s3c24xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform s3c24xx_soc_platform = {
+ .name = "s3c24xx-audio",
+ .pcm_ops = &s3c24xx_pcm_ops,
+ .pcm_new = s3c24xx_pcm_new,
+ .pcm_free = s3c24xx_pcm_free_dma_buffers,
+};
+
+EXPORT_SYMBOL_GPL(s3c24xx_soc_platform);
+
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("Samsung S3C24XX PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.h b/sound/soc/s3c24xx/s3c24xx-pcm.h
new file mode 100644
index 000000000000..0088c79822ea
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-pcm.h
@@ -0,0 +1,31 @@
+/*
+ * s3c24xx-pcm.h --
+ *
+ * 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.
+ *
+ * ALSA PCM interface for the Samsung S3C24xx CPU
+ */
+
+#ifndef _S3C24XX_PCM_H
+#define _S3C24XX_PCM_H
+
+#define ST_RUNNING (1<<0)
+#define ST_OPENED (1<<1)
+
+struct s3c24xx_pcm_dma_params {
+ struct s3c2410_dma_client *client; /* stream identifier */
+ int channel; /* Channel ID */
+ dma_addr_t dma_addr;
+ int dma_size; /* Size of the DMA transfer */
+};
+
+#define S3C24XX_DAI_I2S 0
+
+/* platform data */
+extern struct snd_soc_platform s3c24xx_soc_platform;
+extern struct snd_ac97_bus_ops s3c24xx_ac97_ops;
+
+#endif
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 7caf8c7b0ac5..96bce55572a0 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -882,13 +882,15 @@ int snd_soc_dapm_connect_input(struct snd_soc_codec *codec, const char *sink,
if (wsink->id == snd_soc_dapm_input) {
if (wsource->id == snd_soc_dapm_micbias ||
wsource->id == snd_soc_dapm_mic ||
- wsink->id == snd_soc_dapm_line)
+ wsink->id == snd_soc_dapm_line ||
+ wsink->id == snd_soc_dapm_output)
wsink->ext = 1;
}
if (wsource->id == snd_soc_dapm_output) {
if (wsink->id == snd_soc_dapm_spk ||
wsink->id == snd_soc_dapm_hp ||
- wsink->id == snd_soc_dapm_line)
+ wsink->id == snd_soc_dapm_line ||
+ wsink->id == snd_soc_dapm_input)
wsource->ext = 1;
}