summaryrefslogtreecommitdiffstats
path: root/sound/soc
diff options
context:
space:
mode:
authorTimothy Pearson <tpearson@raptorengineering.com>2017-08-23 14:45:25 -0500
committerTimothy Pearson <tpearson@raptorengineering.com>2017-08-23 14:45:25 -0500
commitfcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch)
tree22962a4387943edc841c72a4e636a068c66d58fd /sound/soc
downloadast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip
ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz
Initial import of modified Linux 2.6.28 tree
Original upstream URL: git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'sound/soc')
-rw-r--r--sound/soc/Kconfig41
-rw-r--r--sound/soc/Makefile5
-rw-r--r--sound/soc/at32/Kconfig34
-rw-r--r--sound/soc/at32/Makefile11
-rw-r--r--sound/soc/at32/at32-pcm.c492
-rw-r--r--sound/soc/at32/at32-pcm.h79
-rw-r--r--sound/soc/at32/at32-ssc.c849
-rw-r--r--sound/soc/at32/at32-ssc.h59
-rw-r--r--sound/soc/at32/playpaq_wm8510.c513
-rw-r--r--sound/soc/at91/Kconfig10
-rw-r--r--sound/soc/at91/Makefile6
-rw-r--r--sound/soc/at91/at91-pcm.c434
-rw-r--r--sound/soc/at91/at91-pcm.h72
-rw-r--r--sound/soc/at91/at91-ssc.c791
-rw-r--r--sound/soc/at91/at91-ssc.h27
-rw-r--r--sound/soc/au1x/Kconfig32
-rw-r--r--sound/soc/au1x/Makefile13
-rw-r--r--sound/soc/au1x/dbdma2.c421
-rw-r--r--sound/soc/au1x/psc-ac97.c387
-rw-r--r--sound/soc/au1x/psc-i2s.c414
-rw-r--r--sound/soc/au1x/psc.h53
-rw-r--r--sound/soc/au1x/sample-ac97.c144
-rw-r--r--sound/soc/blackfin/Kconfig101
-rw-r--r--sound/soc/blackfin/Makefile21
-rw-r--r--sound/soc/blackfin/bf5xx-ac97-pcm.c457
-rw-r--r--sound/soc/blackfin/bf5xx-ac97-pcm.h29
-rw-r--r--sound/soc/blackfin/bf5xx-ac97.c406
-rw-r--r--sound/soc/blackfin/bf5xx-ac97.h36
-rw-r--r--sound/soc/blackfin/bf5xx-ad1980.c113
-rw-r--r--sound/soc/blackfin/bf5xx-ad73311.c240
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.c288
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.h29
-rw-r--r--sound/soc/blackfin/bf5xx-i2s.c321
-rw-r--r--sound/soc/blackfin/bf5xx-i2s.h14
-rw-r--r--sound/soc/blackfin/bf5xx-sport.c1032
-rw-r--r--sound/soc/blackfin/bf5xx-sport.h194
-rw-r--r--sound/soc/blackfin/bf5xx-ssm2602.c186
-rw-r--r--sound/soc/codecs/Kconfig112
-rw-r--r--sound/soc/codecs/Makefile43
-rw-r--r--sound/soc/codecs/ac97.c179
-rw-r--r--sound/soc/codecs/ac97.h19
-rw-r--r--sound/soc/codecs/ad1980.c308
-rw-r--r--sound/soc/codecs/ad1980.h23
-rw-r--r--sound/soc/codecs/ad73311.c107
-rw-r--r--sound/soc/codecs/ad73311.h90
-rw-r--r--sound/soc/codecs/ak4535.c694
-rw-r--r--sound/soc/codecs/ak4535.h47
-rw-r--r--sound/soc/codecs/cs4270.c765
-rw-r--r--sound/soc/codecs/cs4270.h28
-rw-r--r--sound/soc/codecs/ssm2602.c775
-rw-r--r--sound/soc/codecs/ssm2602.h130
-rw-r--r--sound/soc/codecs/tlv320aic23.c714
-rw-r--r--sound/soc/codecs/tlv320aic23.h122
-rw-r--r--sound/soc/codecs/tlv320aic26.c520
-rw-r--r--sound/soc/codecs/tlv320aic26.h96
-rw-r--r--sound/soc/codecs/tlv320aic3x.c1346
-rw-r--r--sound/soc/codecs/tlv320aic3x.h235
-rw-r--r--sound/soc/codecs/uda1380.c849
-rw-r--r--sound/soc/codecs/uda1380.h90
-rw-r--r--sound/soc/codecs/wm8510.c895
-rw-r--r--sound/soc/codecs/wm8510.h105
-rw-r--r--sound/soc/codecs/wm8580.c1053
-rw-r--r--sound/soc/codecs/wm8580.h42
-rw-r--r--sound/soc/codecs/wm8731.c797
-rw-r--r--sound/soc/codecs/wm8731.h46
-rw-r--r--sound/soc/codecs/wm8750.c1091
-rw-r--r--sound/soc/codecs/wm8750.h69
-rw-r--r--sound/soc/codecs/wm8753.c1882
-rw-r--r--sound/soc/codecs/wm8753.h127
-rw-r--r--sound/soc/codecs/wm8900.c1541
-rw-r--r--sound/soc/codecs/wm8900.h64
-rw-r--r--sound/soc/codecs/wm8903.c1813
-rw-r--r--sound/soc/codecs/wm8903.h1463
-rw-r--r--sound/soc/codecs/wm8971.c941
-rw-r--r--sound/soc/codecs/wm8971.h64
-rw-r--r--sound/soc/codecs/wm8990.c1635
-rw-r--r--sound/soc/codecs/wm8990.h843
-rw-r--r--sound/soc/codecs/wm9712.c750
-rw-r--r--sound/soc/codecs/wm9712.h14
-rw-r--r--sound/soc/codecs/wm9713.c1306
-rw-r--r--sound/soc/codecs/wm9713.h53
-rw-r--r--sound/soc/davinci/Kconfig19
-rw-r--r--sound/soc/davinci/Makefile11
-rw-r--r--sound/soc/davinci/davinci-evm.c202
-rw-r--r--sound/soc/davinci/davinci-i2s.c409
-rw-r--r--sound/soc/davinci/davinci-i2s.h17
-rw-r--r--sound/soc/davinci/davinci-pcm.c389
-rw-r--r--sound/soc/davinci/davinci-pcm.h29
-rw-r--r--sound/soc/fsl/Kconfig27
-rw-r--r--sound/soc/fsl/Makefile11
-rw-r--r--sound/soc/fsl/fsl_dma.c858
-rw-r--r--sound/soc/fsl/fsl_dma.h149
-rw-r--r--sound/soc/fsl/fsl_ssi.c697
-rw-r--r--sound/soc/fsl/fsl_ssi.h224
-rw-r--r--sound/soc/fsl/mpc5200_psc_i2s.c886
-rw-r--r--sound/soc/fsl/mpc8610_hpcd.c625
-rw-r--r--sound/soc/fsl/soc-of-simple.c171
-rw-r--r--sound/soc/omap/Kconfig23
-rw-r--r--sound/soc/omap/Makefile13
-rw-r--r--sound/soc/omap/n810.c391
-rw-r--r--sound/soc/omap/omap-mcbsp.c500
-rw-r--r--sound/soc/omap/omap-mcbsp.h55
-rw-r--r--sound/soc/omap/omap-pcm.c359
-rw-r--r--sound/soc/omap/omap-pcm.h35
-rw-r--r--sound/soc/omap/osk5912.c232
-rw-r--r--sound/soc/pxa/Kconfig77
-rw-r--r--sound/soc/pxa/Makefile23
-rw-r--r--sound/soc/pxa/corgi.c372
-rw-r--r--sound/soc/pxa/e800_wm9712.c89
-rw-r--r--sound/soc/pxa/em-x270.c102
-rw-r--r--sound/soc/pxa/poodle.c341
-rw-r--r--sound/soc/pxa/pxa2xx-ac97.c232
-rw-r--r--sound/soc/pxa/pxa2xx-ac97.h22
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.c410
-rw-r--r--sound/soc/pxa/pxa2xx-i2s.h20
-rw-r--r--sound/soc/pxa/pxa2xx-pcm.c123
-rw-r--r--sound/soc/pxa/pxa2xx-pcm.h19
-rw-r--r--sound/soc/pxa/spitz.c375
-rw-r--r--sound/soc/pxa/tosa.c292
-rw-r--r--sound/soc/s3c24xx/Kconfig46
-rw-r--r--sound/soc/s3c24xx/Makefile19
-rw-r--r--sound/soc/s3c24xx/lm4857.h32
-rw-r--r--sound/soc/s3c24xx/ln2440sbc_alc650.c85
-rw-r--r--sound/soc/s3c24xx/neo1973_wm8753.c722
-rw-r--r--sound/soc/s3c24xx/s3c2412-i2s.c745
-rw-r--r--sound/soc/s3c24xx/s3c2412-i2s.h38
-rw-r--r--sound/soc/s3c24xx/s3c2443-ac97.c398
-rw-r--r--sound/soc/s3c24xx/s3c24xx-ac97.h31
-rw-r--r--sound/soc/s3c24xx/s3c24xx-i2s.c483
-rw-r--r--sound/soc/s3c24xx/s3c24xx-i2s.h37
-rw-r--r--sound/soc/s3c24xx/s3c24xx-pcm.c470
-rw-r--r--sound/soc/s3c24xx/s3c24xx-pcm.h31
-rw-r--r--sound/soc/s3c24xx/smdk2443_wm9710.c81
-rw-r--r--sound/soc/sh/Kconfig38
-rw-r--r--sound/soc/sh/Makefile14
-rw-r--r--sound/soc/sh/dma-sh7760.c353
-rw-r--r--sound/soc/sh/hac.c318
-rw-r--r--sound/soc/sh/sh7760-ac97.c91
-rw-r--r--sound/soc/sh/ssi.c399
-rw-r--r--sound/soc/soc-core.c1891
-rw-r--r--sound/soc/soc-dapm.c1545
141 files changed, 48936 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
new file mode 100644
index 0000000..4dfda66
--- /dev/null
+++ b/sound/soc/Kconfig
@@ -0,0 +1,41 @@
+#
+# SoC audio configuration
+#
+
+menuconfig SND_SOC
+ tristate "ALSA for SoC audio support"
+ select SND_PCM
+ select AC97_BUS if SND_SOC_AC97_BUS
+ ---help---
+
+ 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 ASoC audio support can also be built as a module. If so, the module
+ will be called snd-soc-core.
+
+if SND_SOC
+
+config SND_SOC_AC97_BUS
+ bool
+
+# All the supported Soc's
+source "sound/soc/at32/Kconfig"
+source "sound/soc/at91/Kconfig"
+source "sound/soc/au1x/Kconfig"
+source "sound/soc/pxa/Kconfig"
+source "sound/soc/s3c24xx/Kconfig"
+source "sound/soc/sh/Kconfig"
+source "sound/soc/fsl/Kconfig"
+source "sound/soc/davinci/Kconfig"
+source "sound/soc/omap/Kconfig"
+source "sound/soc/blackfin/Kconfig"
+
+# Supported codecs
+source "sound/soc/codecs/Kconfig"
+
+endif # SND_SOC
+
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
new file mode 100644
index 0000000..d849349
--- /dev/null
+++ b/sound/soc/Makefile
@@ -0,0 +1,5 @@
+snd-soc-core-objs := soc-core.o soc-dapm.o
+
+obj-$(CONFIG_SND_SOC) += snd-soc-core.o
+obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/
+obj-$(CONFIG_SND_SOC) += omap/ au1x/ blackfin/
diff --git a/sound/soc/at32/Kconfig b/sound/soc/at32/Kconfig
new file mode 100644
index 0000000..b0765e8
--- /dev/null
+++ b/sound/soc/at32/Kconfig
@@ -0,0 +1,34 @@
+config SND_AT32_SOC
+ tristate "SoC Audio for the Atmel AT32 System-on-a-Chip"
+ depends on AVR32 && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the AT32 SSC interface. You will also need to
+ to select the audio interfaces to support below.
+
+
+config SND_AT32_SOC_SSC
+ tristate
+
+
+
+config SND_AT32_SOC_PLAYPAQ
+ tristate "SoC Audio support for PlayPaq with WM8510"
+ depends on SND_AT32_SOC && BOARD_PLAYPAQ
+ select SND_AT32_SOC_SSC
+ select SND_SOC_WM8510
+ help
+ Say Y or M here if you want to add support for SoC audio
+ on the LRS PlayPaq.
+
+
+
+config SND_AT32_SOC_PLAYPAQ_SLAVE
+ bool "Run CODEC on PlayPaq in slave mode"
+ depends on SND_AT32_SOC_PLAYPAQ
+ default n
+ help
+ Say Y if you want to run with the AT32 SSC generating the BCLK
+ and FRAME signals on the PlayPaq. Unless you want to play
+ with the AT32 as the SSC master, you probably want to say N here,
+ as this will give you better sound quality.
diff --git a/sound/soc/at32/Makefile b/sound/soc/at32/Makefile
new file mode 100644
index 0000000..c03e55e
--- /dev/null
+++ b/sound/soc/at32/Makefile
@@ -0,0 +1,11 @@
+# AT32 Platform Support
+snd-soc-at32-objs := at32-pcm.o
+snd-soc-at32-ssc-objs := at32-ssc.o
+
+obj-$(CONFIG_SND_AT32_SOC) += snd-soc-at32.o
+obj-$(CONFIG_SND_AT32_SOC_SSC) += snd-soc-at32-ssc.o
+
+# AT32 Machine Support
+snd-soc-playpaq-objs := playpaq_wm8510.o
+
+obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
diff --git a/sound/soc/at32/at32-pcm.c b/sound/soc/at32/at32-pcm.c
new file mode 100644
index 0000000..c83584f
--- /dev/null
+++ b/sound/soc/at32/at32-pcm.c
@@ -0,0 +1,492 @@
+/* sound/soc/at32/at32-pcm.c
+ * ASoC PCM interface for Atmel AT32 SoC
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Note that this is basically a port of the sound/soc/at91-pcm.c to
+ * the AVR32 kernel. Thanks to Frank Mandarino for that code.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/atmel_pdc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "at32-pcm.h"
+
+
+
+/*--------------------------------------------------------------------------*\
+ * Hardware definition
+\*--------------------------------------------------------------------------*/
+/* TODO: These values were taken from the AT91 platform driver, check
+ * them against real values for AT32
+ */
+static const struct snd_pcm_hardware at32_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE),
+
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192, /* 512 frames * 16 bytes / frame */
+ .periods_min = 2,
+ .periods_max = 1024,
+ .buffer_bytes_max = 32 * 1024,
+};
+
+
+
+/*--------------------------------------------------------------------------*\
+ * Data types
+\*--------------------------------------------------------------------------*/
+struct at32_runtime_data {
+ struct at32_pcm_dma_params *params;
+ dma_addr_t dma_buffer; /* physical address of DMA buffer */
+ dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
+ size_t period_size;
+
+ dma_addr_t period_ptr; /* physical address of next period */
+ int periods; /* period index of period_ptr */
+
+ /* Save PDC registers (for power management) */
+ u32 pdc_xpr_save;
+ u32 pdc_xcr_save;
+ u32 pdc_xnpr_save;
+ u32 pdc_xncr_save;
+};
+
+
+
+/*--------------------------------------------------------------------------*\
+ * Helper functions
+\*--------------------------------------------------------------------------*/
+static int at32_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *dmabuf = &substream->dma_buffer;
+ size_t size = at32_pcm_hardware.buffer_bytes_max;
+
+ dmabuf->dev.type = SNDRV_DMA_TYPE_DEV;
+ dmabuf->dev.dev = pcm->card->dev;
+ dmabuf->private_data = NULL;
+ dmabuf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &dmabuf->addr, GFP_KERNEL);
+ pr_debug("at32_pcm: preallocate_dma_buffer: "
+ "area=%p, addr=%p, size=%ld\n",
+ (void *)dmabuf->area, (void *)dmabuf->addr, size);
+
+ if (!dmabuf->area)
+ return -ENOMEM;
+
+ dmabuf->bytes = size;
+ return 0;
+}
+
+
+
+/*--------------------------------------------------------------------------*\
+ * ISR
+\*--------------------------------------------------------------------------*/
+static void at32_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *rtd = substream->runtime;
+ struct at32_runtime_data *prtd = rtd->private_data;
+ struct at32_pcm_dma_params *params = prtd->params;
+ static int count;
+
+ count++;
+ if (ssc_sr & params->mask->ssc_endbuf) {
+ pr_warning("at32-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "underrun" : "overrun", params->name, ssc_sr, count);
+
+ /* re-start the PDC */
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end)
+ prtd->period_ptr = prtd->dma_buffer;
+
+
+ ssc_writex(params->ssc->regs, params->pdc->xpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_enable);
+ }
+
+
+ if (ssc_sr & params->mask->ssc_endx) {
+ /* Load the PDC next pointer and counter registers */
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end)
+ prtd->period_ptr = prtd->dma_buffer;
+ ssc_writex(params->ssc->regs, params->pdc->xnpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+ }
+
+
+ snd_pcm_period_elapsed(substream);
+}
+
+
+
+/*--------------------------------------------------------------------------*\
+ * PCM operations
+\*--------------------------------------------------------------------------*/
+static int at32_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at32_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ /* this may get called several times by oss emulation
+ * with different params
+ */
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->params = rtd->dai->cpu_dai->dma_data;
+ prtd->params->dma_intr_handler = at32_pcm_dma_irq;
+
+ prtd->dma_buffer = runtime->dma_addr;
+ prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
+ prtd->period_size = params_period_bytes(params);
+
+ pr_debug("hw_params: DMA for %s initialized "
+ "(dma_bytes=%ld, period_size=%ld)\n",
+ prtd->params->name, runtime->dma_bytes, prtd->period_size);
+
+ return 0;
+}
+
+
+
+static int at32_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct at32_runtime_data *prtd = substream->runtime->private_data;
+ struct at32_pcm_dma_params *params = prtd->params;
+
+ if (params != NULL) {
+ ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+ params->mask->pdc_disable);
+ prtd->params->dma_intr_handler = NULL;
+ }
+
+ return 0;
+}
+
+
+
+static int at32_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct at32_runtime_data *prtd = substream->runtime->private_data;
+ struct at32_pcm_dma_params *params = prtd->params;
+
+ ssc_writex(params->ssc->regs, SSC_IDR,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+
+ return 0;
+}
+
+
+static int at32_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *rtd = substream->runtime;
+ struct at32_runtime_data *prtd = rtd->private_data;
+ struct at32_pcm_dma_params *params = prtd->params;
+ int ret = 0;
+
+ pr_debug("at32_pcm_trigger: buffer_size = %ld, "
+ "dma_area = %p, dma_bytes = %ld\n",
+ rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->period_ptr = prtd->dma_buffer;
+
+ ssc_writex(params->ssc->regs, params->pdc->xpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ prtd->period_ptr += prtd->period_size;
+ ssc_writex(params->ssc->regs, params->pdc->xnpr,
+ prtd->period_ptr);
+ ssc_writex(params->ssc->regs, params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ pr_debug("trigger: period_ptr=%lx, xpr=%x, "
+ "xcr=%d, xnpr=%x, xncr=%d\n",
+ (unsigned long)prtd->period_ptr,
+ ssc_readx(params->ssc->regs, params->pdc->xpr),
+ ssc_readx(params->ssc->regs, params->pdc->xcr),
+ ssc_readx(params->ssc->regs, params->pdc->xnpr),
+ ssc_readx(params->ssc->regs, params->pdc->xncr));
+
+ ssc_writex(params->ssc->regs, SSC_IER,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+ ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+ params->mask->pdc_enable);
+
+ pr_debug("sr=%x, imr=%x\n",
+ ssc_readx(params->ssc->regs, SSC_SR),
+ ssc_readx(params->ssc->regs, SSC_IER));
+ break; /* SNDRV_PCM_TRIGGER_START */
+
+
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+ break;
+
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_enable);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+
+
+static snd_pcm_uframes_t at32_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at32_runtime_data *prtd = runtime->private_data;
+ struct at32_pcm_dma_params *params = prtd->params;
+ dma_addr_t ptr;
+ snd_pcm_uframes_t x;
+
+ ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
+ x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+
+ return x;
+}
+
+
+
+static int at32_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at32_runtime_data *prtd;
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &at32_pcm_hardware);
+
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ runtime->private_data = prtd;
+
+
+out:
+ return ret;
+}
+
+
+
+static int at32_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct at32_runtime_data *prtd = substream->runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+
+static int at32_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+
+
+static struct snd_pcm_ops at32_pcm_ops = {
+ .open = at32_pcm_open,
+ .close = at32_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = at32_pcm_hw_params,
+ .hw_free = at32_pcm_hw_free,
+ .prepare = at32_pcm_prepare,
+ .trigger = at32_pcm_trigger,
+ .pointer = at32_pcm_pointer,
+ .mmap = at32_pcm_mmap,
+};
+
+
+
+/*--------------------------------------------------------------------------*\
+ * ASoC platform driver
+\*--------------------------------------------------------------------------*/
+static u64 at32_pcm_dmamask = 0xffffffff;
+
+static int at32_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &at32_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = at32_pcm_preallocate_dma_buffer(
+ pcm, SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ pr_debug("at32-pcm: Allocating PCM capture DMA buffer\n");
+ ret = at32_pcm_preallocate_dma_buffer(
+ pcm, SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+
+out:
+ return ret;
+}
+
+
+
+static void at32_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (substream == NULL)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+
+
+#ifdef CONFIG_PM
+static int at32_pcm_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct at32_runtime_data *prtd;
+ struct at32_pcm_dma_params *params;
+
+ if (runtime == NULL)
+ return 0;
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* Disable the PDC and save the PDC registers */
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+ params->mask->pdc_disable);
+
+ prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
+ prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
+ prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
+ prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
+
+ return 0;
+}
+
+
+
+static int at32_pcm_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct at32_runtime_data *prtd;
+ struct at32_pcm_dma_params *params;
+
+ if (runtime == NULL)
+ return 0;
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* Restore the PDC registers and enable the PDC */
+ ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
+ ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
+
+ ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, params->mask->pdc_enable);
+ return 0;
+}
+#else /* CONFIG_PM */
+# define at32_pcm_suspend NULL
+# define at32_pcm_resume NULL
+#endif /* CONFIG_PM */
+
+
+
+struct snd_soc_platform at32_soc_platform = {
+ .name = "at32-audio",
+ .pcm_ops = &at32_pcm_ops,
+ .pcm_new = at32_pcm_new,
+ .pcm_free = at32_pcm_free_dma_buffers,
+ .suspend = at32_pcm_suspend,
+ .resume = at32_pcm_resume,
+};
+EXPORT_SYMBOL_GPL(at32_soc_platform);
+
+
+
+MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
+MODULE_DESCRIPTION("Atmel AT32 PCM module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/at32/at32-pcm.h b/sound/soc/at32/at32-pcm.h
new file mode 100644
index 0000000..2a52430
--- /dev/null
+++ b/sound/soc/at32/at32-pcm.h
@@ -0,0 +1,79 @@
+/* sound/soc/at32/at32-pcm.h
+ * ASoC PCM interface for Atmel AT32 SoC
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SOUND_SOC_AT32_AT32_PCM_H
+#define __SOUND_SOC_AT32_AT32_PCM_H __FILE__
+
+#include <linux/atmel-ssc.h>
+
+
+/*
+ * Registers and status bits that are required by the PCM driver
+ * TODO: Is ptcr really used?
+ */
+struct at32_pdc_regs {
+ u32 xpr; /* PDC RX/TX pointer */
+ u32 xcr; /* PDC RX/TX counter */
+ u32 xnpr; /* PDC next RX/TX pointer */
+ u32 xncr; /* PDC next RX/TX counter */
+ u32 ptcr; /* PDC transfer control */
+};
+
+
+
+/*
+ * SSC mask info
+ */
+struct at32_ssc_mask {
+ u32 ssc_enable; /* SSC RX/TX enable */
+ u32 ssc_disable; /* SSC RX/TX disable */
+ u32 ssc_endx; /* SSC ENDTX or ENDRX */
+ u32 ssc_endbuf; /* SSC TXBUFF or RXBUFF */
+ u32 pdc_enable; /* PDC RX/TX enable */
+ u32 pdc_disable; /* PDC RX/TX disable */
+};
+
+
+
+/*
+ * This structure, shared between the PCM driver and the interface,
+ * contains all information required by the PCM driver to perform the
+ * PDC DMA operation. All fields except dma_intr_handler() are initialized
+ * by the interface. The dms_intr_handler() pointer is set by the PCM
+ * driver and called by the interface SSC interrupt handler if it is
+ * non-NULL.
+ */
+struct at32_pcm_dma_params {
+ char *name; /* stream identifier */
+ int pdc_xfer_size; /* PDC counter increment in bytes */
+ struct ssc_device *ssc; /* SSC device for stream */
+ struct at32_pdc_regs *pdc; /* PDC register info */
+ struct at32_ssc_mask *mask; /* SSC mask info */
+ struct snd_pcm_substream *substream;
+ void (*dma_intr_handler) (u32, struct snd_pcm_substream *);
+};
+
+
+
+/*
+ * The AT32 ASoC platform driver
+ */
+extern struct snd_soc_platform at32_soc_platform;
+
+
+
+/*
+ * SSC register access (since ssc_writel() / ssc_readl() require literal name)
+ */
+#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
+#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
+
+#endif /* __SOUND_SOC_AT32_AT32_PCM_H */
diff --git a/sound/soc/at32/at32-ssc.c b/sound/soc/at32/at32-ssc.c
new file mode 100644
index 0000000..4ef6492
--- /dev/null
+++ b/sound/soc/at32/at32-ssc.c
@@ -0,0 +1,849 @@
+/* sound/soc/at32/at32-ssc.c
+ * ASoC platform driver for AT32 using SSC as DAI
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Note that this is basically a port of the sound/soc/at91-ssc.c to
+ * the AVR32 kernel. Thanks to Frank Mandarino for that code.
+ */
+
+/* #define DEBUG */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/atmel_pdc.h>
+#include <linux/atmel-ssc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "at32-pcm.h"
+#include "at32-ssc.h"
+
+
+
+/*-------------------------------------------------------------------------*\
+ * Constants
+\*-------------------------------------------------------------------------*/
+#define NUM_SSC_DEVICES 3
+
+/*
+ * SSC direction masks
+ */
+#define SSC_DIR_MASK_UNUSED 0
+#define SSC_DIR_MASK_PLAYBACK 1
+#define SSC_DIR_MASK_CAPTURE 2
+
+/*
+ * SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
+ * are expected to be used with SSC_BF
+ */
+/* START bit field values */
+#define SSC_START_CONTINUOUS 0
+#define SSC_START_TX_RX 1
+#define SSC_START_LOW_RF 2
+#define SSC_START_HIGH_RF 3
+#define SSC_START_FALLING_RF 4
+#define SSC_START_RISING_RF 5
+#define SSC_START_LEVEL_RF 6
+#define SSC_START_EDGE_RF 7
+#define SSS_START_COMPARE_0 8
+
+/* CKI bit field values */
+#define SSC_CKI_FALLING 0
+#define SSC_CKI_RISING 1
+
+/* CKO bit field values */
+#define SSC_CKO_NONE 0
+#define SSC_CKO_CONTINUOUS 1
+#define SSC_CKO_TRANSFER 2
+
+/* CKS bit field values */
+#define SSC_CKS_DIV 0
+#define SSC_CKS_CLOCK 1
+#define SSC_CKS_PIN 2
+
+/* FSEDGE bit field values */
+#define SSC_FSEDGE_POSITIVE 0
+#define SSC_FSEDGE_NEGATIVE 1
+
+/* FSOS bit field values */
+#define SSC_FSOS_NONE 0
+#define SSC_FSOS_NEGATIVE 1
+#define SSC_FSOS_POSITIVE 2
+#define SSC_FSOS_LOW 3
+#define SSC_FSOS_HIGH 4
+#define SSC_FSOS_TOGGLE 5
+
+#define START_DELAY 1
+
+
+
+/*-------------------------------------------------------------------------*\
+ * Module data
+\*-------------------------------------------------------------------------*/
+/*
+ * SSC PDC registered required by the PCM DMA engine
+ */
+static struct at32_pdc_regs pdc_tx_reg = {
+ .xpr = SSC_PDC_TPR,
+ .xcr = SSC_PDC_TCR,
+ .xnpr = SSC_PDC_TNPR,
+ .xncr = SSC_PDC_TNCR,
+};
+
+
+
+static struct at32_pdc_regs pdc_rx_reg = {
+ .xpr = SSC_PDC_RPR,
+ .xcr = SSC_PDC_RCR,
+ .xnpr = SSC_PDC_RNPR,
+ .xncr = SSC_PDC_RNCR,
+};
+
+
+
+/*
+ * SSC and PDC status bits for transmit and receive
+ */
+static struct at32_ssc_mask ssc_tx_mask = {
+ .ssc_enable = SSC_BIT(CR_TXEN),
+ .ssc_disable = SSC_BIT(CR_TXDIS),
+ .ssc_endx = SSC_BIT(SR_ENDTX),
+ .ssc_endbuf = SSC_BIT(SR_TXBUFE),
+ .pdc_enable = SSC_BIT(PDC_PTCR_TXTEN),
+ .pdc_disable = SSC_BIT(PDC_PTCR_TXTDIS),
+};
+
+
+
+static struct at32_ssc_mask ssc_rx_mask = {
+ .ssc_enable = SSC_BIT(CR_RXEN),
+ .ssc_disable = SSC_BIT(CR_RXDIS),
+ .ssc_endx = SSC_BIT(SR_ENDRX),
+ .ssc_endbuf = SSC_BIT(SR_RXBUFF),
+ .pdc_enable = SSC_BIT(PDC_PTCR_RXTEN),
+ .pdc_disable = SSC_BIT(PDC_PTCR_RXTDIS),
+};
+
+
+
+/*
+ * DMA parameters for each SSC
+ */
+static struct at32_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
+ {
+ {
+ .name = "SSC0 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC0 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ },
+ },
+ {
+ {
+ .name = "SSC1 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC1 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ },
+ },
+ {
+ {
+ .name = "SSC2 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC2 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ },
+ },
+};
+
+
+
+static struct at32_ssc_info ssc_info[NUM_SSC_DEVICES] = {
+ {
+ .name = "ssc0",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+ {
+ .name = "ssc1",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+ {
+ .name = "ssc2",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
+ .dir_mask = SSC_DIR_MASK_UNUSED,
+ .initialized = 0,
+ },
+};
+
+
+
+
+/*-------------------------------------------------------------------------*\
+ * ISR
+\*-------------------------------------------------------------------------*/
+/*
+ * SSC interrupt handler. Passes PDC interrupts to the DMA interrupt
+ * handler in the PCM driver.
+ */
+static irqreturn_t at32_ssc_interrupt(int irq, void *dev_id)
+{
+ struct at32_ssc_info *ssc_p = dev_id;
+ struct at32_pcm_dma_params *dma_params;
+ u32 ssc_sr;
+ u32 ssc_substream_mask;
+ int i;
+
+ ssc_sr = (ssc_readl(ssc_p->ssc->regs, SR) &
+ ssc_readl(ssc_p->ssc->regs, IMR));
+
+ /*
+ * Loop through substreams attached to this SSC. If a DMA-related
+ * interrupt occured on that substream, call the DMA interrupt
+ * handler function, if one has been registered in the dma_param
+ * structure by the PCM driver.
+ */
+ for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
+ dma_params = ssc_p->dma_params[i];
+
+ if ((dma_params != NULL) &&
+ (dma_params->dma_intr_handler != NULL)) {
+ ssc_substream_mask = (dma_params->mask->ssc_endx |
+ dma_params->mask->ssc_endbuf);
+ if (ssc_sr & ssc_substream_mask) {
+ dma_params->dma_intr_handler(ssc_sr,
+ dma_params->
+ substream);
+ }
+ }
+ }
+
+
+ return IRQ_HANDLED;
+}
+
+/*-------------------------------------------------------------------------*\
+ * DAI functions
+\*-------------------------------------------------------------------------*/
+/*
+ * Startup. Only that one substream allowed in each direction.
+ */
+static int at32_ssc_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
+ int dir_mask;
+
+ dir_mask = ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ SSC_DIR_MASK_PLAYBACK : SSC_DIR_MASK_CAPTURE);
+
+ spin_lock_irq(&ssc_p->lock);
+ if (ssc_p->dir_mask & dir_mask) {
+ spin_unlock_irq(&ssc_p->lock);
+ return -EBUSY;
+ }
+ ssc_p->dir_mask |= dir_mask;
+ spin_unlock_irq(&ssc_p->lock);
+
+ return 0;
+}
+
+
+
+/*
+ * Shutdown. Clear DMA parameters and shutdown the SSC if there
+ * are no other substreams open.
+ */
+static void at32_ssc_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
+ struct at32_pcm_dma_params *dma_params;
+ int dir_mask;
+
+ dma_params = ssc_p->dma_params[substream->stream];
+
+ if (dma_params != NULL) {
+ ssc_writel(dma_params->ssc->regs, CR,
+ dma_params->mask->ssc_disable);
+ pr_debug("%s disabled SSC_SR=0x%08x\n",
+ (substream->stream ? "receiver" : "transmit"),
+ ssc_readl(ssc_p->ssc->regs, SR));
+
+ dma_params->ssc = NULL;
+ dma_params->substream = NULL;
+ ssc_p->dma_params[substream->stream] = NULL;
+ }
+
+
+ dir_mask = 1 << substream->stream;
+ spin_lock_irq(&ssc_p->lock);
+ ssc_p->dir_mask &= ~dir_mask;
+ if (!ssc_p->dir_mask) {
+ /* Shutdown the SSC clock */
+ pr_debug("at32-ssc: Stopping user %d clock\n",
+ ssc_p->ssc->user);
+ clk_disable(ssc_p->ssc->clk);
+
+ if (ssc_p->initialized) {
+ free_irq(ssc_p->ssc->irq, ssc_p);
+ ssc_p->initialized = 0;
+ }
+
+ /* Reset the SSC */
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+
+ /* clear the SSC dividers */
+ ssc_p->cmr_div = 0;
+ ssc_p->tcmr_period = 0;
+ ssc_p->rcmr_period = 0;
+ }
+ spin_unlock_irq(&ssc_p->lock);
+}
+
+
+
+/*
+ * Set the SSC system clock rate
+ */
+static int at32_ssc_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ /* TODO: What the heck do I do here? */
+ return 0;
+}
+
+
+
+/*
+ * Record DAI format for use by hw_params()
+ */
+static int at32_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct at32_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ ssc_p->daifmt = fmt;
+ return 0;
+}
+
+
+
+/*
+ * Record SSC clock dividers for use in hw_params()
+ */
+static int at32_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct at32_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ switch (div_id) {
+ case AT32_SSC_CMR_DIV:
+ /*
+ * The same master clock divider is used for both
+ * transmit and receive, so if a value has already
+ * been set, it must match this value
+ */
+ if (ssc_p->cmr_div == 0)
+ ssc_p->cmr_div = div;
+ else if (div != ssc_p->cmr_div)
+ return -EBUSY;
+ break;
+
+ case AT32_SSC_TCMR_PERIOD:
+ ssc_p->tcmr_period = div;
+ break;
+
+ case AT32_SSC_RCMR_PERIOD:
+ ssc_p->rcmr_period = div;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * Configure the SSC
+ */
+static int at32_ssc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ int id = rtd->dai->cpu_dai->id;
+ struct at32_ssc_info *ssc_p = &ssc_info[id];
+ struct at32_pcm_dma_params *dma_params;
+ int channels, bits;
+ u32 tfmr, rfmr, tcmr, rcmr;
+ int start_event;
+ int ret;
+
+
+ /*
+ * Currently, there is only one set of dma_params for each direction.
+ * If more are added, this code will have to be changed to select
+ * the proper set
+ */
+ dma_params = &ssc_dma_params[id][substream->stream];
+ dma_params->ssc = ssc_p->ssc;
+ dma_params->substream = substream;
+
+ ssc_p->dma_params[substream->stream] = dma_params;
+
+
+ /*
+ * The cpu_dai->dma_data field is only used to communicate the
+ * appropriate DMA parameters to the PCM driver's hw_params()
+ * function. It should not be used for other purposes as it
+ * is common to all substreams.
+ */
+ rtd->dai->cpu_dai->dma_data = dma_params;
+
+ 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:
+ bits = 16;
+ dma_params->pdc_xfer_size = 2;
+ break;
+
+ case SNDRV_PCM_FORMAT_S24:
+ bits = 24;
+ dma_params->pdc_xfer_size = 4;
+ break;
+
+ case SNDRV_PCM_FORMAT_S32:
+ bits = 32;
+ dma_params->pdc_xfer_size = 4;
+ break;
+
+ default:
+ pr_warning("at32-ssc: Unsupported PCM format %d",
+ params_format(params));
+ return -EINVAL;
+ }
+ pr_debug("at32-ssc: bits = %d, pdc_xfer_size = %d, channels = %d\n",
+ bits, dma_params->pdc_xfer_size, channels);
+
+
+ /*
+ * The SSC only supports up to 16-bit samples in I2S format, due
+ * to the size of the Frame Mode Register FSLEN field.
+ */
+ if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ if (bits > 16) {
+ pr_warning("at32-ssc: "
+ "sample size %d is too large for I2S\n",
+ bits);
+ return -EINVAL;
+ }
+
+
+ /*
+ * Compute the SSC register settings
+ */
+ switch (ssc_p->daifmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_MASTER_MASK)) {
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * I2S format, SSC provides BCLK and LRS clocks.
+ *
+ * The SSC transmit and receive clocks are generated from the
+ * MCK divider, and the BCLK signal is output on the SSC TK line
+ */
+ pr_debug("at32-ssc: SSC mode is I2S BCLK / FRAME master\n");
+ rcmr = (SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period) |
+ SSC_BF(RCMR_STTDLY, START_DELAY) |
+ SSC_BF(RCMR_START, SSC_START_FALLING_RF) |
+ SSC_BF(RCMR_CKI, SSC_CKI_RISING) |
+ SSC_BF(RCMR_CKO, SSC_CKO_NONE) |
+ SSC_BF(RCMR_CKS, SSC_CKS_DIV));
+
+ rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE) |
+ SSC_BF(RFMR_FSLEN, bits - 1) |
+ SSC_BF(RFMR_DATNB, channels - 1) |
+ SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1));
+
+ tcmr = (SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period) |
+ SSC_BF(TCMR_STTDLY, START_DELAY) |
+ SSC_BF(TCMR_START, SSC_START_FALLING_RF) |
+ SSC_BF(TCMR_CKI, SSC_CKI_FALLING) |
+ SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) |
+ SSC_BF(TCMR_CKS, SSC_CKS_DIV));
+
+ tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE) |
+ SSC_BF(TFMR_FSLEN, bits - 1) |
+ SSC_BF(TFMR_DATNB, channels - 1) | SSC_BIT(TFMR_MSBF) |
+ SSC_BF(TFMR_DATLEN, bits - 1));
+ break;
+
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ /*
+ * I2S format, CODEC supplies BCLK and LRC clock.
+ *
+ * The SSC transmit clock is obtained from the BCLK signal
+ * on the TK line, and the SSC receive clock is generated from
+ * the transmit clock.
+ *
+ * For single channel data, one sample is transferred on the
+ * falling edge of the LRC clock. For two channel data, one
+ * sample is transferred on both edges of the LRC clock.
+ */
+ pr_debug("at32-ssc: SSC mode is I2S BCLK / FRAME slave\n");
+ start_event = ((channels == 1) ?
+ SSC_START_FALLING_RF : SSC_START_EDGE_RF);
+
+ rcmr = (SSC_BF(RCMR_STTDLY, START_DELAY) |
+ SSC_BF(RCMR_START, start_event) |
+ SSC_BF(RCMR_CKI, SSC_CKI_RISING) |
+ SSC_BF(RCMR_CKO, SSC_CKO_NONE) |
+ SSC_BF(RCMR_CKS, SSC_CKS_CLOCK));
+
+ rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(RFMR_FSOS, SSC_FSOS_NONE) |
+ SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1));
+
+ tcmr = (SSC_BF(TCMR_STTDLY, START_DELAY) |
+ SSC_BF(TCMR_START, start_event) |
+ SSC_BF(TCMR_CKI, SSC_CKI_FALLING) |
+ SSC_BF(TCMR_CKO, SSC_CKO_NONE) |
+ SSC_BF(TCMR_CKS, SSC_CKS_PIN));
+
+ tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(TFMR_FSOS, SSC_FSOS_NONE) |
+ SSC_BIT(TFMR_MSBF) | SSC_BF(TFMR_DATLEN, bits - 1));
+ break;
+
+
+ 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
+ */
+ pr_debug("at32-ssc: SSC mode is DSP A BCLK / FRAME master\n");
+ rcmr = (SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period) |
+ SSC_BF(RCMR_STTDLY, 1) |
+ SSC_BF(RCMR_START, SSC_START_RISING_RF) |
+ SSC_BF(RCMR_CKI, SSC_CKI_RISING) |
+ SSC_BF(RCMR_CKO, SSC_CKO_NONE) |
+ SSC_BF(RCMR_CKS, SSC_CKS_DIV));
+
+ rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE) |
+ SSC_BF(RFMR_DATNB, channels - 1) |
+ SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1));
+
+ tcmr = (SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period) |
+ SSC_BF(TCMR_STTDLY, 1) |
+ SSC_BF(TCMR_START, SSC_START_RISING_RF) |
+ SSC_BF(TCMR_CKI, SSC_CKI_RISING) |
+ SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) |
+ SSC_BF(TCMR_CKS, SSC_CKS_DIV));
+
+ tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) |
+ SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE) |
+ SSC_BF(TFMR_DATNB, channels - 1) |
+ SSC_BIT(TFMR_MSBF) | SSC_BF(TFMR_DATLEN, bits - 1));
+ break;
+
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ default:
+ pr_warning("at32-ssc: unsupported DAI format 0x%x\n",
+ ssc_p->daifmt);
+ return -EINVAL;
+ break;
+ }
+ pr_debug("at32-ssc: RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
+ rcmr, rfmr, tcmr, tfmr);
+
+
+ if (!ssc_p->initialized) {
+ /* enable peripheral clock */
+ pr_debug("at32-ssc: Starting clock\n");
+ clk_enable(ssc_p->ssc->clk);
+
+ /* Reset the SSC and its PDC registers */
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+
+ ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
+
+ ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
+ ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
+
+ ret = request_irq(ssc_p->ssc->irq, at32_ssc_interrupt, 0,
+ ssc_p->name, ssc_p);
+ if (ret < 0) {
+ pr_warning("at32-ssc: request irq failed (%d)\n", ret);
+ pr_debug("at32-ssc: Stopping clock\n");
+ clk_disable(ssc_p->ssc->clk);
+ return ret;
+ }
+
+ ssc_p->initialized = 1;
+ }
+
+ /* Set SSC clock mode register */
+ ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
+
+ /* set receive clock mode and format */
+ ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
+ ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
+
+ /* set transmit clock mode and format */
+ ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
+ ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
+
+ pr_debug("at32-ssc: SSC initialized\n");
+ return 0;
+}
+
+
+
+static int at32_ssc_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
+ struct at32_pcm_dma_params *dma_params;
+
+ dma_params = ssc_p->dma_params[substream->stream];
+
+ ssc_writel(dma_params->ssc->regs, CR, dma_params->mask->ssc_enable);
+
+ return 0;
+}
+
+
+
+#ifdef CONFIG_PM
+static int at32_ssc_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at32_ssc_info *ssc_p;
+
+ if (!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ /* Save the status register before disabling transmit and receive */
+ ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
+ ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
+
+ /* Save the current interrupt mask, then disable unmasked interrupts */
+ ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
+ ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
+
+ ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
+ ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
+ ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
+ ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
+ ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
+
+ return 0;
+}
+
+
+
+static int at32_ssc_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at32_ssc_info *ssc_p;
+ u32 cr;
+
+ if (!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ /* restore SSC register settings */
+ ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
+ ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
+ ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
+ ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
+ ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
+
+ /* re-enable interrupts */
+ ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
+
+ /* Re-enable recieve and transmit as appropriate */
+ cr = 0;
+ cr |=
+ (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
+ cr |=
+ (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
+ ssc_writel(ssc_p->ssc->regs, CR, cr);
+
+ return 0;
+}
+#else /* CONFIG_PM */
+# define at32_ssc_suspend NULL
+# define at32_ssc_resume NULL
+#endif /* CONFIG_PM */
+
+
+#define AT32_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)
+
+
+#define AT32_SSC_FORMATS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16 | \
+ SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_S32)
+
+
+struct snd_soc_dai at32_ssc_dai[NUM_SSC_DEVICES] = {
+ {
+ .name = "at32-ssc0",
+ .id = 0,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at32_ssc_suspend,
+ .resume = at32_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .ops = {
+ .startup = at32_ssc_startup,
+ .shutdown = at32_ssc_shutdown,
+ .prepare = at32_ssc_prepare,
+ .hw_params = at32_ssc_hw_params,
+ },
+ .dai_ops = {
+ .set_sysclk = at32_ssc_set_dai_sysclk,
+ .set_fmt = at32_ssc_set_dai_fmt,
+ .set_clkdiv = at32_ssc_set_dai_clkdiv,
+ },
+ .private_data = &ssc_info[0],
+ },
+ {
+ .name = "at32-ssc1",
+ .id = 1,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at32_ssc_suspend,
+ .resume = at32_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .ops = {
+ .startup = at32_ssc_startup,
+ .shutdown = at32_ssc_shutdown,
+ .prepare = at32_ssc_prepare,
+ .hw_params = at32_ssc_hw_params,
+ },
+ .dai_ops = {
+ .set_sysclk = at32_ssc_set_dai_sysclk,
+ .set_fmt = at32_ssc_set_dai_fmt,
+ .set_clkdiv = at32_ssc_set_dai_clkdiv,
+ },
+ .private_data = &ssc_info[1],
+ },
+ {
+ .name = "at32-ssc2",
+ .id = 2,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at32_ssc_suspend,
+ .resume = at32_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT32_SSC_RATES,
+ .formats = AT32_SSC_FORMATS,
+ },
+ .ops = {
+ .startup = at32_ssc_startup,
+ .shutdown = at32_ssc_shutdown,
+ .prepare = at32_ssc_prepare,
+ .hw_params = at32_ssc_hw_params,
+ },
+ .dai_ops = {
+ .set_sysclk = at32_ssc_set_dai_sysclk,
+ .set_fmt = at32_ssc_set_dai_fmt,
+ .set_clkdiv = at32_ssc_set_dai_clkdiv,
+ },
+ .private_data = &ssc_info[2],
+ },
+};
+EXPORT_SYMBOL_GPL(at32_ssc_dai);
+
+
+MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
+MODULE_DESCRIPTION("AT32 SSC ASoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/at32/at32-ssc.h b/sound/soc/at32/at32-ssc.h
new file mode 100644
index 0000000..3c052db
--- /dev/null
+++ b/sound/soc/at32/at32-ssc.h
@@ -0,0 +1,59 @@
+/* sound/soc/at32/at32-ssc.h
+ * ASoC SSC interface for Atmel AT32 SoC
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SOUND_SOC_AT32_AT32_SSC_H
+#define __SOUND_SOC_AT32_AT32_SSC_H __FILE__
+
+#include <linux/types.h>
+#include <linux/atmel-ssc.h>
+
+#include "at32-pcm.h"
+
+
+
+struct at32_ssc_state {
+ u32 ssc_cmr;
+ u32 ssc_rcmr;
+ u32 ssc_rfmr;
+ u32 ssc_tcmr;
+ u32 ssc_tfmr;
+ u32 ssc_sr;
+ u32 ssc_imr;
+};
+
+
+
+struct at32_ssc_info {
+ char *name;
+ struct ssc_device *ssc;
+ spinlock_t lock; /* lock for dir_mask */
+ unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
+ unsigned short initialized; /* true if SSC has been initialized */
+ unsigned short daifmt;
+ unsigned short cmr_div;
+ unsigned short tcmr_period;
+ unsigned short rcmr_period;
+ struct at32_pcm_dma_params *dma_params[2];
+ struct at32_ssc_state ssc_state;
+};
+
+
+/* SSC divider ids */
+#define AT32_SSC_CMR_DIV 0 /* MCK divider for BCLK */
+#define AT32_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
+#define AT32_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
+
+
+extern struct snd_soc_dai at32_ssc_dai[];
+
+
+
+#endif /* __SOUND_SOC_AT32_AT32_SSC_H */
diff --git a/sound/soc/at32/playpaq_wm8510.c b/sound/soc/at32/playpaq_wm8510.c
new file mode 100644
index 0000000..b1966e4
--- /dev/null
+++ b/sound/soc/at32/playpaq_wm8510.c
@@ -0,0 +1,513 @@
+/* sound/soc/at32/playpaq_wm8510.c
+ * ASoC machine driver for PlayPaq using WM8510 codec
+ *
+ * Copyright (C) 2008 Long Range Systems
+ * Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c
+ *
+ * NOTE: If you don't have the AT32 enhanced portmux configured (which
+ * isn't currently in the mainline or Atmel patched kernel), you will
+ * need to set the MCLK pin (PA30) to peripheral A in your board initialization
+ * code. Something like:
+ * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0);
+ *
+ */
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <mach/at32ap700x.h>
+#include <mach/portmux.h>
+
+#include "../codecs/wm8510.h"
+#include "at32-pcm.h"
+#include "at32-ssc.h"
+
+
+/*-------------------------------------------------------------------------*\
+ * constants
+\*-------------------------------------------------------------------------*/
+#define MCLK_PIN GPIO_PIN_PA(30)
+#define MCLK_PERIPH GPIO_PERIPH_A
+
+
+/*-------------------------------------------------------------------------*\
+ * data types
+\*-------------------------------------------------------------------------*/
+/* SSC clocking data */
+struct ssc_clock_data {
+ /* CMR div */
+ unsigned int cmr_div;
+
+ /* Frame period (as needed by xCMR.PERIOD) */
+ unsigned int period;
+
+ /* The SSC clock rate these settings where calculated for */
+ unsigned long ssc_rate;
+};
+
+
+/*-------------------------------------------------------------------------*\
+ * module data
+\*-------------------------------------------------------------------------*/
+static struct clk *_gclk0;
+static struct clk *_pll0;
+
+#define CODEC_CLK (_gclk0)
+
+
+/*-------------------------------------------------------------------------*\
+ * Sound SOC operations
+\*-------------------------------------------------------------------------*/
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at32_ssc_info *ssc_p = cpu_dai->private_data;
+ struct ssc_device *ssc = ssc_p->ssc;
+ struct ssc_clock_data cd;
+ unsigned int rate, width_bits, channels;
+ unsigned int bitrate, ssc_div;
+ unsigned actual_rate;
+
+
+ /*
+ * Figure out required bitrate
+ */
+ rate = params_rate(params);
+ channels = params_channels(params);
+ width_bits = snd_pcm_format_physical_width(params_format(params));
+ bitrate = rate * width_bits * channels;
+
+
+ /*
+ * Figure out required SSC divider and period for required bitrate
+ */
+ cd.ssc_rate = clk_get_rate(ssc->clk);
+ ssc_div = cd.ssc_rate / bitrate;
+ cd.cmr_div = ssc_div / 2;
+ if (ssc_div & 1) {
+ /* round cmr_div up */
+ cd.cmr_div++;
+ }
+ cd.period = width_bits - 1;
+
+
+ /*
+ * Find actual rate, compare to requested rate
+ */
+ actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
+ pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n",
+ rate, actual_rate);
+
+
+ return cd;
+}
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+
+static int playpaq_wm8510_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct at32_ssc_info *ssc_p = cpu_dai->private_data;
+ struct ssc_device *ssc = ssc_p->ssc;
+ unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
+ int ret;
+
+
+ /* Due to difficulties with getting the correct clocks from the AT32's
+ * PLL0, we're going to let the CODEC be in charge of all the clocks
+ */
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+#else
+ struct ssc_clock_data cd;
+ const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+#endif
+
+ if (ssc == NULL) {
+ pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
+ return -EINVAL;
+ }
+
+
+ /*
+ * Figure out PLL and BCLK dividers for WM8510
+ */
+ switch (params_rate(params)) {
+ case 48000:
+ pll_out = 12288000;
+ mclk_div = WM8510_MCLKDIV_1;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 44100:
+ pll_out = 11289600;
+ mclk_div = WM8510_MCLKDIV_1;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 22050:
+ pll_out = 11289600;
+ mclk_div = WM8510_MCLKDIV_2;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 16000:
+ pll_out = 12288000;
+ mclk_div = WM8510_MCLKDIV_3;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 11025:
+ pll_out = 11289600;
+ mclk_div = WM8510_MCLKDIV_4;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ case 8000:
+ pll_out = 12288000;
+ mclk_div = WM8510_MCLKDIV_6;
+ bclk = WM8510_BCLKDIV_8;
+ break;
+
+ default:
+ pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+
+ /*
+ * set CPU and CODEC DAI configuration
+ */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CODEC DAI format (%d)\n",
+ ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CPU DAI format (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ /*
+ * Set CPU clock configuration
+ */
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
+ pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
+ cd.cmr_div, cd.period);
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
+ ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
+ cd.period);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: "
+ "Failed to set CPU transmit period (%d)\n",
+ ret);
+ return ret;
+ }
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+ /*
+ * Set CODEC clock configuration
+ */
+ pr_debug("playpaq_wm8510: "
+ "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
+ clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
+
+
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
+ if (ret < 0) {
+ pr_warning
+ ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
+ ret);
+ return ret;
+ }
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ clk_get_rate(CODEC_CLK), pll_out);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
+ if (ret < 0) {
+ pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
+ ret);
+ return ret;
+ }
+
+
+ return 0;
+}
+
+
+
+static struct snd_soc_ops playpaq_wm8510_ops = {
+ .hw_params = playpaq_wm8510_hw_params,
+};
+
+
+
+static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Int Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* speaker connected to SPKOUT */
+ {"Ext Spk", NULL, "SPKOUTP"},
+ {"Ext Spk", NULL, "SPKOUTN"},
+
+ {"Mic Bias", NULL, "Int Mic"},
+ {"MICN", NULL, "Mic Bias"},
+ {"MICP", NULL, "Mic Bias"},
+};
+
+
+
+static int playpaq_wm8510_init(struct snd_soc_codec *codec)
+{
+ int i;
+
+ /*
+ * Add DAPM widgets
+ */
+ for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
+ snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
+
+
+
+ /*
+ * Setup audio path interconnects
+ */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+
+
+ /* always connected pins */
+ snd_soc_dapm_enable_pin(codec, "Int Mic");
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ snd_soc_dapm_sync(codec);
+
+
+
+ /* Make CSB show PLL rate */
+ snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
+ WM8510_OPCLKDIV_1 | 4);
+
+ return 0;
+}
+
+
+
+static struct snd_soc_dai_link playpaq_wm8510_dai = {
+ .name = "WM8510",
+ .stream_name = "WM8510 PCM",
+ .cpu_dai = &at32_ssc_dai[0],
+ .codec_dai = &wm8510_dai,
+ .init = playpaq_wm8510_init,
+ .ops = &playpaq_wm8510_ops,
+};
+
+
+
+static struct snd_soc_machine snd_soc_machine_playpaq = {
+ .name = "LRS_PlayPaq_WM8510",
+ .dai_link = &playpaq_wm8510_dai,
+ .num_links = 1,
+};
+
+
+
+static struct wm8510_setup_data playpaq_wm8510_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1a,
+};
+
+
+
+static struct snd_soc_device playpaq_wm8510_snd_devdata = {
+ .machine = &snd_soc_machine_playpaq,
+ .platform = &at32_soc_platform,
+ .codec_dev = &soc_codec_dev_wm8510,
+ .codec_data = &playpaq_wm8510_setup,
+};
+
+static struct platform_device *playpaq_snd_device;
+
+
+static int __init playpaq_asoc_init(void)
+{
+ int ret = 0;
+ struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
+ struct ssc_device *ssc = NULL;
+
+
+ /*
+ * Request SSC device
+ */
+ ssc = ssc_request(0);
+ if (IS_ERR(ssc)) {
+ ret = PTR_ERR(ssc);
+ goto err_ssc;
+ }
+ ssc_p->ssc = ssc;
+
+
+ /*
+ * Configure MCLK for WM8510
+ */
+ _gclk0 = clk_get(NULL, "gclk0");
+ if (IS_ERR(_gclk0)) {
+ _gclk0 = NULL;
+ goto err_gclk0;
+ }
+ _pll0 = clk_get(NULL, "pll0");
+ if (IS_ERR(_pll0)) {
+ _pll0 = NULL;
+ goto err_pll0;
+ }
+ if (clk_set_parent(_gclk0, _pll0)) {
+ pr_warning("snd-soc-playpaq: "
+ "Failed to set PLL0 as parent for DAC clock\n");
+ goto err_set_clk;
+ }
+ clk_set_rate(CODEC_CLK, 12000000);
+ clk_enable(CODEC_CLK);
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+ at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
+#endif
+
+
+ /*
+ * Create and register platform device
+ */
+ playpaq_snd_device = platform_device_alloc("soc-audio", 0);
+ if (playpaq_snd_device == NULL) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
+ playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
+
+ ret = platform_device_add(playpaq_snd_device);
+ if (ret) {
+ pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
+ ret);
+ goto err_device_add;
+ }
+
+ return 0;
+
+
+err_device_add:
+ if (playpaq_snd_device != NULL) {
+ platform_device_put(playpaq_snd_device);
+ playpaq_snd_device = NULL;
+ }
+err_device_alloc:
+err_set_clk:
+ if (_pll0 != NULL) {
+ clk_put(_pll0);
+ _pll0 = NULL;
+ }
+err_pll0:
+ if (_gclk0 != NULL) {
+ clk_put(_gclk0);
+ _gclk0 = NULL;
+ }
+err_gclk0:
+ ssc_free(ssc);
+err_ssc:
+ return ret;
+}
+
+
+static void __exit playpaq_asoc_exit(void)
+{
+ struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
+ struct ssc_device *ssc;
+
+ if (ssc_p != NULL) {
+ ssc = ssc_p->ssc;
+ if (ssc != NULL)
+ ssc_free(ssc);
+ ssc_p->ssc = NULL;
+ }
+
+ if (_gclk0 != NULL) {
+ clk_put(_gclk0);
+ _gclk0 = NULL;
+ }
+ if (_pll0 != NULL) {
+ clk_put(_pll0);
+ _pll0 = NULL;
+ }
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+ at32_free_pin(MCLK_PIN);
+#endif
+
+ platform_device_unregister(playpaq_snd_device);
+ playpaq_snd_device = NULL;
+}
+
+module_init(playpaq_asoc_init);
+module_exit(playpaq_asoc_exit);
+
+MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
+MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/at91/Kconfig b/sound/soc/at91/Kconfig
new file mode 100644
index 0000000..85a8832
--- /dev/null
+++ b/sound/soc/at91/Kconfig
@@ -0,0 +1,10 @@
+config SND_AT91_SOC
+ tristate "SoC Audio for the Atmel AT91 System-on-Chip"
+ depends on ARCH_AT91
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the AT91 SSC interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_AT91_SOC_SSC
+ tristate
diff --git a/sound/soc/at91/Makefile b/sound/soc/at91/Makefile
new file mode 100644
index 0000000..b817f11
--- /dev/null
+++ b/sound/soc/at91/Makefile
@@ -0,0 +1,6 @@
+# AT91 Platform Support
+snd-soc-at91-objs := at91-pcm.o
+snd-soc-at91-ssc-objs := at91-ssc.o
+
+obj-$(CONFIG_SND_AT91_SOC) += snd-soc-at91.o
+obj-$(CONFIG_SND_AT91_SOC_SSC) += snd-soc-at91-ssc.o
diff --git a/sound/soc/at91/at91-pcm.c b/sound/soc/at91/at91-pcm.c
new file mode 100644
index 0000000..7ab48bd
--- /dev/null
+++ b/sound/soc/at91/at91-pcm.c
@@ -0,0 +1,434 @@
+/*
+ * at91-pcm.c -- ALSA PCM interface for the Atmel AT91 SoC
+ *
+ * Author: Frank Mandarino <fmandarino@endrelia.com>
+ * Endrelia Technologies Inc.
+ * Created: Mar 3, 2006
+ *
+ * Based on pxa2xx-pcm.c by:
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/atmel_pdc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/at91_ssc.h>
+
+#include "at91-pcm.h"
+
+#if 0
+#define DBG(x...) printk(KERN_INFO "at91-pcm: " x)
+#else
+#define DBG(x...)
+#endif
+
+static const struct snd_pcm_hardware at91_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 2,
+ .periods_max = 1024,
+ .buffer_bytes_max = 32 * 1024,
+};
+
+struct at91_runtime_data {
+ struct at91_pcm_dma_params *params;
+ dma_addr_t dma_buffer; /* physical address of dma buffer */
+ dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
+ size_t period_size;
+ dma_addr_t period_ptr; /* physical address of next period */
+ u32 pdc_xpr_save; /* PDC register save */
+ u32 pdc_xcr_save;
+ u32 pdc_xnpr_save;
+ u32 pdc_xncr_save;
+};
+
+static void at91_pcm_dma_irq(u32 ssc_sr,
+ struct snd_pcm_substream *substream)
+{
+ struct at91_runtime_data *prtd = substream->runtime->private_data;
+ struct at91_pcm_dma_params *params = prtd->params;
+ static int count = 0;
+
+ count++;
+
+ if (ssc_sr & params->mask->ssc_endbuf) {
+
+ printk(KERN_WARNING
+ "at91-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ ? "underrun" : "overrun",
+ params->name, ssc_sr, count);
+
+ /* re-start the PDC */
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_disable);
+
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end) {
+ prtd->period_ptr = prtd->dma_buffer;
+ }
+
+ at91_ssc_write(params->ssc_base + params->pdc->xpr, prtd->period_ptr);
+ at91_ssc_write(params->ssc_base + params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_enable);
+ }
+
+ if (ssc_sr & params->mask->ssc_endx) {
+
+ /* Load the PDC next pointer and counter registers */
+ prtd->period_ptr += prtd->period_size;
+ if (prtd->period_ptr >= prtd->dma_buffer_end) {
+ prtd->period_ptr = prtd->dma_buffer;
+ }
+ at91_ssc_write(params->ssc_base + params->pdc->xnpr,
+ prtd->period_ptr);
+ at91_ssc_write(params->ssc_base + params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+ }
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int at91_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at91_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ /* this may get called several times by oss emulation
+ * with different params */
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->params = rtd->dai->cpu_dai->dma_data;
+ prtd->params->dma_intr_handler = at91_pcm_dma_irq;
+
+ prtd->dma_buffer = runtime->dma_addr;
+ prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
+ prtd->period_size = params_period_bytes(params);
+
+ DBG("hw_params: DMA for %s initialized (dma_bytes=%d, period_size=%d)\n",
+ prtd->params->name, runtime->dma_bytes, prtd->period_size);
+ return 0;
+}
+
+static int at91_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct at91_runtime_data *prtd = substream->runtime->private_data;
+ struct at91_pcm_dma_params *params = prtd->params;
+
+ if (params != NULL) {
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_disable);
+ prtd->params->dma_intr_handler = NULL;
+ }
+
+ return 0;
+}
+
+static int at91_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct at91_runtime_data *prtd = substream->runtime->private_data;
+ struct at91_pcm_dma_params *params = prtd->params;
+
+ at91_ssc_write(params->ssc_base + AT91_SSC_IDR,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_disable);
+ return 0;
+}
+
+static int at91_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct at91_runtime_data *prtd = substream->runtime->private_data;
+ struct at91_pcm_dma_params *params = prtd->params;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->period_ptr = prtd->dma_buffer;
+
+ at91_ssc_write(params->ssc_base + params->pdc->xpr, prtd->period_ptr);
+ at91_ssc_write(params->ssc_base + params->pdc->xcr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ prtd->period_ptr += prtd->period_size;
+ at91_ssc_write(params->ssc_base + params->pdc->xnpr, prtd->period_ptr);
+ at91_ssc_write(params->ssc_base + params->pdc->xncr,
+ prtd->period_size / params->pdc_xfer_size);
+
+ DBG("trigger: period_ptr=%lx, xpr=%lx, xcr=%ld, xnpr=%lx, xncr=%ld\n",
+ (unsigned long) prtd->period_ptr,
+ at91_ssc_read(params->ssc_base + params->pdc->xpr),
+ at91_ssc_read(params->ssc_base + params->pdc->xcr),
+ at91_ssc_read(params->ssc_base + params->pdc->xnpr),
+ at91_ssc_read(params->ssc_base + params->pdc->xncr));
+
+ at91_ssc_write(params->ssc_base + AT91_SSC_IER,
+ params->mask->ssc_endx | params->mask->ssc_endbuf);
+
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR,
+ params->mask->pdc_enable);
+
+ DBG("sr=%lx imr=%lx\n",
+ at91_ssc_read(params->ssc_base + AT91_SSC_SR),
+ at91_ssc_read(params->ssc_base + AT91_SSC_IMR));
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_disable);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_enable);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t at91_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at91_runtime_data *prtd = runtime->private_data;
+ struct at91_pcm_dma_params *params = prtd->params;
+ dma_addr_t ptr;
+ snd_pcm_uframes_t x;
+
+ ptr = (dma_addr_t) at91_ssc_read(params->ssc_base + params->pdc->xpr);
+ x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
+
+ if (x == runtime->buffer_size)
+ x = 0;
+ return x;
+}
+
+static int at91_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct at91_runtime_data *prtd;
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &at91_pcm_hardware);
+
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = kzalloc(sizeof(struct at91_runtime_data), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ runtime->private_data = prtd;
+
+ out:
+ return ret;
+}
+
+static int at91_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct at91_runtime_data *prtd = substream->runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+static int at91_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+struct snd_pcm_ops at91_pcm_ops = {
+ .open = at91_pcm_open,
+ .close = at91_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = at91_pcm_hw_params,
+ .hw_free = at91_pcm_hw_free,
+ .prepare = at91_pcm_prepare,
+ .trigger = at91_pcm_trigger,
+ .pointer = at91_pcm_pointer,
+ .mmap = at91_pcm_mmap,
+};
+
+static int at91_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 = at91_pcm_hardware.buffer_bytes_max;
+
+ 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);
+
+ DBG("preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
+ (void *) buf->area,
+ (void *) buf->addr,
+ size);
+
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static u64 at91_pcm_dmamask = 0xffffffff;
+
+static int at91_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &at91_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = at91_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = at91_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+static void at91_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ 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;
+ }
+}
+
+#ifdef CONFIG_PM
+static int at91_pcm_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct at91_runtime_data *prtd;
+ struct at91_pcm_dma_params *params;
+
+ if (!runtime)
+ return 0;
+
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* disable the PDC and save the PDC registers */
+
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_disable);
+
+ prtd->pdc_xpr_save = at91_ssc_read(params->ssc_base + params->pdc->xpr);
+ prtd->pdc_xcr_save = at91_ssc_read(params->ssc_base + params->pdc->xcr);
+ prtd->pdc_xnpr_save = at91_ssc_read(params->ssc_base + params->pdc->xnpr);
+ prtd->pdc_xncr_save = at91_ssc_read(params->ssc_base + params->pdc->xncr);
+
+ return 0;
+}
+
+static int at91_pcm_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = dai->runtime;
+ struct at91_runtime_data *prtd;
+ struct at91_pcm_dma_params *params;
+
+ if (!runtime)
+ return 0;
+
+ prtd = runtime->private_data;
+ params = prtd->params;
+
+ /* restore the PDC registers and enable the PDC */
+ at91_ssc_write(params->ssc_base + params->pdc->xpr, prtd->pdc_xpr_save);
+ at91_ssc_write(params->ssc_base + params->pdc->xcr, prtd->pdc_xcr_save);
+ at91_ssc_write(params->ssc_base + params->pdc->xnpr, prtd->pdc_xnpr_save);
+ at91_ssc_write(params->ssc_base + params->pdc->xncr, prtd->pdc_xncr_save);
+
+ at91_ssc_write(params->ssc_base + ATMEL_PDC_PTCR, params->mask->pdc_enable);
+ return 0;
+}
+#else
+#define at91_pcm_suspend NULL
+#define at91_pcm_resume NULL
+#endif
+
+struct snd_soc_platform at91_soc_platform = {
+ .name = "at91-audio",
+ .pcm_ops = &at91_pcm_ops,
+ .pcm_new = at91_pcm_new,
+ .pcm_free = at91_pcm_free_dma_buffers,
+ .suspend = at91_pcm_suspend,
+ .resume = at91_pcm_resume,
+};
+
+EXPORT_SYMBOL_GPL(at91_soc_platform);
+
+MODULE_AUTHOR("Frank Mandarino <fmandarino@endrelia.com>");
+MODULE_DESCRIPTION("Atmel AT91 PCM module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/at91/at91-pcm.h b/sound/soc/at91/at91-pcm.h
new file mode 100644
index 0000000..e5aada2
--- /dev/null
+++ b/sound/soc/at91/at91-pcm.h
@@ -0,0 +1,72 @@
+/*
+ * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC
+ *
+ * Author: Frank Mandarino <fmandarino@endrelia.com>
+ * Endrelia Technologies Inc.
+ * Created: Mar 3, 2006
+ *
+ * Based on pxa2xx-pcm.h by:
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AT91_PCM_H
+#define _AT91_PCM_H
+
+#include <mach/hardware.h>
+
+struct at91_ssc_periph {
+ void __iomem *base;
+ u32 pid;
+};
+
+/*
+ * Registers and status bits that are required by the PCM driver.
+ */
+struct at91_pdc_regs {
+ unsigned int xpr; /* PDC recv/trans pointer */
+ unsigned int xcr; /* PDC recv/trans counter */
+ unsigned int xnpr; /* PDC next recv/trans pointer */
+ unsigned int xncr; /* PDC next recv/trans counter */
+ unsigned int ptcr; /* PDC transfer control */
+};
+
+struct at91_ssc_mask {
+ u32 ssc_enable; /* SSC recv/trans enable */
+ u32 ssc_disable; /* SSC recv/trans disable */
+ u32 ssc_endx; /* SSC ENDTX or ENDRX */
+ u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
+ u32 pdc_enable; /* PDC recv/trans enable */
+ u32 pdc_disable; /* PDC recv/trans disable */
+};
+
+/*
+ * This structure, shared between the PCM driver and the interface,
+ * contains all information required by the PCM driver to perform the
+ * PDC DMA operation. All fields except dma_intr_handler() are initialized
+ * by the interface. The dms_intr_handler() pointer is set by the PCM
+ * driver and called by the interface SSC interrupt handler if it is
+ * non-NULL.
+ */
+struct at91_pcm_dma_params {
+ char *name; /* stream identifier */
+ int pdc_xfer_size; /* PDC counter increment in bytes */
+ void __iomem *ssc_base; /* SSC base address */
+ struct at91_pdc_regs *pdc; /* PDC receive or transmit registers */
+ struct at91_ssc_mask *mask;/* SSC & PDC status bits */
+ struct snd_pcm_substream *substream;
+ void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
+};
+
+extern struct snd_soc_platform at91_soc_platform;
+
+#define at91_ssc_read(a) ((unsigned long) __raw_readl(a))
+#define at91_ssc_write(a,v) __raw_writel((v),(a))
+
+#endif /* _AT91_PCM_H */
diff --git a/sound/soc/at91/at91-ssc.c b/sound/soc/at91/at91-ssc.c
new file mode 100644
index 0000000..1b61cc4
--- /dev/null
+++ b/sound/soc/at91/at91-ssc.c
@@ -0,0 +1,791 @@
+/*
+ * at91-ssc.c -- ALSA SoC AT91 SSC Audio Layer Platform driver
+ *
+ * Author: Frank Mandarino <fmandarino@endrelia.com>
+ * Endrelia Technologies Inc.
+ *
+ * Based on pxa2xx Platform drivers by
+ * Liam Girdwood <lrg@slimlogic.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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/atmel_pdc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/at91_pmc.h>
+#include <mach/at91_ssc.h>
+
+#include "at91-pcm.h"
+#include "at91-ssc.h"
+
+#if 0
+#define DBG(x...) printk(KERN_DEBUG "at91-ssc:" x)
+#else
+#define DBG(x...)
+#endif
+
+#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
+#define NUM_SSC_DEVICES 1
+#else
+#define NUM_SSC_DEVICES 3
+#endif
+
+
+/*
+ * SSC PDC registers required by the PCM DMA engine.
+ */
+static struct at91_pdc_regs pdc_tx_reg = {
+ .xpr = ATMEL_PDC_TPR,
+ .xcr = ATMEL_PDC_TCR,
+ .xnpr = ATMEL_PDC_TNPR,
+ .xncr = ATMEL_PDC_TNCR,
+};
+
+static struct at91_pdc_regs pdc_rx_reg = {
+ .xpr = ATMEL_PDC_RPR,
+ .xcr = ATMEL_PDC_RCR,
+ .xnpr = ATMEL_PDC_RNPR,
+ .xncr = ATMEL_PDC_RNCR,
+};
+
+/*
+ * SSC & PDC status bits for transmit and receive.
+ */
+static struct at91_ssc_mask ssc_tx_mask = {
+ .ssc_enable = AT91_SSC_TXEN,
+ .ssc_disable = AT91_SSC_TXDIS,
+ .ssc_endx = AT91_SSC_ENDTX,
+ .ssc_endbuf = AT91_SSC_TXBUFE,
+ .pdc_enable = ATMEL_PDC_TXTEN,
+ .pdc_disable = ATMEL_PDC_TXTDIS,
+};
+
+static struct at91_ssc_mask ssc_rx_mask = {
+ .ssc_enable = AT91_SSC_RXEN,
+ .ssc_disable = AT91_SSC_RXDIS,
+ .ssc_endx = AT91_SSC_ENDRX,
+ .ssc_endbuf = AT91_SSC_RXBUFF,
+ .pdc_enable = ATMEL_PDC_RXTEN,
+ .pdc_disable = ATMEL_PDC_RXTDIS,
+};
+
+
+/*
+ * DMA parameters.
+ */
+static struct at91_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
+ {{
+ .name = "SSC0 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC0 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ }},
+#if NUM_SSC_DEVICES == 3
+ {{
+ .name = "SSC1 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC1 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ }},
+ {{
+ .name = "SSC2 PCM out",
+ .pdc = &pdc_tx_reg,
+ .mask = &ssc_tx_mask,
+ },
+ {
+ .name = "SSC2 PCM in",
+ .pdc = &pdc_rx_reg,
+ .mask = &ssc_rx_mask,
+ }},
+#endif
+};
+
+struct at91_ssc_state {
+ u32 ssc_cmr;
+ u32 ssc_rcmr;
+ u32 ssc_rfmr;
+ u32 ssc_tcmr;
+ u32 ssc_tfmr;
+ u32 ssc_sr;
+ u32 ssc_imr;
+};
+
+static struct at91_ssc_info {
+ char *name;
+ struct at91_ssc_periph ssc;
+ spinlock_t lock; /* lock for dir_mask */
+ unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
+ unsigned short initialized; /* 1=SSC has been initialized */
+ unsigned short daifmt;
+ unsigned short cmr_div;
+ unsigned short tcmr_period;
+ unsigned short rcmr_period;
+ struct at91_pcm_dma_params *dma_params[2];
+ struct at91_ssc_state ssc_state;
+
+} ssc_info[NUM_SSC_DEVICES] = {
+ {
+ .name = "ssc0",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
+ .dir_mask = 0,
+ .initialized = 0,
+ },
+#if NUM_SSC_DEVICES == 3
+ {
+ .name = "ssc1",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
+ .dir_mask = 0,
+ .initialized = 0,
+ },
+ {
+ .name = "ssc2",
+ .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
+ .dir_mask = 0,
+ .initialized = 0,
+ },
+#endif
+};
+
+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_ssc_interrupt(int irq, void *dev_id)
+{
+ struct at91_ssc_info *ssc_p = dev_id;
+ struct at91_pcm_dma_params *dma_params;
+ u32 ssc_sr;
+ int i;
+
+ ssc_sr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR)
+ & at91_ssc_read(ssc_p->ssc.base + AT91_SSC_IMR);
+
+ /*
+ * Loop through the substreams attached to this SSC. If
+ * a DMA-related interrupt occurred on that substream, call
+ * the DMA interrupt handler function, if one has been
+ * registered in the dma_params structure by the PCM driver.
+ */
+ for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
+ dma_params = ssc_p->dma_params[i];
+
+ if (dma_params != NULL && dma_params->dma_intr_handler != NULL &&
+ (ssc_sr &
+ (dma_params->mask->ssc_endx | dma_params->mask->ssc_endbuf)))
+
+ dma_params->dma_intr_handler(ssc_sr, dma_params->substream);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Startup. Only that one substream allowed in each direction.
+ */
+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("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;
+
+ spin_lock_irq(&ssc_p->lock);
+ if (ssc_p->dir_mask & dir_mask) {
+ spin_unlock_irq(&ssc_p->lock);
+ return -EBUSY;
+ }
+ ssc_p->dir_mask |= dir_mask;
+ spin_unlock_irq(&ssc_p->lock);
+
+ return 0;
+}
+
+/*
+ * Shutdown. Clear DMA parameters and shutdown the SSC if there
+ * are no other substreams open.
+ */
+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];
+ struct at91_pcm_dma_params *dma_params;
+ int dir, dir_mask;
+
+ dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+ dma_params = ssc_p->dma_params[dir];
+
+ if (dma_params != NULL) {
+ at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR,
+ dma_params->mask->ssc_disable);
+ DBG("%s disabled SSC_SR=0x%08lx\n", (dir ? "receive" : "transmit"),
+ at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR));
+
+ dma_params->ssc_base = NULL;
+ dma_params->substream = NULL;
+ ssc_p->dma_params[dir] = NULL;
+ }
+
+ dir_mask = 1 << dir;
+
+ spin_lock_irq(&ssc_p->lock);
+ ssc_p->dir_mask &= ~dir_mask;
+ if (!ssc_p->dir_mask) {
+ /* Shutdown the SSC clock. */
+ DBG("Stopping pid %d clock\n", ssc_p->ssc.pid);
+ at91_sys_write(AT91_PMC_PCDR, 1<<ssc_p->ssc.pid);
+
+ if (ssc_p->initialized) {
+ free_irq(ssc_p->ssc.pid, ssc_p);
+ ssc_p->initialized = 0;
+ }
+
+ /* Reset the SSC */
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST);
+
+ /* Clear the SSC dividers */
+ ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
+ }
+ spin_unlock_irq(&ssc_p->lock);
+}
+
+/*
+ * Record the SSC system clock rate.
+ */
+static int at91_ssc_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ /*
+ * The only clock supplied to the SSC is the AT91 master clock,
+ * which is only used if the SSC is generating BCLK and/or
+ * LRC clocks.
+ */
+ switch (clk_id) {
+ case AT91_SYSCLK_MCK:
+ at91_ssc_sysclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Record the DAI format for use in hw_params().
+ */
+static int at91_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ ssc_p->daifmt = fmt;
+ return 0;
+}
+
+/*
+ * Record SSC clock dividers for use in hw_params().
+ */
+static int at91_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+ switch (div_id) {
+ case AT91SSC_CMR_DIV:
+ /*
+ * The same master clock divider is used for both
+ * transmit and receive, so if a value has already
+ * been set, it must match this value.
+ */
+ if (ssc_p->cmr_div == 0)
+ ssc_p->cmr_div = div;
+ else
+ if (div != ssc_p->cmr_div)
+ return -EBUSY;
+ break;
+
+ case AT91SSC_TCMR_PERIOD:
+ ssc_p->tcmr_period = div;
+ break;
+
+ case AT91SSC_RCMR_PERIOD:
+ ssc_p->rcmr_period = div;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Configure the SSC.
+ */
+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;
+ int id = rtd->dai->cpu_dai->id;
+ struct at91_ssc_info *ssc_p = &ssc_info[id];
+ struct at91_pcm_dma_params *dma_params;
+ int dir, channels, bits;
+ u32 tfmr, rfmr, tcmr, rcmr;
+ int start_event;
+ int ret;
+
+ /*
+ * Currently, there is only one set of dma params for
+ * each direction. If more are added, this code will
+ * have to be changed to select the proper set.
+ */
+ dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ dma_params = &ssc_dma_params[id][dir];
+ dma_params->ssc_base = ssc_p->ssc.base;
+ dma_params->substream = substream;
+
+ ssc_p->dma_params[dir] = dma_params;
+
+ /*
+ * The cpu_dai->dma_data field is only used to communicate the
+ * appropriate DMA parameters to the pcm driver hw_params()
+ * function. It should not be used for other purposes
+ * as it is common to all substreams.
+ */
+ rtd->dai->cpu_dai->dma_data = dma_params;
+
+ 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\n");
+ 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.
+ */
+ 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
+ & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * 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.
+ */
+ rcmr = (( ssc_p->rcmr_period << 24) & AT91_SSC_PERIOD)
+ | (( 1 << 16) & AT91_SSC_STTDLY)
+ | (( AT91_SSC_START_FALLING_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_NEGATIVE ) & AT91_SSC_FSOS)
+ | (((bits - 1) << 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_FALLING_RF ) & AT91_SSC_START)
+ | (( AT91_SSC_CKI_FALLING ) & 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_NEGATIVE ) & AT91_SSC_FSOS)
+ | (((bits - 1) << 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_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ /*
+ * 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
+ * transmit clock.
+ *
+ * For single channel data, one sample is transferred on the falling
+ * edge of the LRC clock. For two channel data, one sample is
+ * transferred on both edges of the LRC clock.
+ */
+ start_event = channels == 1
+ ? AT91_SSC_START_FALLING_RF
+ : AT91_SSC_START_EDGE_RF;
+
+ rcmr = (( 0 << 24) & AT91_SSC_PERIOD)
+ | (( 1 << 16) & AT91_SSC_STTDLY)
+ | (( start_event ) & AT91_SSC_START)
+ | (( AT91_SSC_CK_RISING ) & AT91_SSC_CKI)
+ | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO)
+ | (( AT91_SSC_CKS_CLOCK ) & AT91_SSC_CKS);
+
+ rfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE)
+ | (( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS)
+ | (( 0 << 16) & AT91_SSC_FSLEN)
+ | (( 0 << 8) & AT91_SSC_DATNB)
+ | (( 1 << 7) & AT91_SSC_MSBF)
+ | (( 0 << 5) & AT91_SSC_LOOP)
+ | (((bits - 1) << 0) & AT91_SSC_DATALEN);
+
+ tcmr = (( 0 << 24) & AT91_SSC_PERIOD)
+ | (( 1 << 16) & AT91_SSC_STTDLY)
+ | (( start_event ) & AT91_SSC_START)
+ | (( AT91_SSC_CKI_FALLING ) & AT91_SSC_CKI)
+ | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO)
+ | (( AT91_SSC_CKS_PIN ) & AT91_SSC_CKS);
+
+ tfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE)
+ | (( 0 << 23) & AT91_SSC_FSDEN)
+ | (( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS)
+ | (( 0 << 16) & AT91_SSC_FSLEN)
+ | (( 0 << 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_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-ssc: unsupported DAI format 0x%x.\n",
+ ssc_p->daifmt);
+ return -EINVAL;
+ break;
+ }
+ DBG("RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", rcmr, rfmr, tcmr, tfmr);
+
+ if (!ssc_p->initialized) {
+
+ /* Enable PMC peripheral clock for this SSC */
+ DBG("Starting pid %d clock\n", ssc_p->ssc.pid);
+ at91_sys_write(AT91_PMC_PCER, 1<<ssc_p->ssc.pid);
+
+ /* Reset the SSC and its PDC registers */
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST);
+
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_RPR, 0);
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_RCR, 0);
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_RNPR, 0);
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_RNCR, 0);
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_TPR, 0);
+ at91_ssc_write(ssc_p->ssc.base + ATMEL_PDC_TCR, 0);
+ 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_ssc_interrupt,
+ 0, ssc_p->name, ssc_p)) < 0) {
+ printk(KERN_WARNING "at91-ssc: request_irq failure\n");
+
+ DBG("Stopping pid %d clock\n", ssc_p->ssc.pid);
+ at91_sys_write(AT91_PMC_PCDR, 1<<ssc_p->ssc.pid);
+ return ret;
+ }
+
+ ssc_p->initialized = 1;
+ }
+
+ /* set SSC clock mode register */
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CMR, ssc_p->cmr_div);
+
+ /* set receive clock mode and format */
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RCMR, rcmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RFMR, rfmr);
+
+ /* set transmit clock mode and format */
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TCMR, tcmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TFMR, tfmr);
+
+ DBG("hw_params: SSC initialized\n");
+ return 0;
+}
+
+
+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];
+ struct at91_pcm_dma_params *dma_params;
+ int dir;
+
+ dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+ dma_params = ssc_p->dma_params[dir];
+
+ at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR,
+ dma_params->mask->ssc_enable);
+
+ DBG("%s enabled SSC_SR=0x%08lx\n", dir ? "receive" : "transmit",
+ at91_ssc_read(dma_params->ssc_base + AT91_SSC_SR));
+ return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int at91_ssc_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at91_ssc_info *ssc_p;
+
+ if(!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ /* Save the status register before disabling transmit and receive. */
+ ssc_p->ssc_state.ssc_sr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR,
+ AT91_SSC_TXDIS | AT91_SSC_RXDIS);
+
+ /* Save the current interrupt mask, then disable unmasked interrupts. */
+ ssc_p->ssc_state.ssc_imr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_IMR);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_IDR, ssc_p->ssc_state.ssc_imr);
+
+ ssc_p->ssc_state.ssc_cmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_CMR);
+ ssc_p->ssc_state.ssc_rcmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_RCMR);
+ ssc_p->ssc_state.ssc_rfmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_RFMR);
+ ssc_p->ssc_state.ssc_tcmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_TCMR);
+ ssc_p->ssc_state.ssc_tfmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_TFMR);
+
+ return 0;
+}
+
+static int at91_ssc_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct at91_ssc_info *ssc_p;
+
+ if(!cpu_dai->active)
+ return 0;
+
+ ssc_p = &ssc_info[cpu_dai->id];
+
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TFMR, ssc_p->ssc_state.ssc_tfmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TCMR, ssc_p->ssc_state.ssc_tcmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RFMR, ssc_p->ssc_state.ssc_rfmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RCMR, ssc_p->ssc_state.ssc_rcmr);
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CMR, ssc_p->ssc_state.ssc_cmr);
+
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_IER, ssc_p->ssc_state.ssc_imr);
+
+ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR,
+ ((ssc_p->ssc_state.ssc_sr & AT91_SSC_RXENA) ? AT91_SSC_RXEN : 0) |
+ ((ssc_p->ssc_state.ssc_sr & AT91_SSC_TXENA) ? AT91_SSC_TXEN : 0));
+
+ return 0;
+}
+
+#else
+#define at91_ssc_suspend NULL
+#define at91_ssc_resume NULL
+#endif
+
+#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)
+
+#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_dai at91_ssc_dai[NUM_SSC_DEVICES] = {
+ { .name = "at91-ssc0",
+ .id = 0,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .ops = {
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
+ .dai_ops = {
+ .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",
+ .id = 1,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .ops = {
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
+ .dai_ops = {
+ .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",
+ .id = 2,
+ .type = SND_SOC_DAI_PCM,
+ .suspend = at91_ssc_suspend,
+ .resume = at91_ssc_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AT91_SSC_RATES,
+ .formats = AT91_SSC_FORMATS,},
+ .ops = {
+ .startup = at91_ssc_startup,
+ .shutdown = at91_ssc_shutdown,
+ .prepare = at91_ssc_prepare,
+ .hw_params = at91_ssc_hw_params,},
+ .dai_ops = {
+ .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_ssc_dai);
+
+/* Module information */
+MODULE_AUTHOR("Frank Mandarino, fmandarino@endrelia.com, www.endrelia.com");
+MODULE_DESCRIPTION("AT91 SSC ASoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/at91/at91-ssc.h b/sound/soc/at91/at91-ssc.h
new file mode 100644
index 0000000..6b7bf38
--- /dev/null
+++ b/sound/soc/at91/at91-ssc.h
@@ -0,0 +1,27 @@
+/*
+ * at91-ssc.h - ALSA SSC interface for the Atmel AT91 SoC
+ *
+ * Author: Frank Mandarino <fmandarino@endrelia.com>
+ * Endrelia Technologies Inc.
+ * Created: Jan 9, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AT91_SSC_H
+#define _AT91_SSC_H
+
+/* SSC system clock ids */
+#define AT91_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
+
+/* 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_dai at91_ssc_dai[];
+
+#endif /* _AT91_SSC_H */
+
diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
new file mode 100644
index 0000000..410a893
--- /dev/null
+++ b/sound/soc/au1x/Kconfig
@@ -0,0 +1,32 @@
+##
+## Au1200/Au1550 PSC + DBDMA
+##
+config SND_SOC_AU1XPSC
+ tristate "SoC Audio for Au1200/Au1250/Au1550"
+ depends on SOC_AU1200 || SOC_AU1550
+ help
+ This option enables support for the Programmable Serial
+ Controllers in AC97 and I2S mode, and the Descriptor-Based DMA
+ Controller (DBDMA) as found on the Au1200/Au1250/Au1550 SoC.
+
+config SND_SOC_AU1XPSC_I2S
+ tristate
+
+config SND_SOC_AU1XPSC_AC97
+ tristate
+ select AC97_BUS
+ select SND_AC97_CODEC
+ select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_SAMPLE_PSC_AC97
+ tristate "Sample Au12x0/Au1550 PSC AC97 sound machine"
+ depends on SND_SOC_AU1XPSC
+ select SND_SOC_AU1XPSC_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ This is a sample AC97 sound machine for use in Au12x0/Au1550
+ based systems which have audio on PSC1 (e.g. Db1200 demoboard).
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
new file mode 100644
index 0000000..6c6950b
--- /dev/null
+++ b/sound/soc/au1x/Makefile
@@ -0,0 +1,13 @@
+# Au1200/Au1550 PSC audio
+snd-soc-au1xpsc-dbdma-objs := dbdma2.o
+snd-soc-au1xpsc-i2s-objs := psc-i2s.o
+snd-soc-au1xpsc-ac97-objs := psc-ac97.o
+
+obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o
+
+# Boards
+snd-soc-sample-ac97-objs := sample-ac97.o
+
+obj-$(CONFIG_SND_SOC_SAMPLE_PSC_AC97) += snd-soc-sample-ac97.o
diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c
new file mode 100644
index 0000000..1466d93
--- /dev/null
+++ b/sound/soc/au1x/dbdma2.c
@@ -0,0 +1,421 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * DMA glue for Au1x-PSC audio.
+ *
+ * NOTE: all of these drivers can only work with a SINGLE instance
+ * of a PSC. Multiple independent audio devices are impossible
+ * with ASoC v1.
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/*#define PCM_DEBUG*/
+
+#define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x)
+#ifdef PCM_DEBUG
+#define DBG MSG
+#else
+#define DBG(x...) do {} while (0)
+#endif
+
+struct au1xpsc_audio_dmadata {
+ /* DDMA control data */
+ unsigned int ddma_id; /* DDMA direction ID for this PSC */
+ u32 ddma_chan; /* DDMA context */
+
+ /* PCM context (for irq handlers) */
+ struct snd_pcm_substream *substream;
+ unsigned long curr_period; /* current segment DDMA is working on */
+ unsigned long q_period; /* queue period(s) */
+ unsigned long dma_area; /* address of queued DMA area */
+ unsigned long dma_area_s; /* start address of DMA area */
+ unsigned long pos; /* current byte position being played */
+ unsigned long periods; /* number of SG segments in total */
+ unsigned long period_bytes; /* size in bytes of one SG segment */
+
+ /* runtime data */
+ int msbits;
+};
+
+/* instance data. There can be only one, MacLeod!!!! */
+static struct au1xpsc_audio_dmadata *au1xpsc_audio_pcmdma[2];
+
+/*
+ * These settings are somewhat okay, at least on my machine audio plays
+ * almost skip-free. Especially the 64kB buffer seems to help a LOT.
+ */
+#define AU1XPSC_PERIOD_MIN_BYTES 1024
+#define AU1XPSC_BUFFER_MIN_BYTES 65536
+
+#define AU1XPSC_PCM_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \
+ SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \
+ 0)
+
+/* PCM hardware DMA capabilities - platform specific */
+static const struct snd_pcm_hardware au1xpsc_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = AU1XPSC_PCM_FMTS,
+ .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES,
+ .period_bytes_max = 4096 * 1024 - 1,
+ .periods_min = 2,
+ .periods_max = 4096, /* 2 to as-much-as-you-like */
+ .buffer_bytes_max = 4096 * 1024 - 1,
+ .fifo_size = 16, /* fifo entries of AC97/I2S PSC */
+};
+
+static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd)
+{
+ au1xxx_dbdma_put_source_flags(cd->ddma_chan,
+ (void *)phys_to_virt(cd->dma_area),
+ cd->period_bytes, DDMA_FLAGS_IE);
+
+ /* update next-to-queue period */
+ ++cd->q_period;
+ cd->dma_area += cd->period_bytes;
+ if (cd->q_period >= cd->periods) {
+ cd->q_period = 0;
+ cd->dma_area = cd->dma_area_s;
+ }
+}
+
+static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd)
+{
+ au1xxx_dbdma_put_dest_flags(cd->ddma_chan,
+ (void *)phys_to_virt(cd->dma_area),
+ cd->period_bytes, DDMA_FLAGS_IE);
+
+ /* update next-to-queue period */
+ ++cd->q_period;
+ cd->dma_area += cd->period_bytes;
+ if (cd->q_period >= cd->periods) {
+ cd->q_period = 0;
+ cd->dma_area = cd->dma_area_s;
+ }
+}
+
+static void au1x_pcm_dmatx_cb(int irq, void *dev_id)
+{
+ struct au1xpsc_audio_dmadata *cd = dev_id;
+
+ cd->pos += cd->period_bytes;
+ if (++cd->curr_period >= cd->periods) {
+ cd->pos = 0;
+ cd->curr_period = 0;
+ }
+ snd_pcm_period_elapsed(cd->substream);
+ au1x_pcm_queue_tx(cd);
+}
+
+static void au1x_pcm_dmarx_cb(int irq, void *dev_id)
+{
+ struct au1xpsc_audio_dmadata *cd = dev_id;
+
+ cd->pos += cd->period_bytes;
+ if (++cd->curr_period >= cd->periods) {
+ cd->pos = 0;
+ cd->curr_period = 0;
+ }
+ snd_pcm_period_elapsed(cd->substream);
+ au1x_pcm_queue_rx(cd);
+}
+
+static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd)
+{
+ if (pcd->ddma_chan) {
+ au1xxx_dbdma_stop(pcd->ddma_chan);
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+ au1xxx_dbdma_chan_free(pcd->ddma_chan);
+ pcd->ddma_chan = 0;
+ pcd->msbits = 0;
+ }
+}
+
+/* in case of missing DMA ring or changed TX-source / RX-dest bit widths,
+ * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according
+ * to ALSA-supplied sample depth. This is due to limitations in the dbdma api
+ * (cannot adjust source/dest widths of already allocated descriptor ring).
+ */
+static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd,
+ int stype, int msbits)
+{
+ /* DMA only in 8/16/32 bit widths */
+ if (msbits == 24)
+ msbits = 32;
+
+ /* check current config: correct bits and descriptors allocated? */
+ if ((pcd->ddma_chan) && (msbits == pcd->msbits))
+ goto out; /* all ok! */
+
+ au1x_pcm_dbdma_free(pcd);
+
+ if (stype == PCM_RX)
+ pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,
+ DSCR_CMD0_ALWAYS,
+ au1x_pcm_dmarx_cb, (void *)pcd);
+ else
+ pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS,
+ pcd->ddma_id,
+ au1x_pcm_dmatx_cb, (void *)pcd);
+
+ if (!pcd->ddma_chan)
+ return -ENOMEM;;
+
+ au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits);
+ au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2);
+
+ pcd->msbits = msbits;
+
+ au1xxx_dbdma_stop(pcd->ddma_chan);
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+
+out:
+ return 0;
+}
+
+static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct au1xpsc_audio_dmadata *pcd;
+ int stype, ret;
+
+ ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (ret < 0)
+ goto out;
+
+ stype = SUBSTREAM_TYPE(substream);
+ pcd = au1xpsc_audio_pcmdma[stype];
+
+ DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
+ "runtime->min_align %d\n",
+ (unsigned long)runtime->dma_area,
+ (unsigned long)runtime->dma_addr, runtime->dma_bytes,
+ runtime->min_align);
+
+ DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits,
+ params_periods(params), params_period_bytes(params), stype);
+
+ ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits);
+ if (ret) {
+ MSG("DDMA channel (re)alloc failed!\n");
+ goto out;
+ }
+
+ pcd->substream = substream;
+ pcd->period_bytes = params_period_bytes(params);
+ pcd->periods = params_periods(params);
+ pcd->dma_area_s = pcd->dma_area = (unsigned long)runtime->dma_addr;
+ pcd->q_period = 0;
+ pcd->curr_period = 0;
+ pcd->pos = 0;
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct au1xpsc_audio_dmadata *pcd =
+ au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)];
+
+ au1xxx_dbdma_reset(pcd->ddma_chan);
+
+ if (SUBSTREAM_TYPE(substream) == PCM_RX) {
+ au1x_pcm_queue_rx(pcd);
+ au1x_pcm_queue_rx(pcd);
+ } else {
+ au1x_pcm_queue_tx(pcd);
+ au1x_pcm_queue_tx(pcd);
+ }
+
+ return 0;
+}
+
+static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ u32 c = au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->ddma_chan;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ au1xxx_dbdma_start(c);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ au1xxx_dbdma_stop(c);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static snd_pcm_uframes_t
+au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ return bytes_to_frames(substream->runtime,
+ au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->pos);
+}
+
+static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
+{
+ snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);
+ return 0;
+}
+
+static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
+{
+ au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]);
+ return 0;
+}
+
+struct snd_pcm_ops au1xpsc_pcm_ops = {
+ .open = au1xpsc_pcm_open,
+ .close = au1xpsc_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = au1xpsc_pcm_hw_params,
+ .hw_free = au1xpsc_pcm_hw_free,
+ .prepare = au1xpsc_pcm_prepare,
+ .trigger = au1xpsc_pcm_trigger,
+ .pointer = au1xpsc_pcm_pointer,
+};
+
+static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int au1xpsc_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
+
+ return 0;
+}
+
+static int au1xpsc_pcm_probe(struct platform_device *pdev)
+{
+ struct resource *r;
+ int ret;
+
+ if (au1xpsc_audio_pcmdma[PCM_TX] || au1xpsc_audio_pcmdma[PCM_RX])
+ return -EBUSY;
+
+ /* TX DMA */
+ au1xpsc_audio_pcmdma[PCM_TX]
+ = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
+ if (!au1xpsc_audio_pcmdma[PCM_TX])
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out1;
+ }
+ (au1xpsc_audio_pcmdma[PCM_TX])->ddma_id = r->start;
+
+ /* RX DMA */
+ au1xpsc_audio_pcmdma[PCM_RX]
+ = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
+ if (!au1xpsc_audio_pcmdma[PCM_RX])
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!r) {
+ ret = -ENODEV;
+ goto out2;
+ }
+ (au1xpsc_audio_pcmdma[PCM_RX])->ddma_id = r->start;
+
+ return 0;
+
+out2:
+ kfree(au1xpsc_audio_pcmdma[PCM_RX]);
+ au1xpsc_audio_pcmdma[PCM_RX] = NULL;
+out1:
+ kfree(au1xpsc_audio_pcmdma[PCM_TX]);
+ au1xpsc_audio_pcmdma[PCM_TX] = NULL;
+ return ret;
+}
+
+static int au1xpsc_pcm_remove(struct platform_device *pdev)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (au1xpsc_audio_pcmdma[i]) {
+ au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[i]);
+ kfree(au1xpsc_audio_pcmdma[i]);
+ au1xpsc_audio_pcmdma[i] = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/* au1xpsc audio platform */
+struct snd_soc_platform au1xpsc_soc_platform = {
+ .name = "au1xpsc-pcm-dbdma",
+ .probe = au1xpsc_pcm_probe,
+ .remove = au1xpsc_pcm_remove,
+ .pcm_ops = &au1xpsc_pcm_ops,
+ .pcm_new = au1xpsc_pcm_new,
+ .pcm_free = au1xpsc_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(au1xpsc_soc_platform);
+
+static int __init au1xpsc_audio_dbdma_init(void)
+{
+ au1xpsc_audio_pcmdma[PCM_TX] = NULL;
+ au1xpsc_audio_pcmdma[PCM_RX] = NULL;
+ return 0;
+}
+
+static void __exit au1xpsc_audio_dbdma_exit(void)
+{
+}
+
+module_init(au1xpsc_audio_dbdma_init);
+module_exit(au1xpsc_audio_dbdma_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c
new file mode 100644
index 0000000..57facba
--- /dev/null
+++ b/sound/soc/au1x/psc-ac97.c
@@ -0,0 +1,387 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC AC97 glue.
+ *
+ * NOTE: all of these drivers can only work with a SINGLE instance
+ * of a PSC. Multiple independent audio devices are impossible
+ * with ASoC v1.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+#define AC97_DIR \
+ (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AC97_RATES \
+ SNDRV_PCM_RATE_8000_48000
+
+#define AC97_FMTS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE)
+
+#define AC97PCR_START(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS)
+#define AC97PCR_STOP(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP)
+#define AC97PCR_CLRFIFO(stype) \
+ ((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)
+
+/* instance data. There can be only one, MacLeod!!!! */
+static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
+
+/* AC97 controller reads codec register */
+static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ unsigned short data, tmo;
+
+ au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), AC97_CDC(pscdata));
+ au_sync();
+
+ tmo = 1000;
+ while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo)
+ udelay(2);
+
+ if (!tmo)
+ data = 0xffff;
+ else
+ data = au_readl(AC97_CDC(pscdata)) & 0xffff;
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+
+ return data;
+}
+
+/* AC97 controller writes to codec register */
+static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ unsigned int tmo;
+
+ au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), AC97_CDC(pscdata));
+ au_sync();
+ tmo = 1000;
+ while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo)
+ au_sync();
+
+ au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+ au_sync();
+}
+
+/* AC97 controller asserts a warm reset */
+static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+
+ au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata));
+ au_sync();
+ msleep(10);
+ au_writel(0, AC97_RST(pscdata));
+ au_sync();
+}
+
+static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ int i;
+
+ /* disable PSC during cold reset */
+ au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ /* issue cold reset */
+ au_writel(PSC_AC97RST_RST, AC97_RST(pscdata));
+ au_sync();
+ msleep(500);
+ au_writel(0, AC97_RST(pscdata));
+ au_sync();
+
+ /* enable PSC */
+ au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ /* wait for PSC to indicate it's ready */
+ i = 100000;
+ while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i))
+ au_sync();
+
+ if (i == 0) {
+ printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n");
+ return;
+ }
+
+ /* enable the ac97 function */
+ au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ /* wait for AC97 core to become ready */
+ i = 100000;
+ while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i))
+ au_sync();
+ if (i == 0)
+ printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n");
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = au1xpsc_ac97_read,
+ .write = au1xpsc_ac97_write,
+ .reset = au1xpsc_ac97_cold_reset,
+ .warm_reset = au1xpsc_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ unsigned long r, stat;
+ int chans, stype = SUBSTREAM_TYPE(substream);
+
+ chans = params_channels(params);
+
+ r = au_readl(AC97_CFG(pscdata));
+ stat = au_readl(AC97_STAT(pscdata));
+
+ /* already active? */
+ if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) {
+ /* reject parameters not currently set up */
+ if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) ||
+ (pscdata->rate != params_rate(params)))
+ return -EINVAL;
+ } else {
+ /* disable AC97 device controller first */
+ au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
+ r &= ~PSC_AC97CFG_LEN_MASK;
+ r |= PSC_AC97CFG_SET_LEN(params->msbits);
+
+ /* channels: enable slots for front L/R channel */
+ if (stype == PCM_TX) {
+ r &= ~PSC_AC97CFG_TXSLOT_MASK;
+ r |= PSC_AC97CFG_TXSLOT_ENA(3);
+ r |= PSC_AC97CFG_TXSLOT_ENA(4);
+ } else {
+ r &= ~PSC_AC97CFG_RXSLOT_MASK;
+ r |= PSC_AC97CFG_RXSLOT_ENA(3);
+ r |= PSC_AC97CFG_RXSLOT_ENA(4);
+ }
+
+ /* finally enable the AC97 controller again */
+ au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+ au_sync();
+
+ pscdata->cfg = r;
+ pscdata->rate = params_rate(params);
+ }
+
+ return 0;
+}
+
+static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ /* FIXME */
+ struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
+ int ret, stype = SUBSTREAM_TYPE(substream);
+
+ ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ au_writel(AC97PCR_START(stype), AC97_PCR(pscdata));
+ au_sync();
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata));
+ au_sync();
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int au1xpsc_ac97_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct resource *r;
+ unsigned long sel;
+
+ if (au1xpsc_ac97_workdata)
+ return -EBUSY;
+
+ au1xpsc_ac97_workdata =
+ kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+ if (!au1xpsc_ac97_workdata)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out0;
+ }
+
+ ret = -EBUSY;
+ au1xpsc_ac97_workdata->ioarea =
+ request_mem_region(r->start, r->end - r->start + 1,
+ "au1xpsc_ac97");
+ if (!au1xpsc_ac97_workdata->ioarea)
+ goto out0;
+
+ au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff);
+ if (!au1xpsc_ac97_workdata->mmio)
+ goto out1;
+
+ /* configuration: max dma trigger threshold, enable ac97 */
+ au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 |
+ PSC_AC97CFG_TT_FIFO8 |
+ PSC_AC97CFG_DE_ENABLE;
+
+ /* preserve PSC clock source set up by platform (dev.platform_data
+ * is already occupied by soc layer)
+ */
+ sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK;
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(0, PSC_SEL(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata));
+ au_sync();
+ /* next up: cold reset. Dont check for PSC-ready now since
+ * there may not be any codec clock yet.
+ */
+
+ return 0;
+
+out1:
+ release_resource(au1xpsc_ac97_workdata->ioarea);
+ kfree(au1xpsc_ac97_workdata->ioarea);
+out0:
+ kfree(au1xpsc_ac97_workdata);
+ au1xpsc_ac97_workdata = NULL;
+ return ret;
+}
+
+static void au1xpsc_ac97_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ /* disable PSC completely */
+ au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
+ au_sync();
+
+ iounmap(au1xpsc_ac97_workdata->mmio);
+ release_resource(au1xpsc_ac97_workdata->ioarea);
+ kfree(au1xpsc_ac97_workdata->ioarea);
+ kfree(au1xpsc_ac97_workdata);
+ au1xpsc_ac97_workdata = NULL;
+}
+
+static int au1xpsc_ac97_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ /* save interesting registers and disable PSC */
+ au1xpsc_ac97_workdata->pm[0] =
+ au_readl(PSC_SEL(au1xpsc_ac97_workdata));
+
+ au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
+ au_sync();
+
+ return 0;
+}
+
+static int au1xpsc_ac97_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ /* restore PSC clock config */
+ au_writel(au1xpsc_ac97_workdata->pm[0] | PSC_SEL_PS_AC97MODE,
+ PSC_SEL(au1xpsc_ac97_workdata));
+ au_sync();
+
+ /* after this point the ac97 core will cold-reset the codec.
+ * During cold-reset the PSC is reinitialized and the last
+ * configuration set up in hw_params() is restored.
+ */
+ return 0;
+}
+
+struct snd_soc_dai au1xpsc_ac97_dai = {
+ .name = "au1xpsc_ac97",
+ .type = SND_SOC_DAI_AC97,
+ .probe = au1xpsc_ac97_probe,
+ .remove = au1xpsc_ac97_remove,
+ .suspend = au1xpsc_ac97_suspend,
+ .resume = au1xpsc_ac97_resume,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = {
+ .trigger = au1xpsc_ac97_trigger,
+ .hw_params = au1xpsc_ac97_hw_params,
+ },
+};
+EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai);
+
+static int __init au1xpsc_ac97_init(void)
+{
+ au1xpsc_ac97_workdata = NULL;
+ return 0;
+}
+
+static void __exit au1xpsc_ac97_exit(void)
+{
+}
+
+module_init(au1xpsc_ac97_init);
+module_exit(au1xpsc_ac97_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c
new file mode 100644
index 0000000..9384702
--- /dev/null
+++ b/sound/soc/au1x/psc-i2s.c
@@ -0,0 +1,414 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC I2S glue.
+ *
+ * NOTE: all of these drivers can only work with a SINGLE instance
+ * of a PSC. Multiple independent audio devices are impossible
+ * with ASoC v1.
+ * NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/* supported I2S DAI hardware formats */
+#define AU1XPSC_I2S_DAIFMT \
+ (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \
+ SND_SOC_DAIFMT_NB_NF)
+
+/* supported I2S direction */
+#define AU1XPSC_I2S_DIR \
+ (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AU1XPSC_I2S_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define AU1XPSC_I2S_FMTS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+#define I2SSTAT_BUSY(stype) \
+ ((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)
+#define I2SPCR_START(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)
+#define I2SPCR_STOP(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)
+#define I2SPCR_CLRFIFO(stype) \
+ ((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
+
+
+/* instance data. There can be only one, MacLeod!!!! */
+static struct au1xpsc_audio_data *au1xpsc_i2s_workdata;
+
+static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+ unsigned long ct;
+ int ret;
+
+ ret = -EINVAL;
+
+ ct = pscdata->cfg;
+
+ ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ct |= PSC_I2SCFG_XM; /* enable I2S mode */
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */
+ break;
+ default:
+ goto out;
+ }
+
+ ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ ct |= PSC_I2SCFG_BI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ ct |= PSC_I2SCFG_WI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ goto out;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */
+ ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */
+ ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */
+ break;
+ default:
+ goto out;
+ }
+
+ pscdata->cfg = ct;
+ ret = 0;
+out:
+ return ret;
+}
+
+static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+
+ int cfgbits;
+ unsigned long stat;
+
+ /* check if the PSC is already streaming data */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {
+ /* reject parameters not currently set up in hardware */
+ cfgbits = au_readl(I2S_CFG(pscdata));
+ if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||
+ (params_rate(params) != pscdata->rate))
+ return -EINVAL;
+ } else {
+ /* set sample bitdepth */
+ pscdata->cfg &= ~(0x1f << 4);
+ pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);
+ /* remember current rate for other stream */
+ pscdata->rate = params_rate(params);
+ }
+ return 0;
+}
+
+/* Configure PSC late: on my devel systems the codec is I2S master and
+ * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC
+ * uses aggressive PM and switches the codec off when it is not in use
+ * which also means the PSC unit doesn't get any clocks and is therefore
+ * dead. That's why this chunk here gets called from the trigger callback
+ * because I can be reasonably certain the codec is driving the clocks.
+ */
+static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
+{
+ unsigned long tmo;
+
+ /* bring PSC out of sleep, and configure I2S unit */
+ au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+ au_sync();
+
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)
+ tmo--;
+
+ if (!tmo)
+ goto psc_err;
+
+ au_writel(0, I2S_CFG(pscdata));
+ au_sync();
+ au_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));
+ au_sync();
+
+ /* wait for I2S controller to become ready */
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)
+ tmo--;
+
+ if (tmo)
+ return 0;
+
+psc_err:
+ au_writel(0, I2S_CFG(pscdata));
+ au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+ au_sync();
+ return -ETIMEDOUT;
+}
+
+static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)
+{
+ unsigned long tmo, stat;
+ int ret;
+
+ ret = 0;
+
+ /* if both TX and RX are idle, configure the PSC */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+ ret = au1xpsc_i2s_configure(pscdata);
+ if (ret)
+ goto out;
+ }
+
+ au_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));
+ au_sync();
+ au_writel(I2SPCR_START(stype), I2S_PCR(pscdata));
+ au_sync();
+
+ /* wait for start confirmation */
+ tmo = 1000000;
+ while (!(au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+ tmo--;
+
+ if (!tmo) {
+ au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+ au_sync();
+ ret = -ETIMEDOUT;
+ }
+out:
+ return ret;
+}
+
+static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
+{
+ unsigned long tmo, stat;
+
+ au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+ au_sync();
+
+ /* wait for stop confirmation */
+ tmo = 1000000;
+ while ((au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+ tmo--;
+
+ /* if both TX and RX are idle, disable PSC */
+ stat = au_readl(I2S_STAT(pscdata));
+ if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+ au_writel(0, I2S_CFG(pscdata));
+ au_sync();
+ au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+ au_sync();
+ }
+ return 0;
+}
+
+static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
+ int ret, stype = SUBSTREAM_TYPE(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = au1xpsc_i2s_start(pscdata, stype);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = au1xpsc_i2s_stop(pscdata, stype);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int au1xpsc_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct resource *r;
+ unsigned long sel;
+ int ret;
+
+ if (au1xpsc_i2s_workdata)
+ return -EBUSY;
+
+ au1xpsc_i2s_workdata =
+ kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+ if (!au1xpsc_i2s_workdata)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ ret = -ENODEV;
+ goto out0;
+ }
+
+ ret = -EBUSY;
+ au1xpsc_i2s_workdata->ioarea =
+ request_mem_region(r->start, r->end - r->start + 1,
+ "au1xpsc_i2s");
+ if (!au1xpsc_i2s_workdata->ioarea)
+ goto out0;
+
+ au1xpsc_i2s_workdata->mmio = ioremap(r->start, 0xffff);
+ if (!au1xpsc_i2s_workdata->mmio)
+ goto out1;
+
+ /* preserve PSC clock source set up by platform (dev.platform_data
+ * is already occupied by soc layer)
+ */
+ sel = au_readl(PSC_SEL(au1xpsc_i2s_workdata)) & PSC_SEL_CLK_MASK;
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
+ au_sync();
+ au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(au1xpsc_i2s_workdata));
+ au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
+ au_sync();
+
+ /* preconfigure: set max rx/tx fifo depths */
+ au1xpsc_i2s_workdata->cfg |=
+ PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;
+
+ /* don't wait for I2S core to become ready now; clocks may not
+ * be running yet; depending on clock input for PSC a wait might
+ * time out.
+ */
+
+ return 0;
+
+out1:
+ release_resource(au1xpsc_i2s_workdata->ioarea);
+ kfree(au1xpsc_i2s_workdata->ioarea);
+out0:
+ kfree(au1xpsc_i2s_workdata);
+ au1xpsc_i2s_workdata = NULL;
+ return ret;
+}
+
+static void au1xpsc_i2s_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
+ au_sync();
+
+ iounmap(au1xpsc_i2s_workdata->mmio);
+ release_resource(au1xpsc_i2s_workdata->ioarea);
+ kfree(au1xpsc_i2s_workdata->ioarea);
+ kfree(au1xpsc_i2s_workdata);
+ au1xpsc_i2s_workdata = NULL;
+}
+
+static int au1xpsc_i2s_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ /* save interesting register and disable PSC */
+ au1xpsc_i2s_workdata->pm[0] =
+ au_readl(PSC_SEL(au1xpsc_i2s_workdata));
+
+ au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
+ au_sync();
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
+ au_sync();
+
+ return 0;
+}
+
+static int au1xpsc_i2s_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ /* select I2S mode and PSC clock */
+ au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
+ au_sync();
+ au_writel(0, PSC_SEL(au1xpsc_i2s_workdata));
+ au_sync();
+ au_writel(au1xpsc_i2s_workdata->pm[0],
+ PSC_SEL(au1xpsc_i2s_workdata));
+ au_sync();
+
+ return 0;
+}
+
+struct snd_soc_dai au1xpsc_i2s_dai = {
+ .name = "au1xpsc_i2s",
+ .type = SND_SOC_DAI_I2S,
+ .probe = au1xpsc_i2s_probe,
+ .remove = au1xpsc_i2s_remove,
+ .suspend = au1xpsc_i2s_suspend,
+ .resume = au1xpsc_i2s_resume,
+ .playback = {
+ .rates = AU1XPSC_I2S_RATES,
+ .formats = AU1XPSC_I2S_FMTS,
+ .channels_min = 2,
+ .channels_max = 8, /* 2 without external help */
+ },
+ .capture = {
+ .rates = AU1XPSC_I2S_RATES,
+ .formats = AU1XPSC_I2S_FMTS,
+ .channels_min = 2,
+ .channels_max = 8, /* 2 without external help */
+ },
+ .ops = {
+ .trigger = au1xpsc_i2s_trigger,
+ .hw_params = au1xpsc_i2s_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = au1xpsc_i2s_set_fmt,
+ },
+};
+EXPORT_SYMBOL(au1xpsc_i2s_dai);
+
+static int __init au1xpsc_i2s_init(void)
+{
+ au1xpsc_i2s_workdata = NULL;
+ return 0;
+}
+
+static void __exit au1xpsc_i2s_exit(void)
+{
+}
+
+module_init(au1xpsc_i2s_init);
+module_exit(au1xpsc_i2s_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
new file mode 100644
index 0000000..8fdb1a0
--- /dev/null
+++ b/sound/soc/au1x/psc.h
@@ -0,0 +1,53 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * NOTE: all of these drivers can only work with a SINGLE instance
+ * of a PSC. Multiple independent audio devices are impossible
+ * with ASoC v1.
+ */
+
+#ifndef _AU1X_PCM_H
+#define _AU1X_PCM_H
+
+extern struct snd_soc_dai au1xpsc_ac97_dai;
+extern struct snd_soc_dai au1xpsc_i2s_dai;
+extern struct snd_soc_platform au1xpsc_soc_platform;
+extern struct snd_ac97_bus_ops soc_ac97_ops;
+
+struct au1xpsc_audio_data {
+ void __iomem *mmio;
+
+ unsigned long cfg;
+ unsigned long rate;
+
+ unsigned long pm[2];
+ struct resource *ioarea;
+};
+
+#define PCM_TX 0
+#define PCM_RX 1
+
+#define SUBSTREAM_TYPE(substream) \
+ ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
+
+/* easy access macros */
+#define PSC_CTRL(x) ((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET)
+#define PSC_SEL(x) ((unsigned long)((x)->mmio) + PSC_SEL_OFFSET)
+#define I2S_STAT(x) ((unsigned long)((x)->mmio) + PSC_I2SSTAT_OFFSET)
+#define I2S_CFG(x) ((unsigned long)((x)->mmio) + PSC_I2SCFG_OFFSET)
+#define I2S_PCR(x) ((unsigned long)((x)->mmio) + PSC_I2SPCR_OFFSET)
+#define AC97_CFG(x) ((unsigned long)((x)->mmio) + PSC_AC97CFG_OFFSET)
+#define AC97_CDC(x) ((unsigned long)((x)->mmio) + PSC_AC97CDC_OFFSET)
+#define AC97_EVNT(x) ((unsigned long)((x)->mmio) + PSC_AC97EVNT_OFFSET)
+#define AC97_PCR(x) ((unsigned long)((x)->mmio) + PSC_AC97PCR_OFFSET)
+#define AC97_RST(x) ((unsigned long)((x)->mmio) + PSC_AC97RST_OFFSET)
+#define AC97_STAT(x) ((unsigned long)((x)->mmio) + PSC_AC97STAT_OFFSET)
+
+#endif
diff --git a/sound/soc/au1x/sample-ac97.c b/sound/soc/au1x/sample-ac97.c
new file mode 100644
index 0000000..f75ae7f
--- /dev/null
+++ b/sound/soc/au1x/sample-ac97.c
@@ -0,0 +1,144 @@
+/*
+ * Sample Au12x0/Au1550 PSC AC97 sound machine.
+ *
+ * Copyright (c) 2007-2008 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms outlined in the file COPYING at the root of this
+ * source archive.
+ *
+ * This is a very generic AC97 sound machine driver for boards which
+ * have (AC97) audio at PSC1 (e.g. DB1200 demoboards).
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+
+#include "../codecs/ac97.h"
+#include "psc.h"
+
+static int au1xpsc_sample_ac97_init(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+static struct snd_soc_dai_link au1xpsc_sample_ac97_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &au1xpsc_ac97_dai, /* see psc-ac97.c */
+ .codec_dai = &ac97_dai, /* see codecs/ac97.c */
+ .init = au1xpsc_sample_ac97_init,
+ .ops = NULL,
+};
+
+static struct snd_soc_machine au1xpsc_sample_ac97_machine = {
+ .name = "Au1xxx PSC AC97 Audio",
+ .dai_link = &au1xpsc_sample_ac97_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device au1xpsc_sample_ac97_devdata = {
+ .machine = &au1xpsc_sample_ac97_machine,
+ .platform = &au1xpsc_soc_platform, /* see dbdma2.c */
+ .codec_dev = &soc_codec_dev_ac97,
+};
+
+static struct resource au1xpsc_psc1_res[] = {
+ [0] = {
+ .start = CPHYSADDR(PSC1_BASE_ADDR),
+ .end = CPHYSADDR(PSC1_BASE_ADDR) + 0x000fffff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+#ifdef CONFIG_SOC_AU1200
+ .start = AU1200_PSC1_INT,
+ .end = AU1200_PSC1_INT,
+#elif defined(CONFIG_SOC_AU1550)
+ .start = AU1550_PSC1_INT,
+ .end = AU1550_PSC1_INT,
+#endif
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = {
+ .start = DSCR_CMD0_PSC1_TX,
+ .end = DSCR_CMD0_PSC1_TX,
+ .flags = IORESOURCE_DMA,
+ },
+ [3] = {
+ .start = DSCR_CMD0_PSC1_RX,
+ .end = DSCR_CMD0_PSC1_RX,
+ .flags = IORESOURCE_DMA,
+ },
+};
+
+static struct platform_device *au1xpsc_sample_ac97_dev;
+
+static int __init au1xpsc_sample_ac97_load(void)
+{
+ int ret;
+
+#ifdef CONFIG_SOC_AU1200
+ unsigned long io;
+
+ /* modify sys_pinfunc for AC97 on PSC1 */
+ io = au_readl(SYS_PINFUNC);
+ io |= SYS_PINFUNC_P1C;
+ io &= ~(SYS_PINFUNC_P1A | SYS_PINFUNC_P1B);
+ au_writel(io, SYS_PINFUNC);
+ au_sync();
+#endif
+
+ ret = -ENOMEM;
+
+ /* setup PSC clock source for AC97 part: external clock provided
+ * by codec. The psc-ac97.c driver depends on this setting!
+ */
+ au_writel(PSC_SEL_CLK_SERCLK, PSC1_BASE_ADDR + PSC_SEL_OFFSET);
+ au_sync();
+
+ au1xpsc_sample_ac97_dev = platform_device_alloc("soc-audio", -1);
+ if (!au1xpsc_sample_ac97_dev)
+ goto out;
+
+ au1xpsc_sample_ac97_dev->resource =
+ kmemdup(au1xpsc_psc1_res, sizeof(struct resource) *
+ ARRAY_SIZE(au1xpsc_psc1_res), GFP_KERNEL);
+ au1xpsc_sample_ac97_dev->num_resources = ARRAY_SIZE(au1xpsc_psc1_res);
+ au1xpsc_sample_ac97_dev->id = 1;
+
+ platform_set_drvdata(au1xpsc_sample_ac97_dev,
+ &au1xpsc_sample_ac97_devdata);
+ au1xpsc_sample_ac97_devdata.dev = &au1xpsc_sample_ac97_dev->dev;
+ ret = platform_device_add(au1xpsc_sample_ac97_dev);
+
+ if (ret) {
+ platform_device_put(au1xpsc_sample_ac97_dev);
+ au1xpsc_sample_ac97_dev = NULL;
+ }
+
+out:
+ return ret;
+}
+
+static void __exit au1xpsc_sample_ac97_exit(void)
+{
+ platform_device_unregister(au1xpsc_sample_ac97_dev);
+}
+
+module_init(au1xpsc_sample_ac97_load);
+module_exit(au1xpsc_sample_ac97_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1xxx PSC sample AC97 machine");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig
new file mode 100644
index 0000000..dc00620
--- /dev/null
+++ b/sound/soc/blackfin/Kconfig
@@ -0,0 +1,101 @@
+config SND_BF5XX_I2S
+ tristate "SoC I2S Audio for the ADI BF5xx chip"
+ depends on BLACKFIN && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Blackfin SPORT (synchronous serial ports) interface in I2S
+ mode (supports single stereo In/Out).
+ You will also need to select the audio interfaces to support below.
+
+config SND_BF5XX_SOC_SSM2602
+ tristate "SoC SSM2602 Audio support for BF52x ezkit"
+ depends on SND_BF5XX_I2S
+ select SND_BF5XX_SOC_I2S
+ select SND_SOC_SSM2602
+ select I2C
+ select I2C_BLACKFIN_TWI
+ help
+ Say Y if you want to add support for SoC audio on BF527-EZKIT.
+
+config SND_BF5XX_SOC_AD73311
+ tristate "SoC AD73311 Audio support for Blackfin"
+ depends on SND_BF5XX_I2S
+ select SND_BF5XX_SOC_I2S
+ select SND_SOC_AD73311
+ help
+ Say Y if you want to add support for AD73311 codec on Blackfin.
+
+config SND_BFIN_AD73311_SE
+ int "PF pin for AD73311L Chip Select"
+ depends on SND_BF5XX_SOC_AD73311
+ default 4
+ help
+ Enter the GPIO used to control AD73311's SE pin. Acceptable
+ values are 0 to 7
+
+config SND_BF5XX_AC97
+ tristate "SoC AC97 Audio for the ADI BF5xx chip"
+ depends on BLACKFIN && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Blackfin SPORT (synchronous serial ports) interface in slot 16
+ mode (pseudo AC97 interface).
+ You will also need to select the audio interfaces to support below.
+
+ Note:
+ AC97 codecs which do not implment the slot-16 mode will not function
+ properly with this driver. This driver is known to work with the
+ Analog Devices line of AC97 codecs.
+
+config SND_MMAP_SUPPORT
+ bool "Enable MMAP Support"
+ depends on SND_BF5XX_AC97
+ default y
+ help
+ Say y if you want AC97 driver to support mmap mode.
+ We introduce an intermediate buffer to simulate mmap.
+
+config SND_BF5XX_SOC_SPORT
+ tristate
+
+config SND_BF5XX_SOC_I2S
+ tristate
+ select SND_BF5XX_SOC_SPORT
+
+config SND_BF5XX_SOC_AC97
+ tristate
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+ select SND_BF5XX_SOC_SPORT
+
+config SND_BF5XX_SOC_AD1980
+ tristate "SoC AD1980/1 Audio support for BF5xx"
+ depends on SND_BF5XX_AC97
+ select SND_BF5XX_SOC_AC97
+ select SND_SOC_AD1980
+ help
+ Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+
+config SND_BF5XX_SPORT_NUM
+ int "Set a SPORT for Sound chip"
+ depends on (SND_BF5XX_I2S || SND_BF5XX_AC97)
+ range 0 3 if BF54x
+ range 0 1 if (BF53x || BF561)
+ default 0
+ help
+ Set the correct SPORT for sound chip.
+
+config SND_BF5XX_HAVE_COLD_RESET
+ bool "BOARD has COLD Reset GPIO"
+ depends on SND_BF5XX_AC97
+ default y if BFIN548_EZKIT
+ default n if !BFIN548_EZKIT
+
+config SND_BF5XX_RESET_GPIO_NUM
+ int "Set a GPIO for cold reset"
+ depends on SND_BF5XX_HAVE_COLD_RESET
+ range 0 159
+ default 19 if BFIN548_EZKIT
+ default 5 if BFIN537_STAMP
+ help
+ Set the correct GPIO for RESET the sound chip.
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile
new file mode 100644
index 0000000..97bb37a
--- /dev/null
+++ b/sound/soc/blackfin/Makefile
@@ -0,0 +1,21 @@
+# Blackfin Platform Support
+snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
+snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
+snd-soc-bf5xx-sport-objs := bf5xx-sport.o
+snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
+snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
+
+obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
+obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
+obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
+
+# Blackfin Machine Support
+snd-ad1980-objs := bf5xx-ad1980.o
+snd-ssm2602-objs := bf5xx-ssm2602.o
+snd-ad73311-objs := bf5xx-ad73311.o
+
+obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
+obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.c b/sound/soc/blackfin/bf5xx-ac97-pcm.c
new file mode 100644
index 0000000..25e50d2
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97-pcm.c
@@ -0,0 +1,457 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ac97-pcm.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: DMA Driver for AC97 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+#include "bf5xx-sport.h"
+
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+static void bf5xx_mmap_copy(struct snd_pcm_substream *substream,
+ snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ bf5xx_pcm_to_ac97(
+ (struct ac97_frame *)sport->tx_dma_buf + sport->tx_pos,
+ (__u32 *)runtime->dma_area + sport->tx_pos, count);
+ sport->tx_pos += runtime->period_size;
+ if (sport->tx_pos >= runtime->buffer_size)
+ sport->tx_pos %= runtime->buffer_size;
+ sport->tx_delay_pos = sport->tx_pos;
+ } else {
+ bf5xx_ac97_to_pcm(
+ (struct ac97_frame *)sport->rx_dma_buf + sport->rx_pos,
+ (__u32 *)runtime->dma_area + sport->rx_pos, count);
+ sport->rx_pos += runtime->period_size;
+ if (sport->rx_pos >= runtime->buffer_size)
+ sport->rx_pos %= runtime->buffer_size;
+ }
+}
+#endif
+
+static void bf5xx_dma_irq(void *data)
+{
+ struct snd_pcm_substream *pcm = data;
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ struct sport_device *sport = runtime->private_data;
+ bf5xx_mmap_copy(pcm, runtime->period_size);
+ if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (sport->once == 0) {
+ snd_pcm_period_elapsed(pcm);
+ bf5xx_mmap_copy(pcm, runtime->period_size);
+ sport->once = 1;
+ }
+ }
+#endif
+ snd_pcm_period_elapsed(pcm);
+}
+
+/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes.
+ * The total rx/tx buffer is for ac97 frame to hold all pcm data
+ * is 0x20000 * sizeof(struct ac97_frame) / 4.
+ */
+#ifdef CONFIG_SND_MMAP_SUPPORT
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+#else
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+#endif
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 0x10000,
+ .periods_min = 1,
+ .periods_max = PAGE_SIZE/32,
+ .buffer_bytes_max = 0x20000, /* 128 kbytes */
+ .fifo_size = 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max
+ * sizeof(struct ac97_frame) / 4;
+
+ snd_pcm_lib_malloc_pages(substream, size);
+
+ return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ memset(runtime->dma_area, 0, runtime->buffer_size);
+ snd_pcm_lib_free_pages(substream);
+ return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+
+ /* An intermediate buffer is introduced for implementing mmap for
+ * SPORT working in TMD mode(include AC97).
+ */
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ }
+#else
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, runtime->dma_area, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, runtime->dma_area, runtime->periods,
+ runtime->period_size * sizeof(struct ac97_frame));
+ }
+#endif
+ return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ bf5xx_mmap_copy(substream, runtime->period_size);
+ snd_pcm_period_elapsed(substream);
+ sport->tx_delay_pos = 0;
+ sport_tx_start(sport);
+ }
+ else
+ sport_rx_start(sport);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ sport->tx_pos = 0;
+#endif
+ sport_tx_stop(sport);
+ } else {
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ sport->rx_pos = 0;
+#endif
+ sport_rx_stop(sport);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int curr;
+
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ curr = sport->tx_delay_pos;
+ else
+ curr = sport->rx_pos;
+#else
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame);
+ else
+ curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame);
+
+#endif
+ return curr;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ if (sport_handle != NULL)
+ runtime->private_data = sport_handle;
+ else {
+ pr_err("sport_handle is NULL\n");
+ return -1;
+ }
+ return 0;
+
+ out:
+ return ret;
+}
+
+static int bf5xx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+
+ pr_debug("%s enter\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport->once = 0;
+ memset(sport->tx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame));
+ } else
+ memset(sport->rx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame));
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_MMAP_SUPPORT
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t size = vma->vm_end - vma->vm_start;
+ vma->vm_start = (unsigned long)runtime->dma_area;
+ vma->vm_end = vma->vm_start + size;
+ vma->vm_flags |= VM_SHARED;
+ return 0 ;
+}
+#else
+static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos,
+ void __user *buf, snd_pcm_uframes_t count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ pr_debug("%s copy pos:0x%lx count:0x%lx\n",
+ substream->stream ? "Capture" : "Playback", pos, count);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ bf5xx_pcm_to_ac97(
+ (struct ac97_frame *)runtime->dma_area + pos,
+ buf, count);
+ else
+ bf5xx_ac97_to_pcm(
+ (struct ac97_frame *)runtime->dma_area + pos,
+ buf, count);
+ return 0;
+}
+#endif
+
+struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
+ .open = bf5xx_pcm_open,
+ .close = bf5xx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bf5xx_pcm_hw_params,
+ .hw_free = bf5xx_pcm_hw_free,
+ .prepare = bf5xx_pcm_prepare,
+ .trigger = bf5xx_pcm_trigger,
+ .pointer = bf5xx_pcm_pointer,
+#ifdef CONFIG_SND_MMAP_SUPPORT
+ .mmap = bf5xx_pcm_mmap,
+#else
+ .copy = bf5xx_pcm_copy,
+#endif
+};
+
+static int bf5xx_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 = bf5xx_pcm_hardware.buffer_bytes_max
+ * sizeof(struct ac97_frame) / 4;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ pr_err("Failed to allocate dma memory\n");
+ pr_err("Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ }
+ buf->bytes = size;
+
+ pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+ buf->area, buf->bytes);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_handle->tx_buf = buf->area;
+ else
+ sport_handle->rx_buf = buf->area;
+
+/*
+ * Need to allocate local buffer when enable
+ * MMAP for SPORT working in TMD mode (include AC97).
+ */
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (!sport_handle->tx_dma_buf) {
+ sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
+ size, &sport_handle->tx_dma_phy, GFP_KERNEL);
+ if (!sport_handle->tx_dma_buf) {
+ pr_err("Failed to allocate memory for tx dma \
+ buf - Please increase uncached DMA \
+ memory region\n");
+ return -ENOMEM;
+ } else
+ memset(sport_handle->tx_dma_buf, 0, size);
+ } else
+ memset(sport_handle->tx_dma_buf, 0, size);
+ } else {
+ if (!sport_handle->rx_dma_buf) {
+ sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
+ size, &sport_handle->rx_dma_phy, GFP_KERNEL);
+ if (!sport_handle->rx_dma_buf) {
+ pr_err("Failed to allocate memory for rx dma \
+ buf - Please increase uncached DMA \
+ memory region\n");
+ return -ENOMEM;
+ } else
+ memset(sport_handle->rx_dma_buf, 0, size);
+ } else
+ memset(sport_handle->rx_dma_buf, 0, size);
+ }
+#endif
+ return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max *
+ sizeof(struct ac97_frame) / 4;
+#endif
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+ buf->area = NULL;
+#if defined(CONFIG_SND_MMAP_SUPPORT)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (sport_handle->tx_dma_buf)
+ dma_free_coherent(NULL, size, \
+ sport_handle->tx_dma_buf, 0);
+ sport_handle->tx_dma_buf = NULL;
+ } else {
+
+ if (sport_handle->rx_dma_buf)
+ dma_free_coherent(NULL, size, \
+ sport_handle->rx_dma_buf, 0);
+ sport_handle->rx_dma_buf = NULL;
+ }
+#endif
+ }
+ if (sport_handle)
+ sport_done(sport_handle);
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK;
+
+int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bf5xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_32BIT_MASK;
+
+ if (dai->playback.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform bf5xx_ac97_soc_platform = {
+ .name = "bf5xx-audio",
+ .pcm_ops = &bf5xx_pcm_ac97_ops,
+ .pcm_new = bf5xx_pcm_ac97_new,
+ .pcm_free = bf5xx_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.h b/sound/soc/blackfin/bf5xx-ac97-pcm.h
new file mode 100644
index 0000000..350125a
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97-pcm.h
@@ -0,0 +1,29 @@
+/*
+ * linux/sound/arm/bf5xx-ac97-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_PCM_H
+#define _BF5XX_AC97_PCM_H
+
+struct bf5xx_pcm_dma_params {
+ char *name; /* stream identifier */
+};
+
+struct bf5xx_gpio {
+ u32 sys;
+ u32 rx;
+ u32 tx;
+ u32 clk;
+ u32 frm;
+};
+
+/* platform data */
+extern struct snd_soc_platform bf5xx_ac97_soc_platform;
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c
new file mode 100644
index 0000000..5e5aafb
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97.c
@@ -0,0 +1,406 @@
+/*
+ * bf5xx-ac97.c -- AC97 support for the ADI blackfin chip.
+ *
+ * Author: Roy Huang
+ * Created: 11th. June 2007
+ * Copyright: Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-ac97.h"
+
+#if defined(CONFIG_BF54x)
+#define PIN_REQ_SPORT_0 {P_SPORT0_TFS, P_SPORT0_DTPRI, P_SPORT0_TSCLK, \
+ P_SPORT0_RFS, P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}
+
+#define PIN_REQ_SPORT_1 {P_SPORT1_TFS, P_SPORT1_DTPRI, P_SPORT1_TSCLK, \
+ P_SPORT1_RFS, P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}
+
+#define PIN_REQ_SPORT_2 {P_SPORT2_TFS, P_SPORT2_DTPRI, P_SPORT2_TSCLK, \
+ P_SPORT2_RFS, P_SPORT2_DRPRI, P_SPORT2_RSCLK, 0}
+
+#define PIN_REQ_SPORT_3 {P_SPORT3_TFS, P_SPORT3_DTPRI, P_SPORT3_TSCLK, \
+ P_SPORT3_RFS, P_SPORT3_DRPRI, P_SPORT3_RSCLK, 0}
+#else
+#define PIN_REQ_SPORT_0 {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, \
+ P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}
+
+#define PIN_REQ_SPORT_1 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, \
+ P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}
+#endif
+
+static int *cmd_count;
+static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+
+#if defined(CONFIG_BF54x)
+static struct sport_param sport_params[4] = {
+ {
+ .dma_rx_chan = CH_SPORT0_RX,
+ .dma_tx_chan = CH_SPORT0_TX,
+ .err_irq = IRQ_SPORT0_ERR,
+ .regs = (struct sport_register *)SPORT0_TCR1,
+ },
+ {
+ .dma_rx_chan = CH_SPORT1_RX,
+ .dma_tx_chan = CH_SPORT1_TX,
+ .err_irq = IRQ_SPORT1_ERR,
+ .regs = (struct sport_register *)SPORT1_TCR1,
+ },
+ {
+ .dma_rx_chan = CH_SPORT2_RX,
+ .dma_tx_chan = CH_SPORT2_TX,
+ .err_irq = IRQ_SPORT2_ERR,
+ .regs = (struct sport_register *)SPORT2_TCR1,
+ },
+ {
+ .dma_rx_chan = CH_SPORT3_RX,
+ .dma_tx_chan = CH_SPORT3_TX,
+ .err_irq = IRQ_SPORT3_ERR,
+ .regs = (struct sport_register *)SPORT3_TCR1,
+ }
+};
+#else
+static struct sport_param sport_params[2] = {
+ {
+ .dma_rx_chan = CH_SPORT0_RX,
+ .dma_tx_chan = CH_SPORT0_TX,
+ .err_irq = IRQ_SPORT0_ERROR,
+ .regs = (struct sport_register *)SPORT0_TCR1,
+ },
+ {
+ .dma_rx_chan = CH_SPORT1_RX,
+ .dma_tx_chan = CH_SPORT1_TX,
+ .err_irq = IRQ_SPORT1_ERROR,
+ .regs = (struct sport_register *)SPORT1_TCR1,
+ }
+};
+#endif
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \
+ size_t count)
+{
+ while (count--) {
+ dst->ac97_tag = TAG_VALID | TAG_PCM;
+ (dst++)->ac97_pcm = *src++;
+ }
+}
+EXPORT_SYMBOL(bf5xx_pcm_to_ac97);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \
+ size_t count)
+{
+ while (count--)
+ *(dst++) = (src++)->ac97_pcm;
+}
+EXPORT_SYMBOL(bf5xx_ac97_to_pcm);
+
+static unsigned int sport_tx_curr_frag(struct sport_device *sport)
+{
+ return sport->tx_curr_frag = sport_curr_offset_tx(sport) / \
+ sport->tx_fragsize;
+}
+
+static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data)
+{
+ struct sport_device *sport = sport_handle;
+ int nextfrag = sport_tx_curr_frag(sport);
+ struct ac97_frame *nextwrite;
+
+ sport_incfrag(sport, &nextfrag, 1);
+
+ nextwrite = (struct ac97_frame *)(sport->tx_buf + \
+ nextfrag * sport->tx_fragsize);
+ pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n",
+ sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]);
+ nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD;
+ nextwrite[cmd_count[nextfrag]].ac97_addr = addr;
+ nextwrite[cmd_count[nextfrag]].ac97_data = data;
+ ++cmd_count[nextfrag];
+ pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n",
+ addr >> 8, data, nextfrag);
+}
+
+static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct ac97_frame out_frame[2], in_frame[2];
+
+ pr_debug("%s enter 0x%x\n", __func__, reg);
+
+ /* When dma descriptor is enabled, the register should not be read */
+ if (sport_handle->tx_run || sport_handle->rx_run) {
+ pr_err("Could you send a mail to cliff.cai@analog.com "
+ "to report this?\n");
+ return -EFAULT;
+ }
+
+ memset(&out_frame, 0, 2 * sizeof(struct ac97_frame));
+ memset(&in_frame, 0, 2 * sizeof(struct ac97_frame));
+ out_frame[0].ac97_tag = TAG_VALID | TAG_CMD;
+ out_frame[0].ac97_addr = ((reg << 8) | 0x8000);
+ sport_send_and_recv(sport_handle, (unsigned char *)&out_frame,
+ (unsigned char *)&in_frame,
+ 2 * sizeof(struct ac97_frame));
+ return in_frame[1].ac97_data;
+}
+
+void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val);
+
+ if (sport_handle->tx_run) {
+ enqueue_cmd(ac97, (reg << 8), val); /* write */
+ enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */
+ } else {
+ struct ac97_frame frame;
+ memset(&frame, 0, sizeof(struct ac97_frame));
+ frame.ac97_tag = TAG_VALID | TAG_CMD;
+ frame.ac97_addr = (reg << 8);
+ frame.ac97_data = val;
+ sport_send_and_recv(sport_handle, (unsigned char *)&frame, \
+ NULL, sizeof(struct ac97_frame));
+ }
+}
+
+static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+#if defined(CONFIG_BF54x) || defined(CONFIG_BF561) || \
+ (defined(BF537_FAMILY) && (CONFIG_SND_BF5XX_SPORT_NUM == 1))
+
+#define CONCAT(a, b, c) a ## b ## c
+#define BFIN_SPORT_RFS(x) CONCAT(P_SPORT, x, _RFS)
+
+ u16 per = BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM);
+ u16 gpio = P_IDENT(BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM));
+
+ pr_debug("%s enter\n", __func__);
+
+ peripheral_free(per);
+ gpio_request(gpio, "bf5xx-ac97");
+ gpio_direction_output(gpio, 1);
+ udelay(2);
+ gpio_set_value(gpio, 0);
+ udelay(1);
+ gpio_free(gpio);
+ peripheral_request(per, "soc-audio");
+#else
+ pr_info("%s: Not implemented\n", __func__);
+#endif
+}
+
+static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ pr_debug("%s enter\n", __func__);
+
+ /* It is specified for bf548-ezkit */
+ gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0);
+ /* Keep reset pin low for 1 ms */
+ mdelay(1);
+ gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+ /* Wait for bit clock recover */
+ mdelay(1);
+#else
+ pr_info("%s: Not implemented\n", __func__);
+#endif
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = bf5xx_ac97_read,
+ .write = bf5xx_ac97_write,
+ .warm_reset = bf5xx_ac97_warm_reset,
+ .reset = bf5xx_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+#ifdef CONFIG_PM
+static int bf5xx_ac97_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport =
+ (struct sport_device *)dai->private_data;
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+ if (dai->capture.active)
+ sport_rx_stop(sport);
+ if (dai->playback.active)
+ sport_tx_stop(sport);
+ return 0;
+}
+
+static int bf5xx_ac97_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct sport_device *sport =
+ (struct sport_device *)dai->private_data;
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+
+ ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ if (dai->capture.active)
+ sport_rx_start(sport);
+ if (dai->playback.active)
+ sport_tx_start(sport);
+ return 0;
+}
+
+#else
+#define bf5xx_ac97_suspend NULL
+#define bf5xx_ac97_resume NULL
+#endif
+
+static int bf5xx_ac97_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+#if defined(CONFIG_BF54x)
+ u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1,
+ PIN_REQ_SPORT_2, PIN_REQ_SPORT_3};
+#else
+ u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1};
+#endif
+ cmd_count = (int *)get_zeroed_page(GFP_KERNEL);
+ if (cmd_count == NULL)
+ return -ENOMEM;
+
+ if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
+ pr_err("Requesting Peripherals failed\n");
+ return -EFAULT;
+ }
+
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ /* Request PB3 as reset pin */
+ if (gpio_request(CONFIG_SND_BF5XX_RESET_GPIO_NUM, "SND_AD198x RESET")) {
+ pr_err("Failed to request GPIO_%d for reset\n",
+ CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+ peripheral_free_list(&sport_req[sport_num][0]);
+ return -1;
+ }
+ gpio_direction_output(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+#endif
+ sport_handle = sport_init(&sport_params[sport_num], 2, \
+ sizeof(struct ac97_frame), NULL);
+ if (!sport_handle) {
+ peripheral_free_list(&sport_req[sport_num][0]);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+ return -ENODEV;
+ }
+ /*SPORT works in TDM mode to simulate AC97 transfers*/
+ ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ kfree(sport_handle);
+ peripheral_free_list(&sport_req[sport_num][0]);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+ return -EBUSY;
+ }
+
+ ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ kfree(sport_handle);
+ peripheral_free_list(&sport_req[sport_num][0]);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ kfree(sport_handle);
+ peripheral_free_list(&sport_req[sport_num][0]);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static void bf5xx_ac97_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ free_page((unsigned long)cmd_count);
+ cmd_count = NULL;
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+ gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+}
+
+struct snd_soc_dai bfin_ac97_dai = {
+ .name = "bf5xx-ac97",
+ .id = 0,
+ .type = SND_SOC_DAI_AC97,
+ .probe = bf5xx_ac97_probe,
+ .remove = bf5xx_ac97_remove,
+ .suspend = bf5xx_ac97_suspend,
+ .resume = bf5xx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(bfin_ac97_dai);
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("AC97 driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ac97.h b/sound/soc/blackfin/bf5xx-ac97.h
new file mode 100644
index 0000000..3f77cc5
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ac97.h
@@ -0,0 +1,36 @@
+/*
+ * linux/sound/arm/bf5xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_H
+#define _BF5XX_AC97_H
+
+extern struct snd_ac97_bus_ops bf5xx_ac97_ops;
+extern struct snd_ac97 *ac97;
+/* Frame format in memory, only support stereo currently */
+struct ac97_frame {
+ u16 ac97_tag; /* slot 0 */
+ u16 ac97_addr; /* slot 1 */
+ u16 ac97_data; /* slot 2 */
+ u32 ac97_pcm; /* slot 3 and 4: left and right pcm data */
+} __attribute__ ((packed));
+
+#define TAG_VALID 0x8000
+#define TAG_CMD 0x6000
+#define TAG_PCM_LEFT 0x1000
+#define TAG_PCM_RIGHT 0x0800
+#define TAG_PCM (TAG_PCM_LEFT | TAG_PCM_RIGHT)
+
+extern struct snd_soc_dai bfin_ac97_dai;
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \
+ size_t count);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \
+ size_t count);
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-ad1980.c b/sound/soc/blackfin/bf5xx-ad1980.c
new file mode 100644
index 0000000..124425d
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad1980.c
@@ -0,0 +1,113 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad1980.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Board driver for AD1980/1 audio codec
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <asm/dma.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <linux/gpio.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad1980.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+
+static struct snd_soc_machine bf5xx_board;
+
+static int bf5xx_board_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ pr_debug("%s enter\n", __func__);
+ cpu_dai->private_data = sport_handle;
+ return 0;
+}
+
+static struct snd_soc_ops bf5xx_board_ops = {
+ .startup = bf5xx_board_startup,
+};
+
+static struct snd_soc_dai_link bf5xx_board_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &bfin_ac97_dai,
+ .codec_dai = &ad1980_dai,
+ .ops = &bf5xx_board_ops,
+};
+
+static struct snd_soc_machine bf5xx_board = {
+ .name = "bf5xx-board",
+ .dai_link = &bf5xx_board_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device bf5xx_board_snd_devdata = {
+ .machine = &bf5xx_board,
+ .platform = &bf5xx_ac97_soc_platform,
+ .codec_dev = &soc_codec_dev_ad1980,
+};
+
+static struct platform_device *bf5xx_board_snd_device;
+
+static int __init bf5xx_board_init(void)
+{
+ int ret;
+
+ bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf5xx_board_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board_snd_devdata);
+ bf5xx_board_snd_devdata.dev = &bf5xx_board_snd_device->dev;
+ ret = platform_device_add(bf5xx_board_snd_device);
+
+ if (ret)
+ platform_device_put(bf5xx_board_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_board_exit(void)
+{
+ platform_device_unregister(bf5xx_board_snd_device);
+}
+
+module_init(bf5xx_board_init);
+module_exit(bf5xx_board_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c
new file mode 100644
index 0000000..622c9b9
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ad73311.c
@@ -0,0 +1,240 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ad73311.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Thur Sep 25 2008
+ * Description: Board driver for ad73311 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad73311.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+#include "bf5xx-i2s.h"
+
+#if CONFIG_SND_BF5XX_SPORT_NUM == 0
+#define bfin_write_SPORT_TCR1 bfin_write_SPORT0_TCR1
+#define bfin_read_SPORT_TCR1 bfin_read_SPORT0_TCR1
+#define bfin_write_SPORT_TCR2 bfin_write_SPORT0_TCR2
+#define bfin_write_SPORT_TX16 bfin_write_SPORT0_TX16
+#define bfin_read_SPORT_STAT bfin_read_SPORT0_STAT
+#else
+#define bfin_write_SPORT_TCR1 bfin_write_SPORT1_TCR1
+#define bfin_read_SPORT_TCR1 bfin_read_SPORT1_TCR1
+#define bfin_write_SPORT_TCR2 bfin_write_SPORT1_TCR2
+#define bfin_write_SPORT_TX16 bfin_write_SPORT1_TX16
+#define bfin_read_SPORT_STAT bfin_read_SPORT1_STAT
+#endif
+
+#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE
+
+static struct snd_soc_machine bf5xx_ad73311;
+
+static int snd_ad73311_startup(void)
+{
+ pr_debug("%s enter\n", __func__);
+
+ /* Pull up SE pin on AD73311L */
+ gpio_set_value(GPIO_SE, 1);
+ return 0;
+}
+
+static int snd_ad73311_configure(void)
+{
+ unsigned short ctrl_regs[6];
+ unsigned short status = 0;
+ int count = 0;
+
+ /* DMCLK = MCLK = 16.384 MHz
+ * SCLK = DMCLK/8 = 2.048 MHz
+ * Sample Rate = DMCLK/2048 = 8 KHz
+ */
+ ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \
+ REGB_SCDIV(0) | REGB_DIRATE(0);
+ ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \
+ REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ;
+ ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \
+ REGD_IGS(2);
+ ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f);
+ ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ;
+ ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA;
+
+ local_irq_disable();
+ snd_ad73311_startup();
+ udelay(1);
+
+ bfin_write_SPORT_TCR1(TFSR);
+ bfin_write_SPORT_TCR2(0xF);
+ SSYNC();
+
+ /* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to
+ * FIFO before enable SPORT to transfer the data
+ */
+ for (count = 0; count < 6; count++)
+ bfin_write_SPORT_TX16(ctrl_regs[count]);
+ SSYNC();
+ bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN);
+ SSYNC();
+
+ /* When TUVF is set, the data is already send out */
+ while (!(status & TUVF) && count++ < 10000) {
+ udelay(1);
+ status = bfin_read_SPORT_STAT();
+ SSYNC();
+ }
+ bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN);
+ SSYNC();
+ local_irq_enable();
+
+ if (count == 10000) {
+ printk(KERN_ERR "ad73311: failed to configure codec\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int bf5xx_probe(struct platform_device *pdev)
+{
+ int err;
+ if (gpio_request(GPIO_SE, "AD73311_SE")) {
+ printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE);
+ return -EBUSY;
+ }
+
+ gpio_direction_output(GPIO_SE, 0);
+
+ err = snd_ad73311_configure();
+ if (err < 0)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int bf5xx_ad73311_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ pr_debug("%s enter\n", __func__);
+ cpu_dai->private_data = sport_handle;
+ return 0;
+}
+
+static int bf5xx_ad73311_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_dai *cpu_dai = rtd->dai->cpu_dai;
+ int ret = 0;
+
+ pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+ params_format(params));
+
+ /* set cpu DAI configuration */
+ ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+
+static struct snd_soc_ops bf5xx_ad73311_ops = {
+ .startup = bf5xx_ad73311_startup,
+ .hw_params = bf5xx_ad73311_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad73311_dai = {
+ .name = "ad73311",
+ .stream_name = "AD73311",
+ .cpu_dai = &bf5xx_i2s_dai,
+ .codec_dai = &ad73311_dai,
+ .ops = &bf5xx_ad73311_ops,
+};
+
+static struct snd_soc_machine bf5xx_ad73311 = {
+ .name = "bf5xx_ad73311",
+ .probe = bf5xx_probe,
+ .dai_link = &bf5xx_ad73311_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device bf5xx_ad73311_snd_devdata = {
+ .machine = &bf5xx_ad73311,
+ .platform = &bf5xx_i2s_soc_platform,
+ .codec_dev = &soc_codec_dev_ad73311,
+};
+
+static struct platform_device *bf52x_ad73311_snd_device;
+
+static int __init bf5xx_ad73311_init(void)
+{
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf52x_ad73311_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata);
+ bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev;
+ ret = platform_device_add(bf52x_ad73311_snd_device);
+
+ if (ret)
+ platform_device_put(bf52x_ad73311_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ad73311_exit(void)
+{
+ pr_debug("%s enter\n", __func__);
+ platform_device_unregister(bf52x_ad73311_snd_device);
+}
+
+module_init(bf5xx_ad73311_init);
+module_exit(bf5xx_ad73311_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c
new file mode 100644
index 0000000..61fccf9
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c
@@ -0,0 +1,288 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-i2s-pcm.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: DMA driver for i2s codec
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-i2s-pcm.h"
+#include "bf5xx-i2s.h"
+#include "bf5xx-sport.h"
+
+static void bf5xx_dma_irq(void *data)
+{
+ struct snd_pcm_substream *pcm = data;
+ snd_pcm_period_elapsed(pcm);
+}
+
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 0x10000,
+ .periods_min = 1,
+ .periods_max = PAGE_SIZE/32,
+ .buffer_bytes_max = 0x20000, /* 128 kbytes */
+ .fifo_size = 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+ snd_pcm_lib_malloc_pages(substream, size);
+
+ return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+
+ return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int period_bytes = frames_to_bytes(runtime, runtime->period_size);
+
+ pr_debug("%s enter\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_tx_dma(sport, runtime->dma_area,
+ runtime->periods, period_bytes);
+ } else {
+ sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+ sport_config_rx_dma(sport, runtime->dma_area,
+ runtime->periods, period_bytes);
+ }
+
+ return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_start(sport);
+ else
+ sport_rx_start(sport);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_tx_stop(sport);
+ else
+ sport_rx_stop(sport);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sport_device *sport = runtime->private_data;
+ unsigned int diff;
+ snd_pcm_uframes_t frames;
+ pr_debug("%s enter\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ diff = sport_curr_offset_tx(sport);
+ frames = bytes_to_frames(substream->runtime, diff);
+ } else {
+ diff = sport_curr_offset_rx(sport);
+ frames = bytes_to_frames(substream->runtime, diff);
+ }
+ return frames;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime, \
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ if (sport_handle != NULL)
+ runtime->private_data = sport_handle;
+ else {
+ pr_err("sport_handle is NULL\n");
+ return -1;
+ }
+ return 0;
+
+ out:
+ return ret;
+}
+
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t size = vma->vm_end - vma->vm_start;
+ vma->vm_start = (unsigned long)runtime->dma_area;
+ vma->vm_end = vma->vm_start + size;
+ vma->vm_flags |= VM_SHARED;
+
+ return 0 ;
+}
+
+struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
+ .open = bf5xx_pcm_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bf5xx_pcm_hw_params,
+ .hw_free = bf5xx_pcm_hw_free,
+ .prepare = bf5xx_pcm_prepare,
+ .trigger = bf5xx_pcm_trigger,
+ .pointer = bf5xx_pcm_pointer,
+ .mmap = bf5xx_pcm_mmap,
+};
+
+static int bf5xx_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 = bf5xx_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_coherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ pr_err("Failed to allocate dma memory \
+ Please increase uncached DMA memory region\n");
+ return -ENOMEM;
+ }
+ buf->bytes = size;
+
+ pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+ buf->area, buf->bytes);
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sport_handle->tx_buf = buf->area;
+ else
+ sport_handle->rx_buf = buf->area;
+
+ return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+ dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+ buf->area = NULL;
+ }
+ if (sport_handle)
+ sport_done(sport_handle);
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK;
+
+int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ pr_debug("%s enter\n", __func__);
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bf5xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_32BIT_MASK;
+
+ if (dai->playback.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform bf5xx_i2s_soc_platform = {
+ .name = "bf5xx-audio",
+ .pcm_ops = &bf5xx_pcm_i2s_ops,
+ .pcm_new = bf5xx_pcm_i2s_new,
+ .pcm_free = bf5xx_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(bf5xx_i2s_soc_platform);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.h b/sound/soc/blackfin/bf5xx-i2s-pcm.h
new file mode 100644
index 0000000..4d4609a
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.h
@@ -0,0 +1,29 @@
+/*
+ * linux/sound/arm/bf5xx-i2s-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_I2S_PCM_H
+#define _BF5XX_I2S_PCM_H
+
+struct bf5xx_pcm_dma_params {
+ char *name; /* stream identifier */
+};
+
+struct bf5xx_gpio {
+ u32 sys;
+ u32 rx;
+ u32 tx;
+ u32 clk;
+ u32 frm;
+};
+
+/* platform data */
+extern struct snd_soc_platform bf5xx_i2s_soc_platform;
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c
new file mode 100644
index 0000000..e020c16
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s.c
@@ -0,0 +1,321 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-i2s.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Blackfin I2S CPU DAI driver
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s.h"
+
+struct bf5xx_i2s_port {
+ u16 tcr1;
+ u16 rcr1;
+ u16 tcr2;
+ u16 rcr2;
+ int counter;
+};
+
+static struct bf5xx_i2s_port bf5xx_i2s;
+static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+
+static struct sport_param sport_params[2] = {
+ {
+ .dma_rx_chan = CH_SPORT0_RX,
+ .dma_tx_chan = CH_SPORT0_TX,
+ .err_irq = IRQ_SPORT0_ERROR,
+ .regs = (struct sport_register *)SPORT0_TCR1,
+ },
+ {
+ .dma_rx_chan = CH_SPORT1_RX,
+ .dma_tx_chan = CH_SPORT1_TX,
+ .err_irq = IRQ_SPORT1_ERROR,
+ .regs = (struct sport_register *)SPORT1_TCR1,
+ }
+};
+
+/*
+ * Setting the TFS pin selector for SPORT 0 based on whether the selected
+ * port id F or G. If the port is F then no conflict should exist for the
+ * TFS. When Port G is selected and EMAC then there is a conflict between
+ * the PHY interrupt line and TFS. Current settings prevent the conflict
+ * by ignoring the TFS pin when Port G is selected. This allows both
+ * ssm2602 using Port G and EMAC concurrently.
+ */
+#ifdef CONFIG_BF527_SPORT0_PORTF
+#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
+#else
+#define LOCAL_SPORT0_TFS (0)
+#endif
+
+static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
+ P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
+ {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
+ P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
+
+static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ int ret = 0;
+
+ /* interface format:support I2S,slave mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ bf5xx_i2s.tcr1 |= TFSR | TCKFE;
+ bf5xx_i2s.rcr1 |= RFSR | RCKFE;
+ bf5xx_i2s.tcr2 |= TSFSE;
+ bf5xx_i2s.rcr2 |= RSFSE;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ bf5xx_i2s.tcr1 |= TFSR;
+ bf5xx_i2s.rcr1 |= RFSR;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ret = -EINVAL;
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ret = -EINVAL;
+ break;
+ default:
+ printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int bf5xx_i2s_startup(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s enter\n", __func__);
+
+ /*this counter is used for counting how many pcm streams are opened*/
+ bf5xx_i2s.counter++;
+ return 0;
+}
+
+static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int ret = 0;
+
+ bf5xx_i2s.tcr2 &= ~0x1f;
+ bf5xx_i2s.rcr2 &= ~0x1f;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bf5xx_i2s.tcr2 |= 15;
+ bf5xx_i2s.rcr2 |= 15;
+ sport_handle->wdsize = 2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bf5xx_i2s.tcr2 |= 23;
+ bf5xx_i2s.rcr2 |= 23;
+ sport_handle->wdsize = 3;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bf5xx_i2s.tcr2 |= 31;
+ bf5xx_i2s.rcr2 |= 31;
+ sport_handle->wdsize = 4;
+ break;
+ }
+
+ if (bf5xx_i2s.counter == 1) {
+ /*
+ * TX and RX are not independent,they are enabled at the
+ * same time, even if only one side is running. So, we
+ * need to configure both of them at the time when the first
+ * stream is opened.
+ *
+ * CPU DAI:slave mode.
+ */
+ ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1,
+ bf5xx_i2s.rcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1,
+ bf5xx_i2s.tcr2, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s enter\n", __func__);
+ bf5xx_i2s.counter--;
+}
+
+static int bf5xx_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s enter\n", __func__);
+ if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
+ pr_err("Requesting Peripherals failed\n");
+ return -EFAULT;
+ }
+
+ /* request DMA for SPORT */
+ sport_handle = sport_init(&sport_params[sport_num], 4, \
+ 2 * sizeof(u32), NULL);
+ if (!sport_handle) {
+ peripheral_free_list(&sport_req[sport_num][0]);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void bf5xx_i2s_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s enter\n", __func__);
+ peripheral_free_list(&sport_req[sport_num][0]);
+}
+
+#ifdef CONFIG_PM
+static int bf5xx_i2s_suspend(struct platform_device *dev,
+ struct snd_soc_dai *dai)
+{
+ struct sport_device *sport =
+ (struct sport_device *)dai->private_data;
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+ if (dai->capture.active)
+ sport_rx_stop(sport);
+ if (dai->playback.active)
+ sport_tx_stop(sport);
+ return 0;
+}
+
+static int bf5xx_i2s_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct sport_device *sport =
+ (struct sport_device *)dai->private_data;
+
+ pr_debug("%s : sport %d\n", __func__, dai->id);
+ if (!dai->active)
+ return 0;
+
+ ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0);
+ if (ret) {
+ pr_err("SPORT is busy!\n");
+ return -EBUSY;
+ }
+
+ if (dai->capture.active)
+ sport_rx_start(sport);
+ if (dai->playback.active)
+ sport_tx_start(sport);
+ return 0;
+}
+
+#else
+#define bf5xx_i2s_suspend NULL
+#define bf5xx_i2s_resume NULL
+#endif
+
+#define BF5XX_I2S_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_96000)
+
+#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai bf5xx_i2s_dai = {
+ .name = "bf5xx-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .probe = bf5xx_i2s_probe,
+ .remove = bf5xx_i2s_remove,
+ .suspend = bf5xx_i2s_suspend,
+ .resume = bf5xx_i2s_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BF5XX_I2S_RATES,
+ .formats = BF5XX_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BF5XX_I2S_RATES,
+ .formats = BF5XX_I2S_FORMATS,},
+ .ops = {
+ .startup = bf5xx_i2s_startup,
+ .shutdown = bf5xx_i2s_shutdown,
+ .hw_params = bf5xx_i2s_hw_params,},
+ .dai_ops = {
+ .set_fmt = bf5xx_i2s_set_dai_fmt,
+ },
+};
+EXPORT_SYMBOL_GPL(bf5xx_i2s_dai);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-i2s.h b/sound/soc/blackfin/bf5xx-i2s.h
new file mode 100644
index 0000000..7107d1a
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s.h
@@ -0,0 +1,14 @@
+/*
+ * linux/sound/arm/bf5xx-i2s.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_I2S_H
+#define _BF5XX_I2S_H
+
+extern struct snd_soc_dai bf5xx_i2s_dai;
+
+#endif
diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c
new file mode 100644
index 0000000..3b99e48
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-sport.c
@@ -0,0 +1,1032 @@
+/*
+ * File: bf5xx_sport.c
+ * Based on:
+ * Author: Roy Huang <roy.huang@analog.com>
+ *
+ * Created: Tue Sep 21 10:52:42 CEST 2004
+ * Description:
+ * Blackfin SPORT Driver
+ *
+ * Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/bug.h>
+#include <asm/portmux.h>
+#include <asm/dma.h>
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+
+#include "bf5xx-sport.h"
+/* delay between frame sync pulse and first data bit in multichannel mode */
+#define FRAME_DELAY (1<<12)
+
+struct sport_device *sport_handle;
+EXPORT_SYMBOL(sport_handle);
+/* note: multichannel is in units of 8 channels,
+ * tdm_count is # channels NOT / 8 ! */
+int sport_set_multichannel(struct sport_device *sport,
+ int tdm_count, u32 mask, int packed)
+{
+ pr_debug("%s tdm_count=%d mask:0x%08x packed=%d\n", __func__,
+ tdm_count, mask, packed);
+
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ if (tdm_count & 0x7)
+ return -EINVAL;
+
+ if (tdm_count > 32)
+ return -EINVAL; /* Only support less than 32 channels now */
+
+ if (tdm_count) {
+ sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12;
+ sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \
+ (packed ? (MCDTXPE|MCDRXPE) : 0);
+
+ sport->regs->mtcs0 = mask;
+ sport->regs->mrcs0 = mask;
+ sport->regs->mtcs1 = 0;
+ sport->regs->mrcs1 = 0;
+ sport->regs->mtcs2 = 0;
+ sport->regs->mrcs2 = 0;
+ sport->regs->mtcs3 = 0;
+ sport->regs->mrcs3 = 0;
+ } else {
+ sport->regs->mcmc1 = 0;
+ sport->regs->mcmc2 = 0;
+
+ sport->regs->mtcs0 = 0;
+ sport->regs->mrcs0 = 0;
+ }
+
+ sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0;
+ sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_multichannel);
+
+int sport_config_rx(struct sport_device *sport, unsigned int rcr1,
+ unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ sport->regs->rcr1 = rcr1;
+ sport->regs->rcr2 = rcr2;
+ sport->regs->rclkdiv = clkdiv;
+ sport->regs->rfsdiv = fsdiv;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_rx);
+
+int sport_config_tx(struct sport_device *sport, unsigned int tcr1,
+ unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+ if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+ return -EBUSY;
+
+ sport->regs->tcr1 = tcr1;
+ sport->regs->tcr2 = tcr2;
+ sport->regs->tclkdiv = clkdiv;
+ sport->regs->tfsdiv = fsdiv;
+
+ SSYNC();
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_tx);
+
+static void setup_desc(struct dmasg *desc, void *buf, int fragcount,
+ size_t fragsize, unsigned int cfg,
+ unsigned int x_count, unsigned int ycount, size_t wdsize)
+{
+
+ int i;
+
+ for (i = 0; i < fragcount; ++i) {
+ desc[i].next_desc_addr = (unsigned long)&(desc[i + 1]);
+ desc[i].start_addr = (unsigned long)buf + i*fragsize;
+ desc[i].cfg = cfg;
+ desc[i].x_count = x_count;
+ desc[i].x_modify = wdsize;
+ desc[i].y_count = ycount;
+ desc[i].y_modify = wdsize;
+ }
+
+ /* make circular */
+ desc[fragcount-1].next_desc_addr = (unsigned long)desc;
+
+ pr_debug("setup desc: desc0=%p, next0=%lx, desc1=%p,"
+ "next1=%lx\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
+ &(desc[0]), desc[0].next_desc_addr,
+ &(desc[1]), desc[1].next_desc_addr,
+ desc[0].x_count, desc[0].y_count,
+ desc[0].start_addr, desc[0].cfg);
+}
+
+static int sport_start(struct sport_device *sport)
+{
+ enable_dma(sport->dma_rx_chan);
+ enable_dma(sport->dma_tx_chan);
+ sport->regs->rcr1 |= RSPEN;
+ sport->regs->tcr1 |= TSPEN;
+ SSYNC();
+
+ return 0;
+}
+
+static int sport_stop(struct sport_device *sport)
+{
+ sport->regs->tcr1 &= ~TSPEN;
+ sport->regs->rcr1 &= ~RSPEN;
+ SSYNC();
+
+ disable_dma(sport->dma_rx_chan);
+ disable_dma(sport->dma_tx_chan);
+ return 0;
+}
+
+static inline int sport_hook_rx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc, temp_desc;
+ unsigned long flags;
+
+ BUG_ON(sport->dummy_rx_desc == NULL);
+ BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc);
+
+ /* Maybe the dummy buffer descriptor ring is damaged */
+ sport->dummy_rx_desc->next_desc_addr = \
+ (unsigned long)(sport->dummy_rx_desc+1);
+
+ local_irq_save(flags);
+ desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_rx_chan);
+ /* Copy the descriptor which will be damaged to backup */
+ temp_desc = *desc;
+ desc->x_count = 0xa;
+ desc->y_count = 0;
+ desc->next_desc_addr = (unsigned long)(sport->dummy_rx_desc);
+ local_irq_restore(flags);
+ /* Waiting for dummy buffer descriptor is already hooked*/
+ while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+ sizeof(struct dmasg)) !=
+ (unsigned long)sport->dummy_rx_desc)
+ ;
+ sport->curr_rx_desc = sport->dummy_rx_desc;
+ /* Restore the damaged descriptor */
+ *desc = temp_desc;
+
+ return 0;
+}
+
+static inline int sport_rx_dma_start(struct sport_device *sport, int dummy)
+{
+ if (dummy) {
+ sport->dummy_rx_desc->next_desc_addr = \
+ (unsigned long) sport->dummy_rx_desc;
+ sport->curr_rx_desc = sport->dummy_rx_desc;
+ } else
+ sport->curr_rx_desc = sport->dma_rx_desc;
+
+ set_dma_next_desc_addr(sport->dma_rx_chan, \
+ (unsigned long)(sport->curr_rx_desc));
+ set_dma_x_count(sport->dma_rx_chan, 0);
+ set_dma_x_modify(sport->dma_rx_chan, 0);
+ set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \
+ WDSIZE_32 | WNR));
+ set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr);
+ SSYNC();
+
+ return 0;
+}
+
+static inline int sport_tx_dma_start(struct sport_device *sport, int dummy)
+{
+ if (dummy) {
+ sport->dummy_tx_desc->next_desc_addr = \
+ (unsigned long) sport->dummy_tx_desc;
+ sport->curr_tx_desc = sport->dummy_tx_desc;
+ } else
+ sport->curr_tx_desc = sport->dma_tx_desc;
+
+ set_dma_next_desc_addr(sport->dma_tx_chan, \
+ (unsigned long)(sport->curr_tx_desc));
+ set_dma_x_count(sport->dma_tx_chan, 0);
+ set_dma_x_modify(sport->dma_tx_chan, 0);
+ set_dma_config(sport->dma_tx_chan,
+ (DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32));
+ set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr);
+ SSYNC();
+
+ return 0;
+}
+
+int sport_rx_start(struct sport_device *sport)
+{
+ unsigned long flags;
+ pr_debug("%s enter\n", __func__);
+ if (sport->rx_run)
+ return -EBUSY;
+ if (sport->tx_run) {
+ /* tx is running, rx is not running */
+ BUG_ON(sport->dma_rx_desc == NULL);
+ BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc);
+ local_irq_save(flags);
+ while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+ sizeof(struct dmasg)) !=
+ (unsigned long)sport->dummy_rx_desc)
+ ;
+ sport->dummy_rx_desc->next_desc_addr =
+ (unsigned long)(sport->dma_rx_desc);
+ local_irq_restore(flags);
+ sport->curr_rx_desc = sport->dma_rx_desc;
+ } else {
+ sport_tx_dma_start(sport, 1);
+ sport_rx_dma_start(sport, 0);
+ sport_start(sport);
+ }
+
+ sport->rx_run = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_rx_start);
+
+int sport_rx_stop(struct sport_device *sport)
+{
+ pr_debug("%s enter\n", __func__);
+
+ if (!sport->rx_run)
+ return 0;
+ if (sport->tx_run) {
+ /* TX dma is still running, hook the dummy buffer */
+ sport_hook_rx_dummy(sport);
+ } else {
+ /* Both rx and tx dma will be stopped */
+ sport_stop(sport);
+ sport->curr_rx_desc = NULL;
+ sport->curr_tx_desc = NULL;
+ }
+
+ sport->rx_run = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_rx_stop);
+
+static inline int sport_hook_tx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc, temp_desc;
+ unsigned long flags;
+
+ BUG_ON(sport->dummy_tx_desc == NULL);
+ BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc);
+
+ sport->dummy_tx_desc->next_desc_addr = \
+ (unsigned long)(sport->dummy_tx_desc+1);
+
+ /* Shorten the time on last normal descriptor */
+ local_irq_save(flags);
+ desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_tx_chan);
+ /* Store the descriptor which will be damaged */
+ temp_desc = *desc;
+ desc->x_count = 0xa;
+ desc->y_count = 0;
+ desc->next_desc_addr = (unsigned long)(sport->dummy_tx_desc);
+ local_irq_restore(flags);
+ /* Waiting for dummy buffer descriptor is already hooked*/
+ while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \
+ sizeof(struct dmasg)) != \
+ (unsigned long)sport->dummy_tx_desc)
+ ;
+ sport->curr_tx_desc = sport->dummy_tx_desc;
+ /* Restore the damaged descriptor */
+ *desc = temp_desc;
+
+ return 0;
+}
+
+int sport_tx_start(struct sport_device *sport)
+{
+ unsigned flags;
+ pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__,
+ sport->tx_run, sport->rx_run);
+ if (sport->tx_run)
+ return -EBUSY;
+ if (sport->rx_run) {
+ BUG_ON(sport->dma_tx_desc == NULL);
+ BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc);
+ /* Hook the normal buffer descriptor */
+ local_irq_save(flags);
+ while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) -
+ sizeof(struct dmasg)) !=
+ (unsigned long)sport->dummy_tx_desc)
+ ;
+ sport->dummy_tx_desc->next_desc_addr =
+ (unsigned long)(sport->dma_tx_desc);
+ local_irq_restore(flags);
+ sport->curr_tx_desc = sport->dma_tx_desc;
+ } else {
+
+ sport_tx_dma_start(sport, 0);
+ /* Let rx dma run the dummy buffer */
+ sport_rx_dma_start(sport, 1);
+ sport_start(sport);
+ }
+ sport->tx_run = 1;
+ return 0;
+}
+EXPORT_SYMBOL(sport_tx_start);
+
+int sport_tx_stop(struct sport_device *sport)
+{
+ if (!sport->tx_run)
+ return 0;
+ if (sport->rx_run) {
+ /* RX is still running, hook the dummy buffer */
+ sport_hook_tx_dummy(sport);
+ } else {
+ /* Both rx and tx dma stopped */
+ sport_stop(sport);
+ sport->curr_rx_desc = NULL;
+ sport->curr_tx_desc = NULL;
+ }
+
+ sport->tx_run = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_tx_stop);
+
+static inline int compute_wdsize(size_t wdsize)
+{
+ switch (wdsize) {
+ case 1:
+ return WDSIZE_8;
+ case 2:
+ return WDSIZE_16;
+ case 4:
+ default:
+ return WDSIZE_32;
+ }
+}
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize)
+{
+ unsigned int x_count;
+ unsigned int y_count;
+ unsigned int cfg;
+ dma_addr_t addr;
+
+ pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \
+ buf, fragcount, fragsize);
+
+ x_count = fragsize / sport->wdsize;
+ y_count = 0;
+
+ /* for fragments larger than 64k words we use 2d dma,
+ * denote fragecount as two numbers' mutliply and both of them
+ * are less than 64k.*/
+ if (x_count >= 0x10000) {
+ int i, count = x_count;
+
+ for (i = 16; i > 0; i--) {
+ x_count = 1 << i;
+ if ((count & (x_count - 1)) == 0) {
+ y_count = count >> i;
+ if (y_count < 0x10000)
+ break;
+ }
+ }
+ if (i == 0)
+ return -EINVAL;
+ }
+ pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__,
+ x_count, y_count);
+
+ if (sport->dma_rx_desc)
+ dma_free_coherent(NULL, sport->rx_desc_bytes,
+ sport->dma_rx_desc, 0);
+
+ /* Allocate a new descritor ring as current one. */
+ sport->dma_rx_desc = dma_alloc_coherent(NULL, \
+ fragcount * sizeof(struct dmasg), &addr, 0);
+ sport->rx_desc_bytes = fragcount * sizeof(struct dmasg);
+
+ if (!sport->dma_rx_desc) {
+ pr_err("Failed to allocate memory for rx desc\n");
+ return -ENOMEM;
+ }
+
+ sport->rx_buf = buf;
+ sport->rx_fragsize = fragsize;
+ sport->rx_frags = fragcount;
+
+ cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \
+ (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+ if (y_count != 0)
+ cfg |= DMA2D;
+
+ setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize,
+ cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_rx_dma);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf, \
+ int fragcount, size_t fragsize)
+{
+ unsigned int x_count;
+ unsigned int y_count;
+ unsigned int cfg;
+ dma_addr_t addr;
+
+ pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n",
+ __func__, buf, fragcount, fragsize);
+
+ x_count = fragsize/sport->wdsize;
+ y_count = 0;
+
+ /* for fragments larger than 64k words we use 2d dma,
+ * denote fragecount as two numbers' mutliply and both of them
+ * are less than 64k.*/
+ if (x_count >= 0x10000) {
+ int i, count = x_count;
+
+ for (i = 16; i > 0; i--) {
+ x_count = 1 << i;
+ if ((count & (x_count - 1)) == 0) {
+ y_count = count >> i;
+ if (y_count < 0x10000)
+ break;
+ }
+ }
+ if (i == 0)
+ return -EINVAL;
+ }
+ pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__,
+ x_count, y_count);
+
+
+ if (sport->dma_tx_desc) {
+ dma_free_coherent(NULL, sport->tx_desc_bytes, \
+ sport->dma_tx_desc, 0);
+ }
+
+ sport->dma_tx_desc = dma_alloc_coherent(NULL, \
+ fragcount * sizeof(struct dmasg), &addr, 0);
+ sport->tx_desc_bytes = fragcount * sizeof(struct dmasg);
+ if (!sport->dma_tx_desc) {
+ pr_err("Failed to allocate memory for tx desc\n");
+ return -ENOMEM;
+ }
+
+ sport->tx_buf = buf;
+ sport->tx_fragsize = fragsize;
+ sport->tx_frags = fragcount;
+ cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \
+ (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+ if (y_count != 0)
+ cfg |= DMA2D;
+
+ setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize,
+ cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_config_tx_dma);
+
+/* setup dummy dma descriptor ring, which don't generate interrupts,
+ * the x_modify is set to 0 */
+static int sport_config_rx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc;
+ unsigned config;
+
+ pr_debug("%s entered\n", __func__);
+#if L1_DATA_A_LENGTH != 0
+ desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc));
+#else
+ {
+ dma_addr_t addr;
+ desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ }
+#endif
+ if (desc == NULL) {
+ pr_err("Failed to allocate memory for dummy rx desc\n");
+ return -ENOMEM;
+ }
+ memset(desc, 0, 2 * sizeof(*desc));
+ sport->dummy_rx_desc = desc;
+ desc->start_addr = (unsigned long)sport->dummy_buf;
+ config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize)
+ | WNR | DMAEN;
+ desc->cfg = config;
+ desc->x_count = sport->dummy_count/sport->wdsize;
+ desc->x_modify = sport->wdsize;
+ desc->y_count = 0;
+ desc->y_modify = 0;
+ memcpy(desc+1, desc, sizeof(*desc));
+ desc->next_desc_addr = (unsigned long)(desc+1);
+ desc[1].next_desc_addr = (unsigned long)desc;
+ return 0;
+}
+
+static int sport_config_tx_dummy(struct sport_device *sport)
+{
+ struct dmasg *desc;
+ unsigned int config;
+
+ pr_debug("%s entered\n", __func__);
+
+#if L1_DATA_A_LENGTH != 0
+ desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc));
+#else
+ {
+ dma_addr_t addr;
+ desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ }
+#endif
+ if (!desc) {
+ pr_err("Failed to allocate memory for dummy tx desc\n");
+ return -ENOMEM;
+ }
+ memset(desc, 0, 2 * sizeof(*desc));
+ sport->dummy_tx_desc = desc;
+ desc->start_addr = (unsigned long)sport->dummy_buf + \
+ sport->dummy_count;
+ config = DMAFLOW_LARGE | NDSIZE_9 |
+ compute_wdsize(sport->wdsize) | DMAEN;
+ desc->cfg = config;
+ desc->x_count = sport->dummy_count/sport->wdsize;
+ desc->x_modify = sport->wdsize;
+ desc->y_count = 0;
+ desc->y_modify = 0;
+ memcpy(desc+1, desc, sizeof(*desc));
+ desc->next_desc_addr = (unsigned long)(desc+1);
+ desc[1].next_desc_addr = (unsigned long)desc;
+ return 0;
+}
+
+unsigned long sport_curr_offset_rx(struct sport_device *sport)
+{
+ unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan);
+
+ return (unsigned char *)curr - sport->rx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_rx);
+
+unsigned long sport_curr_offset_tx(struct sport_device *sport)
+{
+ unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan);
+
+ return (unsigned char *)curr - sport->tx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_tx);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx)
+{
+ ++(*frag);
+ if (tx == 1 && *frag == sport->tx_frags)
+ *frag = 0;
+
+ if (tx == 0 && *frag == sport->rx_frags)
+ *frag = 0;
+}
+EXPORT_SYMBOL(sport_incfrag);
+
+void sport_decfrag(struct sport_device *sport, int *frag, int tx)
+{
+ --(*frag);
+ if (tx == 1 && *frag == 0)
+ *frag = sport->tx_frags;
+
+ if (tx == 0 && *frag == 0)
+ *frag = sport->rx_frags;
+}
+EXPORT_SYMBOL(sport_decfrag);
+
+static int sport_check_status(struct sport_device *sport,
+ unsigned int *sport_stat,
+ unsigned int *rx_stat,
+ unsigned int *tx_stat)
+{
+ int status = 0;
+
+ if (sport_stat) {
+ SSYNC();
+ status = sport->regs->stat;
+ if (status & (TOVF|TUVF|ROVF|RUVF))
+ sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+ SSYNC();
+ *sport_stat = status;
+ }
+
+ if (rx_stat) {
+ SSYNC();
+ status = get_dma_curr_irqstat(sport->dma_rx_chan);
+ if (status & (DMA_DONE|DMA_ERR))
+ clear_dma_irqstat(sport->dma_rx_chan);
+ SSYNC();
+ *rx_stat = status;
+ }
+
+ if (tx_stat) {
+ SSYNC();
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ if (status & (DMA_DONE|DMA_ERR))
+ clear_dma_irqstat(sport->dma_tx_chan);
+ SSYNC();
+ *tx_stat = status;
+ }
+
+ return 0;
+}
+
+int sport_dump_stat(struct sport_device *sport, char *buf, size_t len)
+{
+ int ret;
+
+ ret = snprintf(buf, len,
+ "sts: 0x%04x\n"
+ "rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n",
+ sport->regs->stat,
+ sport->dma_rx_chan,
+ get_dma_curr_irqstat(sport->dma_rx_chan),
+ sport->dma_tx_chan,
+ get_dma_curr_irqstat(sport->dma_tx_chan));
+ buf += ret;
+ len -= ret;
+
+ ret += snprintf(buf, len,
+ "curr_rx_desc:0x%p, curr_tx_desc:0x%p\n"
+ "dma_rx_desc:0x%p, dma_tx_desc:0x%p\n"
+ "dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n",
+ sport->curr_rx_desc, sport->curr_tx_desc,
+ sport->dma_rx_desc, sport->dma_tx_desc,
+ sport->dummy_rx_desc, sport->dummy_tx_desc);
+
+ return ret;
+}
+
+static irqreturn_t rx_handler(int irq, void *dev_id)
+{
+ unsigned int rx_stat;
+ struct sport_device *sport = dev_id;
+
+ pr_debug("%s enter\n", __func__);
+ sport_check_status(sport, NULL, &rx_stat, NULL);
+ if (!(rx_stat & DMA_DONE))
+ pr_err("rx dma is already stopped\n");
+
+ if (sport->rx_callback) {
+ sport->rx_callback(sport->rx_data);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t tx_handler(int irq, void *dev_id)
+{
+ unsigned int tx_stat;
+ struct sport_device *sport = dev_id;
+ pr_debug("%s enter\n", __func__);
+ sport_check_status(sport, NULL, NULL, &tx_stat);
+ if (!(tx_stat & DMA_DONE)) {
+ pr_err("tx dma is already stopped\n");
+ return IRQ_HANDLED;
+ }
+ if (sport->tx_callback) {
+ sport->tx_callback(sport->tx_data);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t err_handler(int irq, void *dev_id)
+{
+ unsigned int status = 0;
+ struct sport_device *sport = dev_id;
+
+ pr_debug("%s\n", __func__);
+ if (sport_check_status(sport, &status, NULL, NULL)) {
+ pr_err("error checking status ??");
+ return IRQ_NONE;
+ }
+
+ if (status & (TOVF|TUVF|ROVF|RUVF)) {
+ pr_info("sport status error:%s%s%s%s\n",
+ status & TOVF ? " TOVF" : "",
+ status & TUVF ? " TUVF" : "",
+ status & ROVF ? " ROVF" : "",
+ status & RUVF ? " RUVF" : "");
+ if (status & TOVF || status & TUVF) {
+ disable_dma(sport->dma_tx_chan);
+ if (sport->tx_run)
+ sport_tx_dma_start(sport, 0);
+ else
+ sport_tx_dma_start(sport, 1);
+ enable_dma(sport->dma_tx_chan);
+ } else {
+ disable_dma(sport->dma_rx_chan);
+ if (sport->rx_run)
+ sport_rx_dma_start(sport, 0);
+ else
+ sport_rx_dma_start(sport, 1);
+ enable_dma(sport->dma_rx_chan);
+ }
+ }
+ status = sport->regs->stat;
+ if (status & (TOVF|TUVF|ROVF|RUVF))
+ sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+ SSYNC();
+
+ if (sport->err_callback)
+ sport->err_callback(sport->err_data);
+
+ return IRQ_HANDLED;
+}
+
+int sport_set_rx_callback(struct sport_device *sport,
+ void (*rx_callback)(void *), void *rx_data)
+{
+ BUG_ON(rx_callback == NULL);
+ sport->rx_callback = rx_callback;
+ sport->rx_data = rx_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_rx_callback);
+
+int sport_set_tx_callback(struct sport_device *sport,
+ void (*tx_callback)(void *), void *tx_data)
+{
+ BUG_ON(tx_callback == NULL);
+ sport->tx_callback = tx_callback;
+ sport->tx_data = tx_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_tx_callback);
+
+int sport_set_err_callback(struct sport_device *sport,
+ void (*err_callback)(void *), void *err_data)
+{
+ BUG_ON(err_callback == NULL);
+ sport->err_callback = err_callback;
+ sport->err_data = err_data;
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_set_err_callback);
+
+struct sport_device *sport_init(struct sport_param *param, unsigned wdsize,
+ unsigned dummy_count, void *private_data)
+{
+ int ret;
+ struct sport_device *sport;
+ pr_debug("%s enter\n", __func__);
+ BUG_ON(param == NULL);
+ BUG_ON(wdsize == 0 || dummy_count == 0);
+ sport = kmalloc(sizeof(struct sport_device), GFP_KERNEL);
+ if (!sport) {
+ pr_err("Failed to allocate for sport device\n");
+ return NULL;
+ }
+
+ memset(sport, 0, sizeof(struct sport_device));
+ sport->dma_rx_chan = param->dma_rx_chan;
+ sport->dma_tx_chan = param->dma_tx_chan;
+ sport->err_irq = param->err_irq;
+ sport->regs = param->regs;
+ sport->private_data = private_data;
+
+ if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) {
+ pr_err("Failed to request RX dma %d\n", \
+ sport->dma_rx_chan);
+ goto __init_err1;
+ }
+ if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) {
+ pr_err("Failed to request RX irq %d\n", \
+ sport->dma_rx_chan);
+ goto __init_err2;
+ }
+
+ if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) {
+ pr_err("Failed to request TX dma %d\n", \
+ sport->dma_tx_chan);
+ goto __init_err2;
+ }
+
+ if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) {
+ pr_err("Failed to request TX irq %d\n", \
+ sport->dma_tx_chan);
+ goto __init_err3;
+ }
+
+ if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err",
+ sport) < 0) {
+ pr_err("Failed to request err irq:%d\n", \
+ sport->err_irq);
+ goto __init_err3;
+ }
+
+ pr_err("dma rx:%d tx:%d, err irq:%d, regs:%p\n",
+ sport->dma_rx_chan, sport->dma_tx_chan,
+ sport->err_irq, sport->regs);
+
+ sport->wdsize = wdsize;
+ sport->dummy_count = dummy_count;
+
+#if L1_DATA_A_LENGTH != 0
+ sport->dummy_buf = l1_data_sram_alloc(dummy_count * 2);
+#else
+ sport->dummy_buf = kmalloc(dummy_count * 2, GFP_KERNEL);
+#endif
+ if (sport->dummy_buf == NULL) {
+ pr_err("Failed to allocate dummy buffer\n");
+ goto __error;
+ }
+
+ memset(sport->dummy_buf, 0, dummy_count * 2);
+ ret = sport_config_rx_dummy(sport);
+ if (ret) {
+ pr_err("Failed to config rx dummy ring\n");
+ goto __error;
+ }
+ ret = sport_config_tx_dummy(sport);
+ if (ret) {
+ pr_err("Failed to config tx dummy ring\n");
+ goto __error;
+ }
+
+ return sport;
+__error:
+ free_irq(sport->err_irq, sport);
+__init_err3:
+ free_dma(sport->dma_tx_chan);
+__init_err2:
+ free_dma(sport->dma_rx_chan);
+__init_err1:
+ kfree(sport);
+ return NULL;
+}
+EXPORT_SYMBOL(sport_init);
+
+void sport_done(struct sport_device *sport)
+{
+ if (sport == NULL)
+ return;
+
+ sport_stop(sport);
+ if (sport->dma_rx_desc)
+ dma_free_coherent(NULL, sport->rx_desc_bytes,
+ sport->dma_rx_desc, 0);
+ if (sport->dma_tx_desc)
+ dma_free_coherent(NULL, sport->tx_desc_bytes,
+ sport->dma_tx_desc, 0);
+
+#if L1_DATA_A_LENGTH != 0
+ l1_data_sram_free(sport->dummy_rx_desc);
+ l1_data_sram_free(sport->dummy_tx_desc);
+ l1_data_sram_free(sport->dummy_buf);
+#else
+ dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+ sport->dummy_rx_desc, 0);
+ dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+ sport->dummy_tx_desc, 0);
+ kfree(sport->dummy_buf);
+#endif
+ free_dma(sport->dma_rx_chan);
+ free_dma(sport->dma_tx_chan);
+ free_irq(sport->err_irq, sport);
+
+ kfree(sport);
+ sport = NULL;
+}
+EXPORT_SYMBOL(sport_done);
+/*
+* It is only used to send several bytes when dma is not enabled
+ * sport controller is configured but not enabled.
+ * Multichannel cannot works with pio mode */
+/* Used by ac97 to write and read codec register */
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+ u8 *in_data, int len)
+{
+ unsigned short dma_config;
+ unsigned short status;
+ unsigned long flags;
+ unsigned long wait = 0;
+
+ pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \
+ __func__, out_data, in_data, len);
+ pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n"
+ "mcmc1:0x%04x, mcmc2:0x%04x\n",
+ sport->regs->tcr1, sport->regs->tcr2,
+ sport->regs->tclkdiv, sport->regs->tfsdiv,
+ sport->regs->mcmc1, sport->regs->mcmc2);
+ flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len));
+
+ /* Enable tx dma */
+ dma_config = (RESTART | WDSIZE_16 | DI_EN);
+ set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data);
+ set_dma_x_count(sport->dma_tx_chan, len/2);
+ set_dma_x_modify(sport->dma_tx_chan, 2);
+ set_dma_config(sport->dma_tx_chan, dma_config);
+ enable_dma(sport->dma_tx_chan);
+
+ if (in_data != NULL) {
+ invalidate_dcache_range((unsigned)in_data, \
+ (unsigned)(in_data + len));
+ /* Enable rx dma */
+ dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN);
+ set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data);
+ set_dma_x_count(sport->dma_rx_chan, len/2);
+ set_dma_x_modify(sport->dma_rx_chan, 2);
+ set_dma_config(sport->dma_rx_chan, dma_config);
+ enable_dma(sport->dma_rx_chan);
+ }
+
+ local_irq_save(flags);
+ sport->regs->tcr1 |= TSPEN;
+ sport->regs->rcr1 |= RSPEN;
+ SSYNC();
+
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ while (status & DMA_RUN) {
+ udelay(1);
+ status = get_dma_curr_irqstat(sport->dma_tx_chan);
+ pr_debug("DMA status:0x%04x\n", status);
+ if (wait++ > 100)
+ goto __over;
+ }
+ status = sport->regs->stat;
+ wait = 0;
+
+ while (!(status & TXHRE)) {
+ pr_debug("sport status:0x%04x\n", status);
+ udelay(1);
+ status = *(unsigned short *)&sport->regs->stat;
+ if (wait++ > 1000)
+ goto __over;
+ }
+ /* Wait for the last byte sent out */
+ udelay(20);
+ pr_debug("sport status:0x%04x\n", status);
+
+__over:
+ sport->regs->tcr1 &= ~TSPEN;
+ sport->regs->rcr1 &= ~RSPEN;
+ SSYNC();
+ disable_dma(sport->dma_tx_chan);
+ /* Clear the status */
+ clear_dma_irqstat(sport->dma_tx_chan);
+ if (in_data != NULL) {
+ disable_dma(sport->dma_rx_chan);
+ clear_dma_irqstat(sport->dma_rx_chan);
+ }
+ SSYNC();
+ local_irq_restore(flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(sport_send_and_recv);
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("SPORT driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h
new file mode 100644
index 0000000..fcadcc0
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-sport.h
@@ -0,0 +1,194 @@
+/*
+ * File: bf5xx_ac97_sport.h
+ * Based on:
+ * Author: Roy Huang <roy.huang@analog.com>
+ *
+ * Created:
+ * Description:
+ *
+ * Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __BF5XX_SPORT_H__
+#define __BF5XX_SPORT_H__
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <asm/dma.h>
+
+struct sport_register {
+ u16 tcr1; u16 reserved0;
+ u16 tcr2; u16 reserved1;
+ u16 tclkdiv; u16 reserved2;
+ u16 tfsdiv; u16 reserved3;
+ u32 tx;
+ u32 reserved_l0;
+ u32 rx;
+ u32 reserved_l1;
+ u16 rcr1; u16 reserved4;
+ u16 rcr2; u16 reserved5;
+ u16 rclkdiv; u16 reserved6;
+ u16 rfsdiv; u16 reserved7;
+ u16 stat; u16 reserved8;
+ u16 chnl; u16 reserved9;
+ u16 mcmc1; u16 reserved10;
+ u16 mcmc2; u16 reserved11;
+ u32 mtcs0;
+ u32 mtcs1;
+ u32 mtcs2;
+ u32 mtcs3;
+ u32 mrcs0;
+ u32 mrcs1;
+ u32 mrcs2;
+ u32 mrcs3;
+};
+
+#define DESC_ELEMENT_COUNT 9
+
+struct sport_device {
+ int dma_rx_chan;
+ int dma_tx_chan;
+ int err_irq;
+ struct sport_register *regs;
+
+ unsigned char *rx_buf;
+ unsigned char *tx_buf;
+ unsigned int rx_fragsize;
+ unsigned int tx_fragsize;
+ unsigned int rx_frags;
+ unsigned int tx_frags;
+ unsigned int wdsize;
+
+ /* for dummy dma transfer */
+ void *dummy_buf;
+ unsigned int dummy_count;
+
+ /* DMA descriptor ring head of current audio stream*/
+ struct dmasg *dma_rx_desc;
+ struct dmasg *dma_tx_desc;
+ unsigned int rx_desc_bytes;
+ unsigned int tx_desc_bytes;
+
+ unsigned int rx_run:1; /* rx is running */
+ unsigned int tx_run:1; /* tx is running */
+
+ struct dmasg *dummy_rx_desc;
+ struct dmasg *dummy_tx_desc;
+
+ struct dmasg *curr_rx_desc;
+ struct dmasg *curr_tx_desc;
+
+ int rx_curr_frag;
+ int tx_curr_frag;
+
+ unsigned int rcr1;
+ unsigned int rcr2;
+ int rx_tdm_count;
+
+ unsigned int tcr1;
+ unsigned int tcr2;
+ int tx_tdm_count;
+
+ void (*rx_callback)(void *data);
+ void *rx_data;
+ void (*tx_callback)(void *data);
+ void *tx_data;
+ void (*err_callback)(void *data);
+ void *err_data;
+ unsigned char *tx_dma_buf;
+ unsigned char *rx_dma_buf;
+#ifdef CONFIG_SND_MMAP_SUPPORT
+ dma_addr_t tx_dma_phy;
+ dma_addr_t rx_dma_phy;
+ int tx_pos;/*pcm sample count*/
+ int rx_pos;
+ unsigned int tx_buffer_size;
+ unsigned int rx_buffer_size;
+ int tx_delay_pos;
+ int once;
+#endif
+ void *private_data;
+};
+
+extern struct sport_device *sport_handle;
+
+struct sport_param {
+ int dma_rx_chan;
+ int dma_tx_chan;
+ int err_irq;
+ struct sport_register *regs;
+};
+
+struct sport_device *sport_init(struct sport_param *param, unsigned wdsize,
+ unsigned dummy_count, void *private_data);
+
+void sport_done(struct sport_device *sport);
+
+/* first use these ...*/
+
+/* note: multichannel is in units of 8 channels, tdm_count is number of channels
+ * NOT / 8 ! all channels are enabled by default */
+int sport_set_multichannel(struct sport_device *sport, int tdm_count,
+ u32 mask, int packed);
+
+int sport_config_rx(struct sport_device *sport,
+ unsigned int rcr1, unsigned int rcr2,
+ unsigned int clkdiv, unsigned int fsdiv);
+
+int sport_config_tx(struct sport_device *sport,
+ unsigned int tcr1, unsigned int tcr2,
+ unsigned int clkdiv, unsigned int fsdiv);
+
+/* ... then these: */
+
+/* buffer size (in bytes) == fragcount * fragsize_bytes */
+
+/* this is not a very general api, it sets the dma to 2d autobuffer mode */
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize_bytes);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf,
+ int fragcount, size_t fragsize_bytes);
+
+int sport_tx_start(struct sport_device *sport);
+int sport_tx_stop(struct sport_device *sport);
+int sport_rx_start(struct sport_device *sport);
+int sport_rx_stop(struct sport_device *sport);
+
+/* for use in interrupt handler */
+unsigned long sport_curr_offset_rx(struct sport_device *sport);
+unsigned long sport_curr_offset_tx(struct sport_device *sport);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx);
+void sport_decfrag(struct sport_device *sport, int *frag, int tx);
+
+int sport_set_rx_callback(struct sport_device *sport,
+ void (*rx_callback)(void *), void *rx_data);
+int sport_set_tx_callback(struct sport_device *sport,
+ void (*tx_callback)(void *), void *tx_data);
+int sport_set_err_callback(struct sport_device *sport,
+ void (*err_callback)(void *), void *err_data);
+
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+ u8 *in_data, int len);
+#endif /* BF53X_SPORT_H */
diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c
new file mode 100644
index 0000000..e15f67f
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-ssm2602.c
@@ -0,0 +1,186 @@
+/*
+ * File: sound/soc/blackfin/bf5xx-ssm2602.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: board driver for SSM2602 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/dma.h>
+#include <asm/portmux.h>
+#include <linux/gpio.h>
+#include "../codecs/ssm2602.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+#include "bf5xx-i2s.h"
+
+static struct snd_soc_machine bf5xx_ssm2602;
+
+static int bf5xx_ssm2602_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ pr_debug("%s enter\n", __func__);
+ cpu_dai->private_data = sport_handle;
+ return 0;
+}
+
+static int bf5xx_ssm2602_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+ params_format(params));
+ /*
+ * If you are using a crystal source which frequency is not 12MHz
+ * then modify the below case statement with frequency of the crystal.
+ *
+ * If you are using the SPORT to generate clocking then this is
+ * where to do it.
+ */
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 12000000;
+ break;
+ }
+
+ /*
+ * CODEC is master for BCLK and LRC in this configuration.
+ */
+
+ /* set codec DAI configuration */
+ ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+ /* set cpu DAI configuration */
+ ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = codec_dai->dai_ops.set_sysclk(codec_dai, SSM2602_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops bf5xx_ssm2602_ops = {
+ .startup = bf5xx_ssm2602_startup,
+ .hw_params = bf5xx_ssm2602_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ssm2602_dai = {
+ .name = "ssm2602",
+ .stream_name = "SSM2602",
+ .cpu_dai = &bf5xx_i2s_dai,
+ .codec_dai = &ssm2602_dai,
+ .ops = &bf5xx_ssm2602_ops,
+};
+
+/*
+ * SSM2602 2 wire address is determined by CSB
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static struct ssm2602_setup_data bf5xx_ssm2602_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1b,
+};
+
+static struct snd_soc_machine bf5xx_ssm2602 = {
+ .name = "bf5xx_ssm2602",
+ .dai_link = &bf5xx_ssm2602_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device bf5xx_ssm2602_snd_devdata = {
+ .machine = &bf5xx_ssm2602,
+ .platform = &bf5xx_i2s_soc_platform,
+ .codec_dev = &soc_codec_dev_ssm2602,
+ .codec_data = &bf5xx_ssm2602_setup,
+};
+
+static struct platform_device *bf52x_ssm2602_snd_device;
+
+static int __init bf5xx_ssm2602_init(void)
+{
+ int ret;
+
+ pr_debug("%s enter\n", __func__);
+ bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!bf52x_ssm2602_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(bf52x_ssm2602_snd_device,
+ &bf5xx_ssm2602_snd_devdata);
+ bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev;
+ ret = platform_device_add(bf52x_ssm2602_snd_device);
+
+ if (ret)
+ platform_device_put(bf52x_ssm2602_snd_device);
+
+ return ret;
+}
+
+static void __exit bf5xx_ssm2602_exit(void)
+{
+ pr_debug("%s enter\n", __func__);
+ platform_device_unregister(bf52x_ssm2602_snd_device);
+}
+
+module_init(bf5xx_ssm2602_init);
+module_exit(bf5xx_ssm2602_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
new file mode 100644
index 0000000..38a0e3b
--- /dev/null
+++ b/sound/soc/codecs/Kconfig
@@ -0,0 +1,112 @@
+config SND_SOC_ALL_CODECS
+ tristate "Build all ASoC CODEC drivers"
+ depends on I2C
+ select SPI
+ select SPI_MASTER
+ select SND_SOC_AD73311
+ select SND_SOC_AK4535
+ select SND_SOC_CS4270
+ select SND_SOC_SSM2602
+ select SND_SOC_TLV320AIC23
+ select SND_SOC_TLV320AIC26
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_UDA1380
+ select SND_SOC_WM8510
+ select SND_SOC_WM8580
+ select SND_SOC_WM8731
+ select SND_SOC_WM8750
+ select SND_SOC_WM8753
+ select SND_SOC_WM8900
+ select SND_SOC_WM8903
+ select SND_SOC_WM8971
+ select SND_SOC_WM8990
+ help
+ Normally ASoC codec drivers are only built if a machine driver which
+ uses them is also built since they are only usable with a machine
+ driver. Selecting this option will allow these drivers to be built
+ without an explicit machine driver for test and development purposes.
+
+ If unsure select "N".
+
+
+config SND_SOC_AC97_CODEC
+ tristate
+ select SND_AC97_CODEC
+
+config SND_SOC_AD1980
+ tristate
+
+config SND_SOC_AD73311
+ tristate
+
+config SND_SOC_AK4535
+ tristate
+
+# Cirrus Logic CS4270 Codec
+config SND_SOC_CS4270
+ tristate
+
+# Cirrus Logic CS4270 Codec Hardware Mute Support
+# Select if you have external muting circuitry attached to your CS4270.
+config SND_SOC_CS4270_HWMUTE
+ bool
+ depends on SND_SOC_CS4270
+
+# Cirrus Logic CS4270 Codec VD = 3.3V Errata
+# Select if you are affected by the errata where the part will not function
+# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
+# not select any sample rates that require MCLK to be divided by 1.5.
+config SND_SOC_CS4270_VD33_ERRATA
+ bool
+ depends on SND_SOC_CS4270
+
+config SND_SOC_SSM2602
+ tristate
+
+config SND_SOC_TLV320AIC23
+ tristate
+ depends on I2C
+
+config SND_SOC_TLV320AIC26
+ tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
+ depends on SPI
+
+config SND_SOC_TLV320AIC3X
+ tristate
+ depends on I2C
+
+config SND_SOC_UDA1380
+ tristate
+
+config SND_SOC_WM8510
+ tristate
+
+config SND_SOC_WM8580
+ tristate
+
+config SND_SOC_WM8731
+ tristate
+
+config SND_SOC_WM8750
+ tristate
+
+config SND_SOC_WM8753
+ tristate
+
+config SND_SOC_WM8900
+ tristate
+
+config SND_SOC_WM8903
+ tristate
+
+config SND_SOC_WM8971
+ tristate
+
+config SND_SOC_WM8990
+ tristate
+
+config SND_SOC_WM9712
+ tristate
+
+config SND_SOC_WM9713
+ tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
new file mode 100644
index 0000000..90f0a58
--- /dev/null
+++ b/sound/soc/codecs/Makefile
@@ -0,0 +1,43 @@
+snd-soc-ac97-objs := ac97.o
+snd-soc-ad1980-objs := ad1980.o
+snd-soc-ad73311-objs := ad73311.o
+snd-soc-ak4535-objs := ak4535.o
+snd-soc-cs4270-objs := cs4270.o
+snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-tlv320aic23-objs := tlv320aic23.o
+snd-soc-tlv320aic26-objs := tlv320aic26.o
+snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-uda1380-objs := uda1380.o
+snd-soc-wm8510-objs := wm8510.o
+snd-soc-wm8580-objs := wm8580.o
+snd-soc-wm8731-objs := wm8731.o
+snd-soc-wm8750-objs := wm8750.o
+snd-soc-wm8753-objs := wm8753.o
+snd-soc-wm8900-objs := wm8900.o
+snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm9712-objs := wm9712.o
+snd-soc-wm9713-objs := wm9713.o
+
+obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
+obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
+obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
+obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.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_WM8900) += snd-soc-wm8900.o
+obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
+obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
+obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c
new file mode 100644
index 0000000..bd1ebdc
--- /dev/null
+++ b/sound/soc/codecs/ac97.c
@@ -0,0 +1,179 @@
+/*
+ * ac97.c -- ALSA Soc AC97 codec support
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ *
+ * Generic AC97 support.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include "ac97.h"
+
+#define AC97_VERSION "0.6"
+
+static int ac97_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
+ return snd_ac97_set_rate(codec->ac97, reg, runtime->rate);
+}
+
+#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai ac97_dai = {
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = STD_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_prepare,},
+};
+EXPORT_SYMBOL_GPL(ac97_dai);
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return soc_ac97_ops.read(codec->ac97, reg);
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ return 0;
+}
+
+static int ac97_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct snd_ac97_bus *ac97_bus;
+ struct snd_ac97_template ac97_template;
+ int ret = 0;
+
+ printk(KERN_INFO "AC97 SoC Audio Codec %s\n", AC97_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (!socdev->codec)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->name = "AC97";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ac97_dai;
+ codec->num_dai = 1;
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto err;
+
+ /* add codec as bus device for standard ac97 */
+ ret = snd_ac97_bus(codec->card, 0, &soc_ac97_ops, NULL, &ac97_bus);
+ if (ret < 0)
+ goto bus_err;
+
+ memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
+ ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97);
+ if (ret < 0)
+ goto bus_err;
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0)
+ goto bus_err;
+ return 0;
+
+bus_err:
+ snd_soc_free_pcms(socdev);
+
+err:
+ kfree(socdev->codec->reg_cache);
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ac97_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (!codec)
+ return 0;
+
+ snd_soc_free_pcms(socdev);
+ kfree(socdev->codec->reg_cache);
+ kfree(socdev->codec);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_ac97_suspend(socdev->codec->ac97);
+
+ return 0;
+}
+
+static int ac97_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_ac97_resume(socdev->codec->ac97);
+
+ return 0;
+}
+#else
+#define ac97_soc_suspend NULL
+#define ac97_soc_resume NULL
+#endif
+
+struct snd_soc_codec_device soc_codec_dev_ac97 = {
+ .probe = ac97_soc_probe,
+ .remove = ac97_soc_remove,
+ .suspend = ac97_soc_suspend,
+ .resume = ac97_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ac97);
+
+MODULE_DESCRIPTION("Soc Generic AC97 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h
new file mode 100644
index 0000000..281aa42
--- /dev/null
+++ b/sound/soc/codecs/ac97.h
@@ -0,0 +1,19 @@
+/*
+ * linux/sound/codecs/ac97.h -- ALSA SoC Layer
+ *
+ * Author: Liam Girdwood
+ * Created: Dec 1st 2005
+ * Copyright: Wolfson Microelectronics. PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_SND_SOC_AC97_H
+#define __LINUX_SND_SOC_AC97_H
+
+extern struct snd_soc_codec_device soc_codec_dev_ac97;
+extern struct snd_soc_dai ac97_dai;
+
+#endif
diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c
new file mode 100644
index 0000000..1397b8e
--- /dev/null
+++ b/sound/soc/codecs/ad1980.c
@@ -0,0 +1,308 @@
+/*
+ * ad1980.c -- ALSA Soc AD1980 codec support
+ *
+ * Copyright: Analog Device Inc.
+ * Author: Roy Huang <roy.huang@analog.com>
+ * Cliff Cai <cliff.cai@analog.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "ad1980.h"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * AD1980 register cache
+ */
+static const u16 ad1980_reg[] = {
+ 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */
+ 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */
+ 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */
+ 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */
+ 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */
+ 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */
+ 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+ 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */
+ 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */
+};
+
+static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
+ "Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum ad1980_cap_src =
+ SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel);
+
+static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
+SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+
+SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
+SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
+SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
+
+SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+
+SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
+SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
+
+SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
+
+SOC_ENUM("Capture Source", ad1980_cap_src),
+
+SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
+};
+
+/* add non dapm controls */
+static int ad1980_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card, snd_soc_cnew(
+ &ad1980_snd_ac97_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ switch (reg) {
+ case AC97_RESET:
+ case AC97_INT_PAGING:
+ case AC97_POWERDOWN:
+ case AC97_EXTENDED_STATUS:
+ case AC97_VENDOR_ID1:
+ case AC97_VENDOR_ID2:
+ return soc_ac97_ops.read(codec->ac97, reg);
+ default:
+ reg = reg >> 1;
+
+ if (reg >= (ARRAY_SIZE(ad1980_reg)))
+ return -EINVAL;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg < (ARRAY_SIZE(ad1980_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+struct snd_soc_dai ad1980_dai = {
+ .name = "AC97",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(ad1980_dai);
+
+static int ad1980_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ u16 retry_cnt = 0;
+
+retry:
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, AC97_RESET) == 0x0090)
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ /* Set bit 16slot in register 74h, then every slot will has only 16
+ * bits. This command is sent out in 20bit mode, in which case the
+ * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/
+ ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900);
+
+ if (ac97_read(codec, AC97_RESET) != 0x0090)
+ goto err;
+ return 0;
+
+err:
+ while (retry_cnt++ < 10)
+ goto retry;
+
+ printk(KERN_ERR "AD1980 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int ad1980_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+ u16 vendor_id2;
+
+ printk(KERN_INFO "AD1980 SoC Audio Codec\n");
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache =
+ kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \
+ ARRAY_SIZE(ad1980_reg));
+ codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg);
+ codec->reg_cache_step = 2;
+ codec->name = "AD1980";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ad1980_dai;
+ codec->num_dai = 1;
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "ad1980: failed to register AC97 codec\n");
+ goto codec_err;
+ }
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+
+ ret = ad1980_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* Read out vendor ID to make sure it is ad1980 */
+ if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144)
+ goto reset_err;
+
+ vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2);
+
+ if (vendor_id2 != 0x5370) {
+ if (vendor_id2 != 0x5374)
+ goto reset_err;
+ else
+ printk(KERN_WARNING "ad1980: "
+ "Found AD1981 - only 2/2 IN/OUT Channels "
+ "supported\n");
+ }
+
+ ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */
+ ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */
+ ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */
+
+ ad1980_add_controls(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ad1980: failed to register card\n");
+ goto reset_err;
+ }
+
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ad1980_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->reg_cache);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ad1980 = {
+ .probe = ad1980_soc_probe,
+ .remove = ad1980_soc_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980);
+
+MODULE_DESCRIPTION("ASoC ad1980 driver");
+MODULE_AUTHOR("Roy Huang, Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h
new file mode 100644
index 0000000..db6c850
--- /dev/null
+++ b/sound/soc/codecs/ad1980.h
@@ -0,0 +1,23 @@
+/*
+ * ad1980.h -- ad1980 Soc Audio driver
+ */
+
+#ifndef _AD1980_H
+#define _AD1980_H
+/* Bit definition of Power-Down Control/Status Register */
+#define ADC 0x0001
+#define DAC 0x0002
+#define ANL 0x0004
+#define REF 0x0008
+#define PR0 0x0100
+#define PR1 0x0200
+#define PR2 0x0400
+#define PR3 0x0800
+#define PR4 0x1000
+#define PR5 0x2000
+#define PR6 0x4000
+
+extern struct snd_soc_dai ad1980_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ad1980;
+
+#endif
diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c
new file mode 100644
index 0000000..37af860
--- /dev/null
+++ b/sound/soc/codecs/ad73311.c
@@ -0,0 +1,107 @@
+/*
+ * ad73311.c -- ALSA Soc AD73311 codec support
+ *
+ * Copyright: Analog Device Inc.
+ * Author: Cliff Cai <cliff.cai@analog.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
+ * 25th Sep 2008 Initial version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "ad73311.h"
+
+struct snd_soc_dai ad73311_dai = {
+ .name = "AD73311",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(ad73311_dai);
+
+static int ad73311_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+ mutex_init(&codec->mutex);
+ codec->name = "AD73311";
+ codec->owner = THIS_MODULE;
+ codec->dai = &ad73311_dai;
+ codec->num_dai = 1;
+ socdev->codec = codec;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "ad73311: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ad73311: failed to register card\n");
+ goto register_err;
+ }
+
+ return ret;
+
+register_err:
+ snd_soc_free_pcms(socdev);
+pcm_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int ad73311_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+ snd_soc_free_pcms(socdev);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ad73311 = {
+ .probe = ad73311_soc_probe,
+ .remove = ad73311_soc_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311);
+
+MODULE_DESCRIPTION("ASoC ad73311 driver");
+MODULE_AUTHOR("Cliff Cai ");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h
new file mode 100644
index 0000000..507ce0c
--- /dev/null
+++ b/sound/soc/codecs/ad73311.h
@@ -0,0 +1,90 @@
+/*
+ * File: sound/soc/codec/ad73311.h
+ * Based on:
+ * Author: Cliff Cai <cliff.cai@analog.com>
+ *
+ * Created: Thur Sep 25, 2008
+ * Description: definitions for AD73311 registers
+ *
+ *
+ * Modified:
+ * Copyright 2006 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __AD73311_H__
+#define __AD73311_H__
+
+#define AD_CONTROL 0x8000
+#define AD_DATA 0x0000
+#define AD_READ 0x4000
+#define AD_WRITE 0x0000
+
+/* Control register A */
+#define CTRL_REG_A (0 << 8)
+
+#define REGA_MODE_PRO 0x00
+#define REGA_MODE_DATA 0x01
+#define REGA_MODE_MIXED 0x03
+#define REGA_DLB 0x04
+#define REGA_SLB 0x08
+#define REGA_DEVC(x) ((x & 0x7) << 4)
+#define REGA_RESET 0x80
+
+/* Control register B */
+#define CTRL_REG_B (1 << 8)
+
+#define REGB_DIRATE(x) (x & 0x3)
+#define REGB_SCDIV(x) ((x & 0x3) << 2)
+#define REGB_MCDIV(x) ((x & 0x7) << 4)
+#define REGB_CEE (1 << 7)
+
+/* Control register C */
+#define CTRL_REG_C (2 << 8)
+
+#define REGC_PUDEV (1 << 0)
+#define REGC_PUADC (1 << 3)
+#define REGC_PUDAC (1 << 4)
+#define REGC_PUREF (1 << 5)
+#define REGC_REFUSE (1 << 6)
+
+/* Control register D */
+#define CTRL_REG_D (3 << 8)
+
+#define REGD_IGS(x) (x & 0x7)
+#define REGD_RMOD (1 << 3)
+#define REGD_OGS(x) ((x & 0x7) << 4)
+#define REGD_MUTE (x << 7)
+
+/* Control register E */
+#define CTRL_REG_E (4 << 8)
+
+#define REGE_DA(x) (x & 0x1f)
+#define REGE_IBYP (1 << 5)
+
+/* Control register F */
+#define CTRL_REG_F (5 << 8)
+
+#define REGF_SEEN (1 << 5)
+#define REGF_INV (1 << 6)
+#define REGF_ALB (1 << 7)
+
+extern struct snd_soc_dai ad73311_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ad73311;
+#endif
diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c
new file mode 100644
index 0000000..2a89b58
--- /dev/null
+++ b/sound/soc/codecs/ak4535.c
@@ -0,0 +1,694 @@
+/*
+ * ak4535.c -- AK4535 ALSA Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "ak4535.h"
+
+#define AK4535_VERSION "0.3"
+
+struct snd_soc_codec_device soc_codec_dev_ak4535;
+
+/* codec private data */
+struct ak4535_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * ak4535 register cache
+ */
+static const u16 ak4535_reg[AK4535_CACHEREGNUM] = {
+ 0x0000, 0x0080, 0x0000, 0x0003,
+ 0x0002, 0x0000, 0x0011, 0x0001,
+ 0x0000, 0x0040, 0x0036, 0x0010,
+ 0x0000, 0x0000, 0x0057, 0x0000,
+};
+
+/*
+ * read ak4535 register cache
+ */
+static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+static inline unsigned int ak4535_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 data;
+ data = reg;
+
+ if (codec->hw_write(codec->control_data, &data, 1) != 1)
+ return -EIO;
+
+ if (codec->hw_read(codec->control_data, &data, 1) != 1)
+ return -EIO;
+
+ return data;
+};
+
+/*
+ * write ak4535 register cache
+ */
+static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= AK4535_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the AK4535 register space
+ */
+static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 AK4535 register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ ak4535_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int ak4535_sync(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i, r = 0;
+
+ for (i = 0; i < AK4535_CACHEREGNUM; i++)
+ r |= ak4535_write(codec, i, cache[i]);
+
+ return r;
+};
+
+static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"};
+static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4535_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};
+static const char *ak4535_mic_select[] = {"Internal", "External"};
+
+static const struct soc_enum ak4535_enum[] = {
+ SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain),
+ SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out),
+ SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out),
+ SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp),
+ SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select),
+};
+
+static const struct snd_kcontrol_new ak4535_snd_controls[] = {
+ SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0),
+ SOC_ENUM("Mono 1 Output", ak4535_enum[1]),
+ SOC_ENUM("Mono 1 Gain", ak4535_enum[0]),
+ SOC_ENUM("Headphone Output", ak4535_enum[2]),
+ SOC_ENUM("Playback Deemphasis", ak4535_enum[3]),
+ SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0),
+ SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0),
+ SOC_ENUM("Mic Select", ak4535_enum[4]),
+ SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0),
+ SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0),
+ SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0),
+ SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0),
+ SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0),
+ SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1),
+ SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1),
+ SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0),
+ SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0),
+};
+
+/* add non dapm controls */
+static int ak4535_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0),
+ SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0),
+ SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4535_input_mux_control =
+ SOC_DAPM_ENUM("Input Select", ak4535_enum[4]);
+
+/* HP L switch */
+static const struct snd_kcontrol_new ak4535_hpl_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1);
+
+/* HP R switch */
+static const struct snd_kcontrol_new ak4535_hpr_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4535_mono2_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0);
+
+/* Line out switch */
+static const struct snd_kcontrol_new ak4535_line_control =
+ SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0);
+
+/* ak4535 dapm widgets */
+static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4535_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono1_mixer_controls[0],
+ ARRAY_SIZE(ak4535_mono1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mixer_controls[0],
+ ARRAY_SIZE(ak4535_input_mixer_controls)),
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &ak4535_input_mux_control),
+ SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0),
+ SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_mono2_control),
+ /* speaker powersave bit */
+ SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_line_control),
+ SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpl_control),
+ SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0,
+ &ak4535_hpr_control),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPP"),
+ SND_SOC_DAPM_OUTPUT("SPN"),
+ SND_SOC_DAPM_OUTPUT("MOUT1"),
+ SND_SOC_DAPM_OUTPUT("MOUT2"),
+ SND_SOC_DAPM_OUTPUT("MICOUT"),
+ SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0),
+ SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0),
+ SND_SOC_DAPM_INPUT("MICIN"),
+ SND_SOC_DAPM_INPUT("MICEXT"),
+ SND_SOC_DAPM_INPUT("AUX"),
+ SND_SOC_DAPM_INPUT("MIN"),
+ SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /*stereo mixer */
+ {"Stereo Mixer", "Playback Switch", "DAC"},
+ {"Stereo Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+ /* mono1 mixer */
+ {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"},
+ {"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+ /* Mic */
+ {"Mic", NULL, "AIN"},
+ {"Input Mux", "Internal", "Mic Int Bias"},
+ {"Input Mux", "External", "Mic Ext Bias"},
+ {"Mic Int Bias", NULL, "MICIN"},
+ {"Mic Ext Bias", NULL, "MICEXT"},
+ {"MICOUT", NULL, "Input Mux"},
+
+ /* line out */
+ {"LOUT", NULL, "Line Out Enable"},
+ {"ROUT", NULL, "Line Out Enable"},
+ {"Line Out Enable", "Switch", "Line Out"},
+ {"Line Out", NULL, "Stereo Mixer"},
+
+ /* mono1 out */
+ {"MOUT1", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono1 Mixer"},
+
+ /* left HP */
+ {"HPL", NULL, "Left HP Enable"},
+ {"Left HP Enable", "Switch", "HP L Amp"},
+ {"HP L Amp", NULL, "Stereo Mixer"},
+
+ /* right HP */
+ {"HPR", NULL, "Right HP Enable"},
+ {"Right HP Enable", "Switch", "HP R Amp"},
+ {"HP R Amp", NULL, "Stereo Mixer"},
+
+ /* speaker */
+ {"SPP", NULL, "Speaker Enable"},
+ {"SPN", NULL, "Speaker Enable"},
+ {"Speaker Enable", "Switch", "Spk Amp"},
+ {"Spk Amp", NULL, "MIN"},
+
+ /* mono 2 */
+ {"MOUT2", NULL, "Mono 2 Enable"},
+ {"Mono 2 Enable", "Switch", "Stereo Mixer"},
+
+ /* Aux In */
+ {"Aux In", NULL, "AUX"},
+
+ /* ADC */
+ {"ADC", NULL, "Input Mixer"},
+ {"Input Mixer", "Mic Capture Switch", "Mic"},
+ {"Input Mixer", "Aux Capture Switch", "Aux In"},
+};
+
+static int ak4535_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets,
+ ARRAY_SIZE(ak4535_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ak4535_priv *ak4535 = codec->private_data;
+
+ ak4535->sysclk = freq;
+ return 0;
+}
+
+static int ak4535_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ak4535_priv *ak4535 = codec->private_data;
+ u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5);
+ int rate = params_rate(params), fs = 256;
+
+ if (rate)
+ fs = ak4535->sysclk / rate;
+
+ /* set fs */
+ switch (fs) {
+ case 1024:
+ mode2 |= (0x2 << 5);
+ break;
+ case 512:
+ mode2 |= (0x1 << 5);
+ break;
+ case 256:
+ break;
+ }
+
+ /* set rate */
+ ak4535_write(codec, AK4535_MODE2, mode2);
+ return 0;
+}
+
+static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 mode1 = 0;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ mode1 = 0x0002;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ mode1 = 0x0001;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* use 32 fs for BCLK to save power */
+ mode1 |= 0x4;
+
+ ak4535_write(codec, AK4535_MODE1, mode1);
+ return 0;
+}
+
+static int ak4535_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf;
+ if (!mute)
+ ak4535_write(codec, AK4535_DAC, mute_reg);
+ else
+ ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+ return 0;
+}
+
+static int ak4535_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ ak4535_mute(codec->dai, 0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ ak4535_mute(codec->dai, 1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i | 0x80);
+ i = ak4535_read_reg_cache(codec, AK4535_PM2);
+ ak4535_write(codec, AK4535_PM2, i & (~0x80));
+ break;
+ case SND_SOC_BIAS_OFF:
+ i = ak4535_read_reg_cache(codec, AK4535_PM1);
+ ak4535_write(codec, AK4535_PM1, i & (~0x80));
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai ak4535_dai = {
+ .name = "AK4535",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4535_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = ak4535_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = ak4535_set_dai_fmt,
+ .digital_mute = ak4535_mute,
+ .set_sysclk = ak4535_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(ak4535_dai);
+
+static int ak4535_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak4535_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ ak4535_sync(codec);
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ak4535_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the AK4535 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int ak4535_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "AK4535";
+ codec->owner = THIS_MODULE;
+ codec->read = ak4535_read_reg_cache;
+ codec->write = ak4535_write;
+ codec->set_bias_level = ak4535_set_bias_level;
+ codec->dai = &ak4535_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(ak4535_reg);
+ codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "ak4535: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ ak4535_add_controls(codec);
+ ak4535_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ak4535: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+
+ return ret;
+}
+
+static struct snd_soc_device *ak4535_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static int ak4535_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = ak4535_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = ak4535_init(socdev);
+ if (ret < 0)
+ printk(KERN_ERR "failed to initialise AK4535\n");
+
+ return ret;
+}
+
+static int ak4535_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id ak4535_i2c_id[] = {
+ { "ak4535", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id);
+
+static struct i2c_driver ak4535_i2c_driver = {
+ .driver = {
+ .name = "AK4535 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4535_i2c_probe,
+ .remove = ak4535_i2c_remove,
+ .id_table = ak4535_i2c_id,
+};
+
+static int ak4535_add_i2c_device(struct platform_device *pdev,
+ const struct ak4535_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&ak4535_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "ak4535", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&ak4535_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int ak4535_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct ak4535_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct ak4535_priv *ak4535;
+ int ret;
+
+ printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL);
+ if (ak4535 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = ak4535;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ak4535_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->hw_read = (hw_read_t)i2c_master_recv;
+ ret = ak4535_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int ak4535_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&ak4535_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ak4535 = {
+ .probe = ak4535_probe,
+ .remove = ak4535_remove,
+ .suspend = ak4535_suspend,
+ .resume = ak4535_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535);
+
+MODULE_DESCRIPTION("Soc AK4535 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h
new file mode 100644
index 0000000..c7a5870
--- /dev/null
+++ b/sound/soc/codecs/ak4535.h
@@ -0,0 +1,47 @@
+/*
+ * ak4535.h -- AK4535 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK4535_H
+#define _AK4535_H
+
+/* AK4535 register space */
+
+#define AK4535_PM1 0x0
+#define AK4535_PM2 0x1
+#define AK4535_SIG1 0x2
+#define AK4535_SIG2 0x3
+#define AK4535_MODE1 0x4
+#define AK4535_MODE2 0x5
+#define AK4535_DAC 0x6
+#define AK4535_MIC 0x7
+#define AK4535_TIMER 0x8
+#define AK4535_ALC1 0x9
+#define AK4535_ALC2 0xa
+#define AK4535_PGA 0xb
+#define AK4535_LATT 0xc
+#define AK4535_RATT 0xd
+#define AK4535_VOL 0xe
+#define AK4535_STATUS 0xf
+
+#define AK4535_CACHEREGNUM 0x10
+
+struct ak4535_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ak4535_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ak4535;
+
+#endif
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
new file mode 100644
index 0000000..0bbd945
--- /dev/null
+++ b/sound/soc/codecs/cs4270.c
@@ -0,0 +1,765 @@
+/*
+ * CS4270 ALSA SoC (ASoC) codec driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ * This is an ASoC device driver for the Cirrus Logic CS4270 codec.
+ *
+ * Current features/limitations:
+ *
+ * 1) Software mode is supported. Stand-alone mode is automatically
+ * selected if I2C is disabled or if a CS4270 is not found on the I2C
+ * bus. However, stand-alone mode is only partially implemented because
+ * there is no mechanism yet for this driver and the machine driver to
+ * communicate the values of the M0, M1, MCLK1, and MCLK2 pins.
+ * 2) Only I2C is supported, not SPI
+ * 3) Only Master mode is supported, not Slave.
+ * 4) The machine driver's 'startup' function must call
+ * cs4270_set_dai_sysclk() with the value of MCLK.
+ * 5) Only I2S and left-justified modes are supported
+ * 6) Power management is not supported
+ * 7) The only supported control is volume and hardware mute (if enabled)
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+
+#include "cs4270.h"
+
+/* If I2C is defined, then we support software mode. However, if we're
+ not compiled as module but I2C is, then we can't use I2C calls. */
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+#define USE_I2C
+#endif
+
+/* Private data for the CS4270 */
+struct cs4270_private {
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+};
+
+/*
+ * The codec isn't really big-endian or little-endian, since the I2S
+ * interface requires data to be sent serially with the MSbit first.
+ * However, to support BE and LE I2S devices, we specify both here. That
+ * way, ALSA will always match the bit patterns.
+ */
+#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
+
+#ifdef USE_I2C
+
+/* CS4270 registers addresses */
+#define CS4270_CHIPID 0x01 /* Chip ID */
+#define CS4270_PWRCTL 0x02 /* Power Control */
+#define CS4270_MODE 0x03 /* Mode Control */
+#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */
+#define CS4270_TRANS 0x05 /* Transition Control */
+#define CS4270_MUTE 0x06 /* Mute Control */
+#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */
+#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */
+
+#define CS4270_FIRSTREG 0x01
+#define CS4270_LASTREG 0x08
+#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1)
+
+/* Bit masks for the CS4270 registers */
+#define CS4270_CHIPID_ID 0xF0
+#define CS4270_CHIPID_REV 0x0F
+#define CS4270_PWRCTL_FREEZE 0x80
+#define CS4270_PWRCTL_PDN_ADC 0x20
+#define CS4270_PWRCTL_PDN_DAC 0x02
+#define CS4270_PWRCTL_PDN 0x01
+#define CS4270_MODE_SPEED_MASK 0x30
+#define CS4270_MODE_1X 0x00
+#define CS4270_MODE_2X 0x10
+#define CS4270_MODE_4X 0x20
+#define CS4270_MODE_SLAVE 0x30
+#define CS4270_MODE_DIV_MASK 0x0E
+#define CS4270_MODE_DIV1 0x00
+#define CS4270_MODE_DIV15 0x02
+#define CS4270_MODE_DIV2 0x04
+#define CS4270_MODE_DIV3 0x06
+#define CS4270_MODE_DIV4 0x08
+#define CS4270_MODE_POPGUARD 0x01
+#define CS4270_FORMAT_FREEZE_A 0x80
+#define CS4270_FORMAT_FREEZE_B 0x40
+#define CS4270_FORMAT_LOOPBACK 0x20
+#define CS4270_FORMAT_DAC_MASK 0x18
+#define CS4270_FORMAT_DAC_LJ 0x00
+#define CS4270_FORMAT_DAC_I2S 0x08
+#define CS4270_FORMAT_DAC_RJ16 0x18
+#define CS4270_FORMAT_DAC_RJ24 0x10
+#define CS4270_FORMAT_ADC_MASK 0x01
+#define CS4270_FORMAT_ADC_LJ 0x00
+#define CS4270_FORMAT_ADC_I2S 0x01
+#define CS4270_TRANS_ONE_VOL 0x80
+#define CS4270_TRANS_SOFT 0x40
+#define CS4270_TRANS_ZERO 0x20
+#define CS4270_TRANS_INV_ADC_A 0x08
+#define CS4270_TRANS_INV_ADC_B 0x10
+#define CS4270_TRANS_INV_DAC_A 0x02
+#define CS4270_TRANS_INV_DAC_B 0x04
+#define CS4270_TRANS_DEEMPH 0x01
+#define CS4270_MUTE_AUTO 0x20
+#define CS4270_MUTE_ADC_A 0x08
+#define CS4270_MUTE_ADC_B 0x10
+#define CS4270_MUTE_POLARITY 0x04
+#define CS4270_MUTE_DAC_A 0x01
+#define CS4270_MUTE_DAC_B 0x02
+
+/*
+ * Clock Ratio Selection for Master Mode with I2C enabled
+ *
+ * The data for this chart is taken from Table 5 of the CS4270 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Mode Control register.
+ * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ *
+ * Each element in this array corresponds to the ratios in mclk_ratios[].
+ * These two arrays need to be in sync.
+ *
+ * 'speed_mode' is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ * In situations where a single ratio is represented by multiple speed
+ * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick
+ * double-speed instead of quad-speed. However, the CS4270 errata states
+ * that Divide-By-1.5 can cause failures, so we avoid that mode where
+ * possible.
+ *
+ * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if VD = 3.3V. If this effects you, select the
+ * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
+ * never select any sample rates that require divide-by-1.5.
+ */
+static struct {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk;
+} cs4270_mode_ratios[] = {
+ {64, CS4270_MODE_4X, CS4270_MODE_DIV1},
+#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
+ {96, CS4270_MODE_4X, CS4270_MODE_DIV15},
+#endif
+ {128, CS4270_MODE_2X, CS4270_MODE_DIV1},
+ {192, CS4270_MODE_4X, CS4270_MODE_DIV3},
+ {256, CS4270_MODE_1X, CS4270_MODE_DIV1},
+ {384, CS4270_MODE_2X, CS4270_MODE_DIV3},
+ {512, CS4270_MODE_1X, CS4270_MODE_DIV2},
+ {768, CS4270_MODE_1X, CS4270_MODE_DIV3},
+ {1024, CS4270_MODE_1X, CS4270_MODE_DIV4}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS4270 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios)
+
+/*
+ * Determine the CS4270 samples rates.
+ *
+ * 'freq' is the input frequency to MCLK. The other parameters are ignored.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine
+ * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ *
+ * This function calculates the nine ratios and determines which ones match
+ * a standard sample rate. If there's a match, then it is added to the list
+ * of support sample rates.
+ *
+ * This function must be called by the machine driver's 'startup' function,
+ * otherwise the list of supported sample rates will not be available in
+ * time for ALSA.
+ *
+ * Note that in stand-alone mode, the sample rate is determined by input
+ * pins M0, M1, MDIV1, and MDIV2. Also in stand-alone mode, divide-by-3
+ * is not a programmable option. However, divide-by-3 is not an available
+ * option in stand-alone mode. This cases two problems: a ratio of 768 is
+ * not available (it requires divide-by-3) and B) ratios 192 and 384 can
+ * only be selected with divide-by-1.5, but there is an errate that make
+ * this selection difficult.
+ *
+ * In addition, there is no mechanism for communicating with the machine
+ * driver what the input settings can be. This would need to be implemented
+ * for stand-alone mode to work.
+ */
+static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
+ unsigned int rates = 0;
+ unsigned int rate_min = -1;
+ unsigned int rate_max = 0;
+ unsigned int i;
+
+ cs4270->mclk = freq;
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ unsigned int rate = freq / cs4270_mode_ratios[i].ratio;
+ rates |= snd_pcm_rate_to_rate_bit(rate);
+ if (rate < rate_min)
+ rate_min = rate;
+ if (rate > rate_max)
+ rate_max = rate;
+ }
+ /* FIXME: soc should support a rate list */
+ rates &= ~SNDRV_PCM_RATE_KNOT;
+
+ if (!rates) {
+ printk(KERN_ERR "cs4270: could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+
+ codec_dai->playback.rates = rates;
+ codec_dai->playback.rate_min = rate_min;
+ codec_dai->playback.rate_max = rate_max;
+
+ codec_dai->capture.rates = rates;
+ codec_dai->capture.rate_min = rate_min;
+ codec_dai->capture.rate_max = rate_max;
+
+ return 0;
+}
+
+/*
+ * Configure the codec for the selected audio format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J. The CS4270 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4270_private *cs4270 = codec->private_data;
+ int ret = 0;
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ printk(KERN_ERR "cs4270: invalid DAI format\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * A list of addresses on which this CS4270 could use. I2C addresses are
+ * 7 bits. For the CS4270, the upper four bits are always 1001, and the
+ * lower three bits are determined via the AD2, AD1, and AD0 pins
+ * (respectively).
+ */
+static const unsigned short normal_i2c[] = {
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END
+};
+I2C_CLIENT_INSMOD;
+
+/*
+ * Pre-fill the CS4270 register cache.
+ *
+ * We use the auto-increment feature of the CS4270 to read all registers in
+ * one shot.
+ */
+static int cs4270_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
+
+ if (length != CS4270_NUMREGS) {
+ printk(KERN_ERR "cs4270: I2C read failure, addr=0x%x\n",
+ i2c_client->addr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Read from the CS4270 register cache.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherncy problem.
+ */
+static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+ return -EIO;
+
+ return cache[reg - CS4270_FIRSTREG];
+}
+
+/*
+ * Write to a CS4270 register via the I2C bus.
+ *
+ * This function writes the given value to the given CS4270 register, and
+ * also updates the register cache.
+ *
+ * Note that we don't use the hw_write function pointer of snd_soc_codec.
+ * That's because it's too clunky: the hw_write_t prototype does not match
+ * i2c_smbus_write_byte_data(), and it's just another layer of overhead.
+ */
+static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+ return -EIO;
+
+ /* Only perform an I2C operation if the new value is different */
+ if (cache[reg - CS4270_FIRSTREG] != value) {
+ struct i2c_client *client = codec->control_data;
+ if (i2c_smbus_write_byte_data(client, reg, value)) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return -EIO;
+ }
+
+ /* We've written to the hardware, so update the cache */
+ cache[reg - CS4270_FIRSTREG] = value;
+ }
+
+ return 0;
+}
+
+/*
+ * Program the CS4270 with the given hardware parameters.
+ *
+ * The .dai_ops functions are used to provide board-specific data, like
+ * input frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs4270_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 cs4270_private *cs4270 = codec->private_data;
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ int reg;
+
+ /* Figure out which MCLK/LRCK ratio to use */
+
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ if (cs4270_mode_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == NUM_MCLK_RATIOS) {
+ /* We did not find a matching ratio */
+ printk(KERN_ERR "cs4270: could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ /* Freeze and power-down the codec */
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE |
+ CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC |
+ CS4270_PWRCTL_PDN);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Program the mode control register */
+
+ reg = snd_soc_read(codec, CS4270_MODE);
+ reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
+ reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk;
+
+ ret = snd_soc_write(codec, CS4270_MODE, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Program the format register */
+
+ reg = snd_soc_read(codec, CS4270_FORMAT);
+ reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
+
+ switch (cs4270->mode) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
+ break;
+ default:
+ printk(KERN_ERR "cs4270: unknown format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_write(codec, CS4270_FORMAT, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Disable auto-mute. This feature appears to be buggy, because in
+ some situations, auto-mute will not deactivate when it should. */
+
+ reg = snd_soc_read(codec, CS4270_MUTE);
+ reg &= ~CS4270_MUTE_AUTO;
+ ret = snd_soc_write(codec, CS4270_MUTE, reg);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ /* Thaw and power-up the codec */
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: I2C write failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+
+/*
+ * Set the CS4270 external mute
+ *
+ * This function toggles the mute bits in the MUTE register. The CS4270's
+ * mute capability is intended for external muting circuitry, so if the
+ * board does not have the MUTEA or MUTEB pins connected to such circuitry,
+ * then this function will do nothing.
+ */
+static int cs4270_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int reg6;
+
+ reg6 = snd_soc_read(codec, CS4270_MUTE);
+
+ if (mute)
+ reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+ CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+ else
+ reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+ CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+
+ return snd_soc_write(codec, CS4270_MUTE, reg6);
+}
+
+#endif
+
+static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *);
+
+/* A list of non-DAPM controls that the CS4270 supports */
+static const struct snd_kcontrol_new cs4270_snd_controls[] = {
+ SOC_DOUBLE_R("Master Playback Volume",
+ CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1)
+};
+
+static const struct i2c_device_id cs4270_id[] = {
+ {"cs4270", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
+static struct i2c_driver cs4270_i2c_driver = {
+ .driver = {
+ .name = "CS4270 I2C",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4270_id,
+ .probe = cs4270_i2c_probe,
+};
+
+/*
+ * Global variable to store socdev for i2c probe function.
+ *
+ * If struct i2c_driver had a private_data field, we wouldn't need to use
+ * cs4270_socdec. This is the only way to pass the socdev structure to
+ * cs4270_i2c_probe().
+ *
+ * The real solution to cs4270_socdev is to create a mechanism
+ * that maps I2C addresses to snd_soc_device structures. Perhaps the
+ * creation of the snd_soc_device object should be moved out of
+ * cs4270_probe() and into cs4270_i2c_probe(), but that would make this
+ * driver dependent on I2C. The CS4270 supports "stand-alone" mode, whereby
+ * the chip is *not* connected to the I2C bus, but is instead configured via
+ * input pins.
+ */
+static struct snd_soc_device *cs4270_socdev;
+
+/*
+ * Initialize the I2C interface of the CS4270
+ *
+ * This function is called for whenever the I2C subsystem finds a device
+ * at a particular address.
+ *
+ * Note: snd_soc_new_pcms() must be called before this function can be called,
+ * because of snd_ctl_add().
+ */
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = cs4270_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ int ret = 0;
+
+ /* Probing all possible addresses has one drawback: if there are
+ multiple CS4270s on the bus, then you cannot specify which
+ socdev is matched with which CS4270. For now, we just reject
+ this I2C device if the socdev already has one attached. */
+ if (codec->control_data)
+ return -ENODEV;
+
+ /* Note: codec_dai->codec is NULL here */
+
+ codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL);
+ if (!codec->reg_cache) {
+ printk(KERN_ERR "cs4270: could not allocate register cache\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Verify that we have a CS4270 */
+
+ ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to read I2C\n");
+ goto error;
+ }
+ /* The top four bits of the chip ID should be 1100. */
+ if ((ret & 0xF0) != 0xC0) {
+ /* The device at this address is not a CS4270 codec */
+ ret = -ENODEV;
+ goto error;
+ }
+
+ printk(KERN_INFO "cs4270: found device at I2C address %X\n",
+ i2c_client->addr);
+ printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF);
+
+ codec->control_data = i2c_client;
+ codec->read = cs4270_read_reg_cache;
+ codec->write = cs4270_i2c_write;
+ codec->reg_cache_size = CS4270_NUMREGS;
+
+ /* The I2C interface is set up, so pre-fill our register cache */
+
+ ret = cs4270_fill_cache(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to fill register cache\n");
+ goto error;
+ }
+
+ /* Add the non-DAPM controls */
+
+ for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
+ struct snd_kcontrol *kctrl =
+ snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
+
+ ret = snd_ctl_add(codec->card, kctrl);
+ if (ret < 0)
+ goto error;
+ }
+
+ i2c_set_clientdata(i2c_client, codec);
+
+ return 0;
+
+error:
+ codec->control_data = NULL;
+
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+ codec->reg_cache_size = 0;
+
+ return ret;
+}
+
+#endif /* USE_I2C*/
+
+struct snd_soc_dai cs4270_dai = {
+ .name = "CS4270",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+};
+EXPORT_SYMBOL_GPL(cs4270_dai);
+
+/*
+ * ASoC probe function
+ *
+ * This function is called when the machine driver calls
+ * platform_device_add().
+ */
+static int cs4270_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "CS4270 ALSA SoC Codec\n");
+
+ /* Allocate enough space for the snd_soc_codec structure
+ and our private data together. */
+ codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) +
+ sizeof(struct cs4270_private), GFP_KERNEL);
+ if (!codec) {
+ printk(KERN_ERR "cs4270: Could not allocate codec structure\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->name = "CS4270";
+ codec->owner = THIS_MODULE;
+ codec->dai = &cs4270_dai;
+ codec->num_dai = 1;
+ codec->private_data = (void *) codec +
+ ALIGN(sizeof(struct snd_soc_codec), 4);
+
+ socdev->codec = codec;
+
+ /* Register PCMs */
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to create PCMs\n");
+ goto error_free_codec;
+ }
+
+#ifdef USE_I2C
+ cs4270_socdev = socdev;
+
+ ret = i2c_add_driver(&cs4270_i2c_driver);
+ if (ret) {
+ printk(KERN_ERR "cs4270: failed to attach driver");
+ goto error_free_pcms;
+ }
+
+ /* Did we find a CS4270 on the I2C bus? */
+ if (codec->control_data) {
+ /* Initialize codec ops */
+ cs4270_dai.ops.hw_params = cs4270_hw_params;
+ cs4270_dai.dai_ops.set_sysclk = cs4270_set_dai_sysclk;
+ cs4270_dai.dai_ops.set_fmt = cs4270_set_dai_fmt;
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+ cs4270_dai.dai_ops.digital_mute = cs4270_mute;
+#endif
+ } else
+ printk(KERN_INFO "cs4270: no I2C device found, "
+ "using stand-alone mode\n");
+#else
+ printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n");
+#endif
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "cs4270: failed to register card\n");
+ goto error_del_driver;
+ }
+
+ return 0;
+
+error_del_driver:
+#ifdef USE_I2C
+ i2c_del_driver(&cs4270_i2c_driver);
+
+error_free_pcms:
+#endif
+ snd_soc_free_pcms(socdev);
+
+error_free_codec:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+
+ return ret;
+}
+
+static int cs4270_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+
+#ifdef USE_I2C
+ i2c_del_driver(&cs4270_i2c_driver);
+#endif
+
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+
+ return 0;
+}
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+struct snd_soc_codec_device soc_codec_device_cs4270 = {
+ .probe = cs4270_probe,
+ .remove = cs4270_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs4270);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h
new file mode 100644
index 0000000..adc6cd9
--- /dev/null
+++ b/sound/soc/codecs/cs4270.h
@@ -0,0 +1,28 @@
+/*
+ * Cirrus Logic CS4270 ALSA SoC Codec Driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#ifndef _CS4270_H
+#define _CS4270_H
+
+/*
+ * The ASoC codec DAI structure for the CS4270. Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_dai cs4270_dai;
+
+/*
+ * The ASoC codec device structure for the CS4270. Assign this structure
+ * to the .codec_dev field of your machine driver's snd_soc_device
+ * structure.
+ */
+extern struct snd_soc_codec_device soc_codec_device_cs4270;
+
+#endif
diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c
new file mode 100644
index 0000000..44ef0da
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.c
@@ -0,0 +1,775 @@
+/*
+ * File: sound/soc/codecs/ssm2602.c
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ * Description: Driver for ssm2602 sound chip
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/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 "ssm2602.h"
+
+#define SSM2602_VERSION "0.1"
+
+struct snd_soc_codec_device soc_codec_dev_ssm2602;
+
+/* codec private data */
+struct ssm2602_priv {
+ unsigned int sysclk;
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+/*
+ * ssm2602 register cache
+ * We can't read the ssm2602 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = {
+ 0x0017, 0x0017, 0x0079, 0x0079,
+ 0x0000, 0x0000, 0x0000, 0x000a,
+ 0x0000, 0x0000
+};
+
+/*
+ * read ssm2602 register cache
+ */
+static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == SSM2602_RESET)
+ return 0;
+ if (reg >= SSM2602_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write ssm2602 register cache
+ */
+static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= SSM2602_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the ssm2602 register space
+ */
+static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 ssm2602 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ ssm2602_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0)
+
+/*Appending several "None"s just for OSS mixer use*/
+static const char *ssm2602_input_select[] = {
+ "Line", "Mic", "None", "None", "None",
+ "None", "None", "None",
+};
+
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum ssm2602_enum[] = {
+ SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select),
+ SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph),
+};
+
+static const struct snd_kcontrol_new ssm2602_snd_controls[] = {
+
+SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V,
+ 7, 1, 0),
+
+SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0),
+SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1),
+
+SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Capture Source", ssm2602_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]),
+};
+
+/* add non dapm controls */
+static int ssm2602_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Output Mixer */
+static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ssm2602_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]);
+
+static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1,
+ &ssm2602_output_mixer_controls[0],
+ ARRAY_SIZE(ssm2602_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route audio_conn[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+static int ssm2602_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets,
+ ARRAY_SIZE(ssm2602_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return i;
+}
+
+static int ssm2602_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ u16 srate;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ssm2602_priv *ssm2602 = codec->private_data;
+ u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3;
+ int i = get_coeff(ssm2602->sysclk, params_rate(params));
+
+ /*no match is found*/
+ if (i == ARRAY_SIZE(coeff_div))
+ return -EINVAL;
+
+ srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ ssm2602_write(codec, SSM2602_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+ ssm2602_write(codec, SSM2602_IFACE, iface);
+ ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+ return 0;
+}
+
+static int ssm2602_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct ssm2602_priv *ssm2602 = codec->private_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (ssm2602->master_substream) {
+ master_runtime = ssm2602->master_substream->runtime;
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ ssm2602->slave_substream = substream;
+ } else
+ ssm2602->master_substream = substream;
+
+ return 0;
+}
+
+static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ /* set active */
+ ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+
+ return 0;
+}
+
+static void ssm2602_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ /* deactivate */
+ if (!codec->active)
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+}
+
+static int ssm2602_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE;
+ if (mute)
+ ssm2602_write(codec, SSM2602_APDIGI,
+ mute_reg | APDIGI_ENABLE_DAC_MUTE);
+ else
+ ssm2602_write(codec, SSM2602_APDIGI, mute_reg);
+ return 0;
+}
+
+static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct ssm2602_priv *ssm2602 = codec->private_data;
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ ssm2602->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ ssm2602_write(codec, SSM2602_IFACE, iface);
+ return 0;
+}
+
+static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ ssm2602_write(codec, SSM2602_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ ssm2602_write(codec, SSM2602_PWR, 0xffff);
+ break;
+
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define SSM2602_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_dai ssm2602_dai = {
+ .name = "SSM2602",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SSM2602_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,},
+ .ops = {
+ .startup = ssm2602_startup,
+ .prepare = ssm2602_pcm_prepare,
+ .hw_params = ssm2602_hw_params,
+ .shutdown = ssm2602_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = ssm2602_mute,
+ .set_sysclk = ssm2602_set_dai_sysclk,
+ .set_fmt = ssm2602_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(ssm2602_dai);
+
+static int ssm2602_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;
+
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ssm2602_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ssm2602_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the ssm2602 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int ssm2602_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "SSM2602";
+ codec->owner = THIS_MODULE;
+ codec->read = ssm2602_read_reg_cache;
+ codec->write = ssm2602_write;
+ codec->set_bias_level = ssm2602_set_bias_level;
+ codec->dai = &ssm2602_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(ssm2602_reg);
+ codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ ssm2602_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ pr_err("ssm2602: failed to create pcms\n");
+ goto pcm_err;
+ }
+ /*power on device*/
+ ssm2602_write(codec, SSM2602_ACTIVE, 0);
+ /* set the update bits */
+ reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL);
+ ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL);
+ ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V);
+ ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH);
+ reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V);
+ ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH);
+ /*select Line in as default input*/
+ ssm2602_write(codec, SSM2602_APANA,
+ APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC |
+ APANA_ENABLE_MIC_BOOST);
+ ssm2602_write(codec, SSM2602_PWR, 0);
+
+ ssm2602_add_controls(codec);
+ ssm2602_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ pr_err("ssm2602: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *ssm2602_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * ssm2602 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int ssm2602_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = ssm2602_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = ssm2602_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise SSM2602\n");
+
+ return ret;
+}
+
+static int ssm2602_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id ssm2602_i2c_id[] = {
+ { "ssm2602", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id);
+/* corgi i2c codec control layer */
+static struct i2c_driver ssm2602_i2c_driver = {
+ .driver = {
+ .name = "SSM2602 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2602_i2c_probe,
+ .remove = ssm2602_i2c_remove,
+ .id_table = ssm2602_i2c_id,
+};
+
+static int ssm2602_add_i2c_device(struct platform_device *pdev,
+ const struct ssm2602_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&ssm2602_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "ssm2602", I2C_NAME_SIZE);
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+ return 0;
+err_driver:
+ i2c_del_driver(&ssm2602_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int ssm2602_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct ssm2602_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct ssm2602_priv *ssm2602;
+ int ret = 0;
+
+ pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL);
+ if (ssm2602 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = ssm2602;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ssm2602_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = ssm2602_add_i2c_device(pdev, setup);
+ }
+#else
+ /* other interfaces */
+#endif
+ return ret;
+}
+
+/* remove everything here */
+static int ssm2602_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)
+ ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&ssm2602_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ssm2602 = {
+ .probe = ssm2602_probe,
+ .remove = ssm2602_remove,
+ .suspend = ssm2602_suspend,
+ .resume = ssm2602_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602);
+
+MODULE_DESCRIPTION("ASoC ssm2602 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h
new file mode 100644
index 0000000..f344e6d
--- /dev/null
+++ b/sound/soc/codecs/ssm2602.h
@@ -0,0 +1,130 @@
+/*
+ * File: sound/soc/codecs/ssm2602.h
+ * Author: Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created: Tue June 06 2008
+ *
+ * Modified:
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs: Enter bugs at http://blackfin.uclinux.org/
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SSM2602_H
+#define _SSM2602_H
+
+/* SSM2602 Codec Register definitions */
+
+#define SSM2602_LINVOL 0x00
+#define SSM2602_RINVOL 0x01
+#define SSM2602_LOUT1V 0x02
+#define SSM2602_ROUT1V 0x03
+#define SSM2602_APANA 0x04
+#define SSM2602_APDIGI 0x05
+#define SSM2602_PWR 0x06
+#define SSM2602_IFACE 0x07
+#define SSM2602_SRATE 0x08
+#define SSM2602_ACTIVE 0x09
+#define SSM2602_RESET 0x0f
+
+/*SSM2602 Codec Register Field definitions
+ *(Mask value to extract the corresponding Register field)
+ */
+
+/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/
+#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */
+#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */
+#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */
+
+/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/
+#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */
+#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */
+#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */
+
+/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/
+#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */
+#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */
+#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */
+
+/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/
+#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */
+#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */
+#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */
+
+/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/
+#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */
+#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */
+#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */
+#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */
+#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */
+#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */
+#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */
+#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */
+
+/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/
+#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */
+#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */
+#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */
+#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */
+
+/*Power Down Control (SSM2602_REG_POWER)
+ *(1=Enable PowerDown, 0=Disable PowerDown)
+ */
+#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */
+#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */
+#define PWR_ADC_PDN 0x004 /* ADC Power Down */
+#define PWR_DAC_PDN 0x008 /* DAC Power Down */
+#define PWR_OUT_PDN 0x010 /* Outputs Power Down */
+#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */
+#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */
+#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */
+
+/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/
+#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */
+#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */
+#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */
+#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */
+#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */
+#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */
+
+/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/
+#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */
+#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */
+#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */
+#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */
+#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */
+
+/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/
+#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */
+
+/*********************************************************************/
+
+#define SSM2602_CACHEREGNUM 10
+
+#define SSM2602_SYSCLK 0
+#define SSM2602_DAI 0
+
+struct ssm2602_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ssm2602_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ssm2602;
+
+#endif
diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c
new file mode 100644
index 0000000..44308da
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.c
@@ -0,0 +1,714 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd.,
+ *
+ * Based on sound/soc/codecs/wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC23 is a driver for a low power stereo audio
+ * codec tlv320aic23
+ *
+ * The machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "tlv320aic23.h"
+
+#define AIC23_VERSION "0.1"
+
+struct tlv320aic23_srate_reg_info {
+ u32 sample_rate;
+ u8 control; /* SR3, SR2, SR1, SR0 and BOSR */
+ u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */
+};
+
+/*
+ * AIC23 register cache
+ */
+static const u16 tlv320aic23_reg[] = {
+ 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */
+ 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */
+ 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */
+};
+
+/*
+ * read tlv320aic23 register cache
+ */
+static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec
+ *codec, unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write tlv320aic23 register cache
+ */
+static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u16 value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the tlv320aic23 register space
+ */
+static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+
+ u8 data[2];
+
+ /* TLV320AIC23 has 7 bit address and 9 bits of data
+ * so we need to switch one data bit into reg and rest
+ * of data into val
+ */
+
+ if ((reg < 0 || reg > 9) && (reg != 15)) {
+ printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg);
+ return -1;
+ }
+
+ data[0] = (reg << 1) | (value >> 8 & 0x01);
+ data[1] = value & 0xff;
+
+ tlv320aic23_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+
+ printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__,
+ value, reg);
+
+ return -EIO;
+}
+
+static const char *rec_src_text[] = { "Line", "Mic" };
+static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum rec_src_enum =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+
+static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls =
+SOC_DAPM_ENUM("Input Select", rec_src_enum);
+
+static const struct soc_enum tlv320aic23_rec_src =
+ SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+static const struct soc_enum tlv320aic23_deemph =
+ SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0);
+static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0);
+
+static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val, reg;
+
+ val = (ucontrol->value.integer.value[0] & 0x07);
+
+ /* linear conversion to userspace
+ * 000 = -6db
+ * 001 = -9db
+ * 010 = -12db
+ * 011 = -18db (Min)
+ * 100 = 0db (Max)
+ */
+ val = (val >= 4) ? 4 : (3 - val);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6));
+
+ return 0;
+}
+
+static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 val;
+
+ val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0);
+ val = val >> 6;
+ val = (val >= 4) ? 4 : (3 - val);
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+
+}
+
+#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\
+ .put = snd_soc_tlv320aic23_put_volsw, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL,
+ TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv),
+ SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1),
+ SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 7, 1, 0),
+ SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL,
+ TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv),
+ SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1),
+ SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0),
+ SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG,
+ 6, 4, 0, sidetone_vol_tlv),
+ SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph),
+};
+
+/* add non dapm controls */
+static int tlv320aic23_add_controls(struct snd_soc_codec *codec)
+{
+
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&tlv320aic23_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+
+}
+
+/* PGA Mixer controls for Line and Mic switch */
+static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0),
+ SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
+ SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),
+ SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0,
+ &tlv320aic23_rec_src_mux_controls),
+ SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1,
+ &tlv320aic23_output_mixer_controls[0],
+ ARRAY_SIZE(tlv320aic23_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LHPOUT"),
+ SND_SOC_DAPM_OUTPUT("RHPOUT"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("LLINEIN"),
+ SND_SOC_DAPM_INPUT("RLINEIN"),
+
+ SND_SOC_DAPM_INPUT("MICIN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Output Mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Input"},
+
+ /* Outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+
+ /* Inputs */
+ {"Line Input", "NULL", "LLINEIN"},
+ {"Line Input", "NULL", "RLINEIN"},
+
+ {"Mic Input", "NULL", "MICIN"},
+
+ /* input mux */
+ {"Capture Source", "Line", "Line Input"},
+ {"Capture Source", "Mic", "Mic Input"},
+ {"ADC", NULL, "Capture Source"},
+
+};
+
+/* tlv320aic23 related */
+static const struct tlv320aic23_srate_reg_info srate_reg_info[] = {
+ {4000, 0x06, 1}, /* 4000 */
+ {8000, 0x06, 0}, /* 8000 */
+ {16000, 0x0C, 1}, /* 16000 */
+ {22050, 0x11, 1}, /* 22050 */
+ {24000, 0x00, 1}, /* 24000 */
+ {32000, 0x0C, 0}, /* 32000 */
+ {44100, 0x11, 0}, /* 44100 */
+ {48000, 0x00, 0}, /* 48000 */
+ {88200, 0x1F, 0}, /* 88200 */
+ {96000, 0x0E, 0}, /* 96000 */
+};
+
+static int tlv320aic23_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int tlv320aic23_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 iface_reg, data;
+ u8 count = 0;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec,
+ TLV320AIC23_DIGT_FMT) & ~(0x03 << 2);
+
+ /* Search for the right sample rate */
+ /* Verify what happens if the rate is not supported
+ * now it goes to 96Khz */
+ while ((srate_reg_info[count].sample_rate != params_rate(params)) &&
+ (count < ARRAY_SIZE(srate_reg_info))) {
+ count++;
+ }
+
+ data = (srate_reg_info[count].divider << TLV320AIC23_CLKIN_SHIFT) |
+ (srate_reg_info[count]. control << TLV320AIC23_BOSR_SHIFT) |
+ TLV320AIC23_USB_CLK_ON;
+
+ tlv320aic23_write(codec, TLV320AIC23_SRATE, data);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface_reg |= (0x01 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface_reg |= (0x02 << 2);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface_reg |= (0x03 << 2);
+ break;
+ }
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* set active */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void tlv320aic23_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ }
+}
+
+static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 reg;
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT);
+ if (mute)
+ reg |= TLV320AIC23_DACM_MUTE;
+
+ else
+ reg &= ~TLV320AIC23_DACM_MUTE;
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, reg);
+
+ return 0;
+}
+
+static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface_reg;
+
+ iface_reg =
+ tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface_reg |= TLV320AIC23_MS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface_reg |= TLV320AIC23_FOR_I2S;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface_reg |= TLV320AIC23_FOR_DSP;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface_reg |= TLV320AIC23_FOR_LJUST;
+ break;
+ default:
+ return -EINVAL;
+
+ }
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+ return 0;
+}
+
+static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+
+ switch (freq) {
+ case 12000000:
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define AIC23_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai tlv320aic23_dai = {
+ .name = "tlv320aic23",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC23_RATES,
+ .formats = AIC23_FORMATS,},
+ .ops = {
+ .prepare = tlv320aic23_pcm_prepare,
+ .hw_params = tlv320aic23_hw_params,
+ .shutdown = tlv320aic23_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = tlv320aic23_mute,
+ .set_fmt = tlv320aic23_set_dai_fmt,
+ .set_sysclk = tlv320aic23_set_dai_sysclk,
+ }
+};
+EXPORT_SYMBOL_GPL(tlv320aic23_dai);
+
+static int tlv320aic23_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;
+
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int tlv320aic23_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) {
+ u16 val = tlv320aic23_read_reg_cache(codec, reg);
+ tlv320aic23_write(codec, reg, val);
+ }
+
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ tlv320aic23_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+/*
+ * initialise the AIC23 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int tlv320aic23_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+ u16 reg;
+
+ codec->name = "tlv320aic23";
+ codec->owner = THIS_MODULE;
+ codec->read = tlv320aic23_read_reg_cache;
+ codec->write = tlv320aic23_write;
+ codec->set_bias_level = tlv320aic23_set_bias_level;
+ codec->dai = &tlv320aic23_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(tlv320aic23_reg);
+ codec->reg_cache =
+ kmemdup(tlv320aic23_reg, sizeof(tlv320aic23_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* Reset codec */
+ tlv320aic23_write(codec, TLV320AIC23_RESET, 0);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K);
+
+ /* Unmute input */
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_LINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ (TLV320AIC23_LRS_ENABLED));
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL);
+ tlv320aic23_write(codec, TLV320AIC23_RINVOL,
+ (reg & (~TLV320AIC23_LIM_MUTED)) |
+ TLV320AIC23_LRS_ENABLED);
+
+ reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG);
+ tlv320aic23_write(codec, TLV320AIC23_ANLG,
+ (reg) & (~TLV320AIC23_BYPASS_ON) &
+ (~TLV320AIC23_MICM_MUTED));
+
+ /* Default output volume */
+ tlv320aic23_write(codec, TLV320AIC23_LCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+ tlv320aic23_write(codec, TLV320AIC23_RCHNVOL,
+ TLV320AIC23_DEFAULT_OUT_VOL &
+ TLV320AIC23_OUT_VOL_MASK);
+
+ tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1);
+
+ tlv320aic23_add_controls(codec);
+ tlv320aic23_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+static struct snd_soc_device *tlv320aic23_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int tlv320aic23_codec_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *i2c_id)
+{
+ struct snd_soc_device *socdev = tlv320aic23_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EINVAL;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = tlv320aic23_init(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "tlv320aic23: failed to initialise AIC23\n");
+ goto err;
+ }
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c)
+{
+ put_device(&i2c->dev);
+ return 0;
+}
+
+static const struct i2c_device_id tlv320aic23_id[] = {
+ {"tlv320aic23", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, tlv320aic23_id);
+
+static struct i2c_driver tlv320aic23_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic23",
+ },
+ .probe = tlv320aic23_codec_probe,
+ .remove = __exit_p(tlv320aic23_i2c_remove),
+ .id_table = tlv320aic23_id,
+};
+
+#endif
+
+static int tlv320aic23_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION);
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ tlv320aic23_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->hw_read = NULL;
+ ret = i2c_add_driver(&tlv320aic23_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+#endif
+ return ret;
+}
+
+static int tlv320aic23_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)
+ tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&tlv320aic23_i2c_driver);
+#endif
+ kfree(codec->reg_cache);
+ kfree(codec);
+
+ return 0;
+}
+struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = {
+ .probe = tlv320aic23_probe,
+ .remove = tlv320aic23_remove,
+ .suspend = tlv320aic23_suspend,
+ .resume = tlv320aic23_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver");
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h
new file mode 100644
index 0000000..79d1faf
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic23.h
@@ -0,0 +1,122 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author: Arun KS, <arunks@mistralsolutions.com>
+ * Copyright: (C) 2008 Mistral Solutions Pvt Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _TLV320AIC23_H
+#define _TLV320AIC23_H
+
+/* Codec TLV320AIC23 */
+#define TLV320AIC23_LINVOL 0x00
+#define TLV320AIC23_RINVOL 0x01
+#define TLV320AIC23_LCHNVOL 0x02
+#define TLV320AIC23_RCHNVOL 0x03
+#define TLV320AIC23_ANLG 0x04
+#define TLV320AIC23_DIGT 0x05
+#define TLV320AIC23_PWR 0x06
+#define TLV320AIC23_DIGT_FMT 0x07
+#define TLV320AIC23_SRATE 0x08
+#define TLV320AIC23_ACTIVE 0x09
+#define TLV320AIC23_RESET 0x0F
+
+/* Left (right) line input volume control register */
+#define TLV320AIC23_LRS_ENABLED 0x0100
+#define TLV320AIC23_LIM_MUTED 0x0080
+#define TLV320AIC23_LIV_DEFAULT 0x0017
+#define TLV320AIC23_LIV_MAX 0x001f
+#define TLV320AIC23_LIV_MIN 0x0000
+
+/* Left (right) channel headphone volume control register */
+#define TLV320AIC23_LZC_ON 0x0080
+#define TLV320AIC23_LHV_DEFAULT 0x0079
+#define TLV320AIC23_LHV_MAX 0x007f
+#define TLV320AIC23_LHV_MIN 0x0000
+
+/* Analog audio path control register */
+#define TLV320AIC23_STA_REG(x) ((x)<<6)
+#define TLV320AIC23_STE_ENABLED 0x0020
+#define TLV320AIC23_DAC_SELECTED 0x0010
+#define TLV320AIC23_BYPASS_ON 0x0008
+#define TLV320AIC23_INSEL_MIC 0x0004
+#define TLV320AIC23_MICM_MUTED 0x0002
+#define TLV320AIC23_MICB_20DB 0x0001
+
+/* Digital audio path control register */
+#define TLV320AIC23_DACM_MUTE 0x0008
+#define TLV320AIC23_DEEMP_32K 0x0002
+#define TLV320AIC23_DEEMP_44K 0x0004
+#define TLV320AIC23_DEEMP_48K 0x0006
+#define TLV320AIC23_ADCHP_ON 0x0001
+
+/* Power control down register */
+#define TLV320AIC23_DEVICE_PWR_OFF 0x0080
+#define TLV320AIC23_CLK_OFF 0x0040
+#define TLV320AIC23_OSC_OFF 0x0020
+#define TLV320AIC23_OUT_OFF 0x0010
+#define TLV320AIC23_DAC_OFF 0x0008
+#define TLV320AIC23_ADC_OFF 0x0004
+#define TLV320AIC23_MIC_OFF 0x0002
+#define TLV320AIC23_LINE_OFF 0x0001
+
+/* Digital audio interface register */
+#define TLV320AIC23_MS_MASTER 0x0040
+#define TLV320AIC23_LRSWAP_ON 0x0020
+#define TLV320AIC23_LRP_ON 0x0010
+#define TLV320AIC23_IWL_16 0x0000
+#define TLV320AIC23_IWL_20 0x0004
+#define TLV320AIC23_IWL_24 0x0008
+#define TLV320AIC23_IWL_32 0x000C
+#define TLV320AIC23_FOR_I2S 0x0002
+#define TLV320AIC23_FOR_DSP 0x0003
+#define TLV320AIC23_FOR_LJUST 0x0001
+
+/* Sample rate control register */
+#define TLV320AIC23_CLKOUT_HALF 0x0080
+#define TLV320AIC23_CLKIN_HALF 0x0040
+#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */
+#define TLV320AIC23_USB_CLK_ON 0x0001
+#define TLV320AIC23_SR_MASK 0xf
+#define TLV320AIC23_CLKOUT_SHIFT 7
+#define TLV320AIC23_CLKIN_SHIFT 6
+#define TLV320AIC23_SR_SHIFT 2
+#define TLV320AIC23_BOSR_SHIFT 1
+
+/* Digital interface register */
+#define TLV320AIC23_ACT_ON 0x0001
+
+/*
+ * AUDIO related MACROS
+ */
+
+#define TLV320AIC23_DEFAULT_OUT_VOL 0x70
+#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10
+
+#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN
+#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX
+#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \
+ TLV320AIC23_OUT_VOL_MIN)
+#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX
+
+#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN
+#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX
+#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \
+ TLV320AIC23_IN_VOL_MIN)
+#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX
+
+#define TLV320AIC23_SIDETONE_MASK 0x1c0
+#define TLV320AIC23_SIDETONE_0 0x100
+#define TLV320AIC23_SIDETONE_6 0x000
+#define TLV320AIC23_SIDETONE_9 0x040
+#define TLV320AIC23_SIDETONE_12 0x080
+#define TLV320AIC23_SIDETONE_18 0x0c0
+
+extern struct snd_soc_dai tlv320aic23_dai;
+extern struct snd_soc_codec_device soc_codec_dev_tlv320aic23;
+
+#endif /* _TLV320AIC23_H */
diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c
new file mode 100644
index 0000000..bed8a9e
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.c
@@ -0,0 +1,520 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * ALSA SoC CODEC driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.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/soc-of-simple.h>
+#include <sound/initval.h>
+
+#include "tlv320aic26.h"
+
+MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver");
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+/* AIC26 driver private data */
+struct aic26 {
+ struct spi_device *spi;
+ struct snd_soc_codec codec;
+ u16 reg_cache[AIC26_NUM_REGS]; /* shadow registers */
+ int master;
+ int datfm;
+ int mclk;
+
+ /* Keyclick parameters */
+ int keyclick_amplitude;
+ int keyclick_freq;
+ int keyclick_len;
+};
+
+/* ---------------------------------------------------------------------
+ * Register access routines
+ */
+static unsigned int aic26_reg_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct aic26 *aic26 = codec->private_data;
+ u16 *cache = codec->reg_cache;
+ u16 cmd, value;
+ u8 buffer[2];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is
+ * register contents */
+ cmd = AIC26_READ_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+ value = (buffer[0] << 8) | buffer[1];
+
+ /* Update the cache before returning with the value */
+ cache[reg] = value;
+ return value;
+}
+
+static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ return cache[reg];
+}
+
+static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct aic26 *aic26 = codec->private_data;
+ u16 *cache = codec->reg_cache;
+ u16 cmd;
+ u8 buffer[4];
+ int rc;
+
+ if (reg >= AIC26_NUM_REGS) {
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* Do SPI transfer; first 16bits are command; remaining is data
+ * to write into register */
+ cmd = AIC26_WRITE_COMMAND_WORD(reg);
+ buffer[0] = (cmd >> 8) & 0xff;
+ buffer[1] = cmd & 0xff;
+ buffer[2] = value >> 8;
+ buffer[3] = value;
+ rc = spi_write(aic26->spi, buffer, 4);
+ if (rc) {
+ dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+ return -EIO;
+ }
+
+ /* update cache before returning */
+ cache[reg] = value;
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Operations
+ */
+static int aic26_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 aic26 *aic26 = codec->private_data;
+ int fsref, divisor, wlen, pval, jval, dval, qval;
+ u16 reg;
+
+ dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n",
+ substream, params);
+ dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params),
+ params_format(params));
+
+ switch (params_rate(params)) {
+ case 8000: fsref = 48000; divisor = AIC26_DIV_6; break;
+ case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
+ case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
+ case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
+ case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
+ case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
+ case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
+ case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
+ case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
+ }
+
+ /* select data word length */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break;
+ case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break;
+ case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ /* Configure PLL */
+ pval = 1;
+ jval = (fsref == 44100) ? 7 : 8;
+ dval = (fsref == 44100) ? 5264 : 1920;
+ qval = 0;
+ reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
+ reg = dval << 2;
+ aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ if (aic26->master)
+ reg |= 0x0800;
+ if (fsref == 48000)
+ reg |= 0x2000;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Audio Control 1 (FSref divisor) */
+ reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1);
+ reg &= ~0x0fff;
+ reg |= wlen | aic26->datfm | (divisor << 3) | divisor;
+ aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg);
+
+ return 0;
+}
+
+/**
+ * aic26_mute - Mute control to reduce noise when changing audio format
+ */
+static int aic26_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+ u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN);
+
+ dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n",
+ dai, mute);
+
+ if (mute)
+ reg |= 0x8080;
+ else
+ reg &= ~0x8080;
+ aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg);
+
+ return 0;
+}
+
+static int aic26_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i,"
+ " freq=%i, dir=%i)\n",
+ codec_dai, clk_id, freq, dir);
+
+ /* MCLK needs to fall between 2MHz and 50 MHz */
+ if ((freq < 2000000) || (freq > 50000000))
+ return -EINVAL;
+
+ aic26->mclk = freq;
+ return 0;
+}
+
+static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic26 *aic26 = codec->private_data;
+
+ dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n",
+ codec_dai, fmt);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break;
+ case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
+ case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
+ case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
+ case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
+ default:
+ dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Definition
+ */
+#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+struct snd_soc_dai aic26_dai = {
+ .name = "tlv320aic26",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AIC26_RATES,
+ .formats = AIC26_FORMATS,
+ },
+ .ops = {
+ .hw_params = aic26_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = aic26_mute,
+ .set_sysclk = aic26_set_sysclk,
+ .set_fmt = aic26_set_fmt,
+ },
+};
+EXPORT_SYMBOL_GPL(aic26_dai);
+
+/* ---------------------------------------------------------------------
+ * ALSA controls
+ */
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"};
+static const struct soc_enum aic26_capture_src_enum =
+ SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text);
+
+static const struct snd_kcontrol_new aic26_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1),
+ SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1),
+ SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0),
+ SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1),
+ SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
+ SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
+ SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
+ SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
+ SOC_ENUM("Capture Source", aic26_capture_src_enum),
+};
+
+/* ---------------------------------------------------------------------
+ * SoC CODEC portion of driver: probe and release routines
+ */
+static int aic26_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct snd_kcontrol *kcontrol;
+ struct aic26 *aic26;
+ int i, ret, err;
+
+ dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n");
+ dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
+ dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);
+
+ /* Fetch the relevant aic26 private data here (it's already been
+ * stored in the .codec pointer) */
+ aic26 = socdev->codec_data;
+ if (aic26 == NULL) {
+ dev_err(&pdev->dev, "aic26: missing codec pointer\n");
+ return -ENODEV;
+ }
+ codec = &aic26->codec;
+ socdev->codec = codec;
+
+ dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
+ &pdev->dev, socdev->dev);
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "aic26: failed to create pcms\n");
+ return -ENODEV;
+ }
+
+ /* register controls */
+ dev_dbg(&pdev->dev, "Registering controls\n");
+ for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) {
+ kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL);
+ err = snd_ctl_add(codec->card, kcontrol);
+ WARN_ON(err < 0);
+ }
+
+ /* CODEC is setup, we can register the card now */
+ dev_dbg(&pdev->dev, "Registering card\n");
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "aic26: failed to register card\n");
+ goto card_err;
+ }
+ return 0;
+
+ card_err:
+ snd_soc_free_pcms(socdev);
+ return ret;
+}
+
+static int aic26_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ snd_soc_free_pcms(socdev);
+ return 0;
+}
+
+struct snd_soc_codec_device aic26_soc_codec_dev = {
+ .probe = aic26_probe,
+ .remove = aic26_remove,
+};
+EXPORT_SYMBOL_GPL(aic26_soc_codec_dev);
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: sysfs files for debugging
+ */
+
+static ssize_t aic26_keyclick_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val, amp, freq, len;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ amp = (val >> 12) & 0x7;
+ freq = (125 << ((val >> 8) & 0x7)) >> 1;
+ len = 2 * (1 + ((val >> 4) & 0xf));
+
+ return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len);
+}
+
+/* Any write to the keyclick attribute will trigger the keyclick event */
+static ssize_t aic26_keyclick_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct aic26 *aic26 = dev_get_drvdata(dev);
+ int val;
+
+ val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+ val |= 0x8000;
+ aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val);
+
+ return count;
+}
+
+static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: probe and release routines and SPI
+ * driver registration.
+ */
+static int aic26_spi_probe(struct spi_device *spi)
+{
+ struct aic26 *aic26;
+ int rc, i, reg;
+
+ dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n");
+
+ /* Allocate driver data */
+ aic26 = kzalloc(sizeof *aic26, GFP_KERNEL);
+ if (!aic26)
+ return -ENOMEM;
+
+ /* Initialize the driver data */
+ aic26->spi = spi;
+ dev_set_drvdata(&spi->dev, aic26);
+
+ /* Setup what we can in the codec structure so that the register
+ * access functions will work as expected. More will be filled
+ * out when it is probed by the SoC CODEC part of this driver */
+ aic26->codec.private_data = aic26;
+ aic26->codec.name = "aic26";
+ aic26->codec.owner = THIS_MODULE;
+ aic26->codec.dai = &aic26_dai;
+ aic26->codec.num_dai = 1;
+ aic26->codec.read = aic26_reg_read;
+ aic26->codec.write = aic26_reg_write;
+ aic26->master = 1;
+ mutex_init(&aic26->codec.mutex);
+ INIT_LIST_HEAD(&aic26->codec.dapm_widgets);
+ INIT_LIST_HEAD(&aic26->codec.dapm_paths);
+ aic26->codec.reg_cache_size = AIC26_NUM_REGS;
+ aic26->codec.reg_cache = aic26->reg_cache;
+
+ /* Reset the codec to power on defaults */
+ aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00);
+
+ /* Power up CODEC */
+ aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0);
+
+ /* Audio Control 3 (master mode, fsref rate) */
+ reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3);
+ reg &= ~0xf800;
+ reg |= 0x0800; /* set master mode */
+ aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+ /* Fill register cache */
+ for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++)
+ aic26_reg_read(&aic26->codec, i);
+
+ /* Register the sysfs files for debugging */
+ /* Create SysFS files */
+ rc = device_create_file(&spi->dev, &dev_attr_keyclick);
+ if (rc)
+ dev_info(&spi->dev, "error creating sysfs files\n");
+
+#if defined(CONFIG_SND_SOC_OF_SIMPLE)
+ /* Tell the of_soc helper about this codec */
+ of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
+ spi->dev.archdata.of_node);
+#endif
+
+ dev_dbg(&spi->dev, "SPI device initialized\n");
+ return 0;
+}
+
+static int aic26_spi_remove(struct spi_device *spi)
+{
+ struct aic26 *aic26 = dev_get_drvdata(&spi->dev);
+
+ kfree(aic26);
+
+ return 0;
+}
+
+static struct spi_driver aic26_spi = {
+ .driver = {
+ .name = "tlv320aic26",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic26_spi_probe,
+ .remove = aic26_spi_remove,
+};
+
+static int __init aic26_init(void)
+{
+ return spi_register_driver(&aic26_spi);
+}
+module_init(aic26_init);
+
+static void __exit aic26_exit(void)
+{
+ spi_unregister_driver(&aic26_spi);
+}
+module_exit(aic26_exit);
diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h
new file mode 100644
index 0000000..786ba16
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic26.h
@@ -0,0 +1,96 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * register definitions
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#ifndef _TLV320AIC16_H_
+#define _TLV320AIC16_H_
+
+/* AIC26 Registers */
+#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5))
+#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5))
+#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset)
+#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0)
+
+/* Page 0: Auxillary data registers */
+#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05)
+#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06)
+#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07)
+#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09)
+#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A)
+
+/* Page 1: Auxillary control registers */
+#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00)
+#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01)
+#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03)
+#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04)
+
+/* Page 2: Audio control registers */
+#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00)
+#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01)
+#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02)
+#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03)
+#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04)
+#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05)
+#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06)
+
+#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07)
+#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08)
+#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09)
+#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A)
+#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B)
+#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C)
+#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D)
+#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E)
+#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F)
+#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10)
+#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11)
+#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12)
+#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13)
+#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14)
+#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15)
+#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16)
+#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17)
+#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18)
+#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19)
+#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A)
+
+#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B)
+#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C)
+#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D)
+#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E)
+
+/* fsref dividers; used in register 'Audio Control 1' */
+enum aic26_divisors {
+ AIC26_DIV_1 = 0,
+ AIC26_DIV_1_5 = 1,
+ AIC26_DIV_2 = 2,
+ AIC26_DIV_3 = 3,
+ AIC26_DIV_4 = 4,
+ AIC26_DIV_5 = 5,
+ AIC26_DIV_5_5 = 6,
+ AIC26_DIV_6 = 7,
+};
+
+/* Digital data format */
+enum aic26_datfm {
+ AIC26_DATFM_I2S = 0 << 8,
+ AIC26_DATFM_DSP = 1 << 8,
+ AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */
+ AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */
+};
+
+/* Sample word length in bits; used in register 'Audio Control 1' */
+enum aic26_wlen {
+ AIC26_WLEN_16 = 0 << 10,
+ AIC26_WLEN_20 = 1 << 10,
+ AIC26_WLEN_24 = 2 << 10,
+ AIC26_WLEN_32 = 3 << 10,
+};
+
+extern struct snd_soc_dai aic26_dai;
+extern struct snd_soc_codec_device aic26_soc_codec_dev;
+
+#endif /* _TLV320AIC16_H_ */
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
new file mode 100644
index 0000000..cff276e
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -0,0 +1,1346 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * Based on sound/soc/codecs/wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ * The AIC3X is a driver for a low power stereo audio
+ * codecs aic31, aic32, aic33.
+ *
+ * It supports full aic33 codec functionality.
+ * The compatibility with aic32, aic31 is as follows:
+ * aic32 | aic31
+ * ---------------------------------------
+ * MONO_LOUT -> N/A | MONO_LOUT -> N/A
+ * | IN1L -> LINE1L
+ * | IN1R -> LINE1R
+ * | IN2L -> LINE2L
+ * | IN2R -> LINE2R
+ * | MIC3L/R -> N/A
+ * truncated internal functionality in
+ * accordance with documentation
+ * ---------------------------------------
+ *
+ * Hence the machine layer should disable unsupported inputs/outputs by
+ * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/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 "tlv320aic3x.h"
+
+#define AIC3X_VERSION "0.2"
+
+/* codec private data */
+struct aic3x_priv {
+ unsigned int sysclk;
+ int master;
+};
+
+/*
+ * AIC3X register cache
+ * We can't read the AIC3X register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = {
+ 0x00, 0x00, 0x00, 0x10, /* 0 */
+ 0x04, 0x00, 0x00, 0x00, /* 4 */
+ 0x00, 0x00, 0x00, 0x01, /* 8 */
+ 0x00, 0x00, 0x00, 0x80, /* 12 */
+ 0x80, 0xff, 0xff, 0x78, /* 16 */
+ 0x78, 0x78, 0x78, 0x78, /* 20 */
+ 0x78, 0x00, 0x00, 0xfe, /* 24 */
+ 0x00, 0x00, 0xfe, 0x00, /* 28 */
+ 0x18, 0x18, 0x00, 0x00, /* 32 */
+ 0x00, 0x00, 0x00, 0x00, /* 36 */
+ 0x00, 0x00, 0x00, 0x80, /* 40 */
+ 0x80, 0x00, 0x00, 0x00, /* 44 */
+ 0x00, 0x00, 0x00, 0x04, /* 48 */
+ 0x00, 0x00, 0x00, 0x00, /* 52 */
+ 0x00, 0x00, 0x04, 0x00, /* 56 */
+ 0x00, 0x00, 0x00, 0x00, /* 60 */
+ 0x00, 0x04, 0x00, 0x00, /* 64 */
+ 0x00, 0x00, 0x00, 0x00, /* 68 */
+ 0x04, 0x00, 0x00, 0x00, /* 72 */
+ 0x00, 0x00, 0x00, 0x00, /* 76 */
+ 0x00, 0x00, 0x00, 0x00, /* 80 */
+ 0x00, 0x00, 0x00, 0x00, /* 84 */
+ 0x00, 0x00, 0x00, 0x00, /* 88 */
+ 0x00, 0x00, 0x00, 0x00, /* 92 */
+ 0x00, 0x00, 0x00, 0x00, /* 96 */
+ 0x00, 0x00, 0x02, /* 100 */
+};
+
+/*
+ * read aic3x register cache
+ */
+static inline unsigned int aic3x_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= AIC3X_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write aic3x register cache
+ */
+static inline void aic3x_write_reg_cache(struct snd_soc_codec *codec,
+ u8 reg, u8 value)
+{
+ u8 *cache = codec->reg_cache;
+ if (reg >= AIC3X_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the aic3x register space
+ */
+static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D8 aic3x register offset
+ * D7...D0 register data
+ */
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ aic3x_write_reg_cache(codec, data[0], data[1]);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * read from the aic3x register space
+ */
+static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
+ u8 *value)
+{
+ *value = reg & 0xff;
+ if (codec->hw_read(codec->control_data, value, 1) != 1)
+ return -EIO;
+
+ aic3x_write_reg_cache(codec, reg, *value);
+ return 0;
+}
+
+#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) }
+
+/*
+ * All input lines are connected when !0xf and disconnected with 0xf bit field,
+ * so we have to use specific dapm_put call for input mixer
+ */
+static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0x0f;
+ int mask = (kcontrol->private_value >> 16) & 0xff;
+ int invert = (kcontrol->private_value >> 24) & 0x01;
+ unsigned short val, val_mask;
+ int ret;
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ mask = 0xf;
+ if (val)
+ val = mask;
+
+ if (invert)
+ val = mask - val;
+ val_mask = mask << shift;
+ val = val << shift;
+
+ mutex_lock(&widget->codec->mutex);
+
+ if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ /* found, now check type */
+ found = 1;
+ if (val)
+ /* new connection */
+ path->connect = invert ? 0 : 1;
+ else
+ /* old connection must be powered down */
+ path->connect = invert ? 1 : 0;
+ break;
+ }
+
+ if (found)
+ snd_soc_dapm_sync(widget->codec);
+ }
+
+ ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+ mutex_unlock(&widget->codec->mutex);
+ return ret;
+}
+
+static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" };
+static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" };
+static const char *aic3x_left_hpcom_mux[] =
+ { "differential of HPLOUT", "constant VCM", "single-ended" };
+static const char *aic3x_right_hpcom_mux[] =
+ { "differential of HPROUT", "constant VCM", "single-ended",
+ "differential of HPLCOM", "external feedback" };
+static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" };
+static const char *aic3x_adc_hpf[] =
+ { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" };
+
+#define LDAC_ENUM 0
+#define RDAC_ENUM 1
+#define LHPCOM_ENUM 2
+#define RHPCOM_ENUM 3
+#define LINE1L_ENUM 4
+#define LINE1R_ENUM 5
+#define LINE2L_ENUM 6
+#define LINE2R_ENUM 7
+#define ADC_HPF_ENUM 8
+
+static const struct soc_enum aic3x_enum[] = {
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
+ SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux),
+ SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux),
+ SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux),
+ SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+ SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf),
+};
+
+static const struct snd_kcontrol_new aic3x_snd_controls[] = {
+ /* Output */
+ SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL,
+ DACR1_2_RLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Line DAC Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("Line PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL,
+ PGAR_2_RLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Line Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL,
+ LINE2R_2_RLOPM_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL,
+ DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1),
+ SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0),
+ SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL,
+ PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL,
+ LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL,
+ DACR1_2_HPROUT_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("HP PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL,
+ PGAR_2_HPROUT_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL,
+ LINE2R_2_HPROUT_VOL, 0, 0x7f, 1),
+
+ SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL,
+ DACR1_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3,
+ 0x01, 0),
+ SOC_DOUBLE_R("HPCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL,
+ PGAR_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL,
+ LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1),
+
+ /*
+ * Note: enable Automatic input Gain Controller with care. It can
+ * adjust PGA to max value when ADC is on and will never go back.
+ */
+ SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0),
+
+ /* Input */
+ SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0),
+ SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
+
+ SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
+};
+
+/* add non dapm controls */
+static int aic3x_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&aic3x_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Left DAC Mux */
+static const struct snd_kcontrol_new aic3x_left_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]);
+
+/* Right DAC Mux */
+static const struct snd_kcontrol_new aic3x_right_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]);
+
+/* Left HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]);
+
+/* Right HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]);
+
+/* Left DAC_L1 Mixer */
+static const struct snd_kcontrol_new aic3x_left_dac_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", DACL1_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right DAC_R1 Mixer */
+static const struct snd_kcontrol_new aic3x_right_dac_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", DACR1_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", DACR1_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left PGA Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1),
+};
+
+/* Right PGA Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
+ SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1),
+ SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1),
+};
+
+/* Left Line1 Mux */
+static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
+
+/* Right Line1 Mux */
+static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
+
+/* Left Line2 Mux */
+static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]);
+
+/* Right Line2 Mux */
+static const struct snd_kcontrol_new aic3x_right_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]);
+
+/* Left PGA Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", PGAL_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right PGA Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", PGAR_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", PGAR_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left Line2 Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_left_line2_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right Line2 Bypass Mixer */
+static const struct snd_kcontrol_new aic3x_right_line2_bp_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("Mono Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HP Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0),
+ SOC_DAPM_SINGLE("HPCOM Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ /* Left DAC to Left Outputs */
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0),
+ SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_dac_mux_controls),
+ SND_SOC_DAPM_MIXER("Left DAC_L1 Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_dac_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_dac_mixer_controls)),
+ SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Right DAC to Right Outputs */
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0),
+ SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_dac_mux_controls),
+ SND_SOC_DAPM_MIXER("Right DAC_R1 Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_dac_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_dac_mixer_controls)),
+ SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_hpcom_mux_controls),
+ SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0),
+
+ /* Mono Output */
+ SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0),
+
+ /* Left Inputs to Left ADC */
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_pga_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_pga_mixer_controls)),
+ SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line2_mux_controls),
+
+ /* Right Inputs to Right ADC */
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+ LINE1R_2_RADC_CTRL, 2, 0),
+ SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_pga_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
+ SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line1_mux_controls),
+ SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line2_mux_controls),
+
+ /*
+ * Not a real mic bias widget but similar function. This is for dynamic
+ * control of GPIO1 digital mic modulator clock output function when
+ * using digital mic.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk",
+ AIC3X_GPIO1_REG, 4, 0xf,
+ AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK,
+ AIC3X_GPIO1_FUNC_DISABLED),
+
+ /*
+ * Also similar function like mic bias. Selects digital mic with
+ * configurable oversampling rate instead of ADC converter.
+ */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32",
+ AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0),
+
+ /* Mic Bias */
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V",
+ MICBIAS_CTRL, 6, 3, 1, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V",
+ MICBIAS_CTRL, 6, 3, 2, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD",
+ MICBIAS_CTRL, 6, 3, 3, 0),
+
+ /* Left PGA to Left Output bypass */
+ SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_pga_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_pga_bp_mixer_controls)),
+
+ /* Right PGA to Right Output bypass */
+ SND_SOC_DAPM_MIXER("Right PGA Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_pga_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_pga_bp_mixer_controls)),
+
+ /* Left Line2 to Left Output bypass */
+ SND_SOC_DAPM_MIXER("Left Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_left_line2_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_left_line2_bp_mixer_controls)),
+
+ /* Right Line2 to Right Output bypass */
+ SND_SOC_DAPM_MIXER("Right Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0,
+ &aic3x_right_line2_bp_mixer_controls[0],
+ ARRAY_SIZE(aic3x_right_line2_bp_mixer_controls)),
+
+ SND_SOC_DAPM_OUTPUT("LLOUT"),
+ SND_SOC_DAPM_OUTPUT("RLOUT"),
+ SND_SOC_DAPM_OUTPUT("MONO_LOUT"),
+ SND_SOC_DAPM_OUTPUT("HPLOUT"),
+ SND_SOC_DAPM_OUTPUT("HPROUT"),
+ SND_SOC_DAPM_OUTPUT("HPLCOM"),
+ SND_SOC_DAPM_OUTPUT("HPRCOM"),
+
+ SND_SOC_DAPM_INPUT("MIC3L"),
+ SND_SOC_DAPM_INPUT("MIC3R"),
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* Left Output */
+ {"Left DAC Mux", "DAC_L1", "Left DAC"},
+ {"Left DAC Mux", "DAC_L2", "Left DAC"},
+ {"Left DAC Mux", "DAC_L3", "Left DAC"},
+
+ {"Left DAC_L1 Mixer", "Line Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "Mono Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "HP Switch", "Left DAC Mux"},
+ {"Left DAC_L1 Mixer", "HPCOM Switch", "Left DAC Mux"},
+ {"Left Line Out", NULL, "Left DAC Mux"},
+ {"Left HP Out", NULL, "Left DAC Mux"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left DAC_L1 Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left DAC_L1 Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left DAC_L1 Mixer"},
+
+ {"Left Line Out", NULL, "Left DAC_L1 Mixer"},
+ {"Mono Out", NULL, "Left DAC_L1 Mixer"},
+ {"Left HP Out", NULL, "Left DAC_L1 Mixer"},
+ {"Left HP Com", NULL, "Left HPCOM Mux"},
+
+ {"LLOUT", NULL, "Left Line Out"},
+ {"LLOUT", NULL, "Left Line Out"},
+ {"HPLOUT", NULL, "Left HP Out"},
+ {"HPLCOM", NULL, "Left HP Com"},
+
+ /* Right Output */
+ {"Right DAC Mux", "DAC_R1", "Right DAC"},
+ {"Right DAC Mux", "DAC_R2", "Right DAC"},
+ {"Right DAC Mux", "DAC_R3", "Right DAC"},
+
+ {"Right DAC_R1 Mixer", "Line Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "Mono Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "HP Switch", "Right DAC Mux"},
+ {"Right DAC_R1 Mixer", "HPCOM Switch", "Right DAC Mux"},
+ {"Right Line Out", NULL, "Right DAC Mux"},
+ {"Right HP Out", NULL, "Right DAC Mux"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right DAC_R1 Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right DAC_R1 Mixer"},
+
+ {"Right Line Out", NULL, "Right DAC_R1 Mixer"},
+ {"Mono Out", NULL, "Right DAC_R1 Mixer"},
+ {"Right HP Out", NULL, "Right DAC_R1 Mixer"},
+ {"Right HP Com", NULL, "Right HPCOM Mux"},
+
+ {"RLOUT", NULL, "Right Line Out"},
+ {"RLOUT", NULL, "Right Line Out"},
+ {"HPROUT", NULL, "Right HP Out"},
+ {"HPRCOM", NULL, "Right HP Com"},
+
+ /* Mono Output */
+ {"MONO_LOUT", NULL, "Mono Out"},
+ {"MONO_LOUT", NULL, "Mono Out"},
+
+ /* Left Input */
+ {"Left Line1L Mux", "single-ended", "LINE1L"},
+ {"Left Line1L Mux", "differential", "LINE1L"},
+
+ {"Left Line2L Mux", "single-ended", "LINE2L"},
+ {"Left Line2L Mux", "differential", "LINE2L"},
+
+ {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"},
+ {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"},
+ {"Left PGA Mixer", "Mic3L Switch", "MIC3L"},
+
+ {"Left ADC", NULL, "Left PGA Mixer"},
+ {"Left ADC", NULL, "GPIO1 dmic modclk"},
+
+ /* Right Input */
+ {"Right Line1R Mux", "single-ended", "LINE1R"},
+ {"Right Line1R Mux", "differential", "LINE1R"},
+
+ {"Right Line2R Mux", "single-ended", "LINE2R"},
+ {"Right Line2R Mux", "differential", "LINE2R"},
+
+ {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"},
+ {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"},
+ {"Right PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+ {"Right ADC", NULL, "Right PGA Mixer"},
+ {"Right ADC", NULL, "GPIO1 dmic modclk"},
+
+ /* Left PGA Bypass */
+ {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "Mono Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "HP Switch", "Left PGA Mixer"},
+ {"Left PGA Bypass Mixer", "HPCOM Switch", "Left PGA Mixer"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left PGA Bypass Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left PGA Bypass Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left PGA Bypass Mixer"},
+
+ {"Left Line Out", NULL, "Left PGA Bypass Mixer"},
+ {"Mono Out", NULL, "Left PGA Bypass Mixer"},
+ {"Left HP Out", NULL, "Left PGA Bypass Mixer"},
+
+ /* Right PGA Bypass */
+ {"Right PGA Bypass Mixer", "Line Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "Mono Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "HP Switch", "Right PGA Mixer"},
+ {"Right PGA Bypass Mixer", "HPCOM Switch", "Right PGA Mixer"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right PGA Bypass Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right PGA Bypass Mixer"},
+
+ {"Right Line Out", NULL, "Right PGA Bypass Mixer"},
+ {"Mono Out", NULL, "Right PGA Bypass Mixer"},
+ {"Right HP Out", NULL, "Right PGA Bypass Mixer"},
+
+ /* Left Line2 Bypass */
+ {"Left Line2 Bypass Mixer", "Line Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "Mono Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "HP Switch", "Left Line2L Mux"},
+ {"Left Line2 Bypass Mixer", "HPCOM Switch", "Left Line2L Mux"},
+
+ {"Left HPCOM Mux", "differential of HPLOUT", "Left Line2 Bypass Mixer"},
+ {"Left HPCOM Mux", "constant VCM", "Left Line2 Bypass Mixer"},
+ {"Left HPCOM Mux", "single-ended", "Left Line2 Bypass Mixer"},
+
+ {"Left Line Out", NULL, "Left Line2 Bypass Mixer"},
+ {"Mono Out", NULL, "Left Line2 Bypass Mixer"},
+ {"Left HP Out", NULL, "Left Line2 Bypass Mixer"},
+
+ /* Right Line2 Bypass */
+ {"Right Line2 Bypass Mixer", "Line Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "Mono Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "HP Switch", "Right Line2R Mux"},
+ {"Right Line2 Bypass Mixer", "HPCOM Switch", "Right Line2R Mux"},
+
+ {"Right HPCOM Mux", "differential of HPROUT", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "constant VCM", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "single-ended", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "differential of HPLCOM", "Right Line2 Bypass Mixer"},
+ {"Right HPCOM Mux", "external feedback", "Right Line2 Bypass Mixer"},
+
+ {"Right Line Out", NULL, "Right Line2 Bypass Mixer"},
+ {"Mono Out", NULL, "Right Line2 Bypass Mixer"},
+ {"Right HP Out", NULL, "Right Line2 Bypass Mixer"},
+
+ /*
+ * Logical path between digital mic enable and GPIO1 modulator clock
+ * output function
+ */
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 128"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 64"},
+ {"GPIO1 dmic modclk", NULL, "DMic Rate 32"},
+};
+
+static int aic3x_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int aic3x_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 aic3x_priv *aic3x = codec->private_data;
+ int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
+ u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
+ u16 pll_d = 1;
+
+ /* select data word length */
+ data =
+ aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4));
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ data |= (0x01 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ data |= (0x02 << 4);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data |= (0x03 << 4);
+ break;
+ }
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data);
+
+ /* Fsref can be 44100 or 48000 */
+ fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+
+ /* Try to find a value for Q which allows us to bypass the PLL and
+ * generate CODEC_CLK directly. */
+ for (pll_q = 2; pll_q < 18; pll_q++)
+ if (aic3x->sysclk / (128 * pll_q) == fsref) {
+ bypass_pll = 1;
+ break;
+ }
+
+ if (bypass_pll) {
+ pll_q &= 0xf;
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT);
+ aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV);
+ } else
+ aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV);
+
+ /* Route Left DAC to left channel input and
+ * right DAC to right channel input */
+ data = (LDAC2LCH | RDAC2RCH);
+ data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+ if (params_rate(params) >= 64000)
+ data |= DUAL_RATE_MODE;
+ aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data);
+
+ /* codec sample rate select */
+ data = (fsref * 20) / params_rate(params);
+ if (params_rate(params) < 64000)
+ data /= 2;
+ data /= 5;
+ data -= 2;
+ data |= (data << 4);
+ aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data);
+
+ if (bypass_pll)
+ return 0;
+
+ /* Use PLL
+ * find an apropriate setup for j, d, r and p by iterating over
+ * p and r - j and d are calculated for each fraction.
+ * Up to 128 values are probed, the closest one wins the game.
+ * The sysclk is divided by 1000 to prevent integer overflows.
+ */
+ codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000);
+
+ for (r = 1; r <= 16; r++)
+ for (p = 1; p <= 8; p++) {
+ int clk, tmp = (codec_clk * pll_r * 10) / pll_p;
+ u8 j = tmp / 10000;
+ u16 d = tmp % 10000;
+
+ if (j > 63)
+ continue;
+
+ if (d != 0 && aic3x->sysclk < 10000000)
+ continue;
+
+ /* This is actually 1000 * ((j + (d/10000)) * r) / p
+ * The term had to be converted to get rid of the
+ * division by 10000 */
+ clk = ((10000 * j * r) + (d * r)) / (10 * p);
+
+ /* check whether this values get closer than the best
+ * ones we had before */
+ if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
+ pll_j = j; pll_d = d; pll_r = r; pll_p = p;
+ last_clk = clk;
+ }
+
+ /* Early exit for exact matches */
+ if (clk == codec_clk)
+ break;
+ }
+
+ if (last_clk == 0) {
+ printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
+ return -EINVAL;
+ }
+
+ data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT));
+ aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGC_REG, (pll_d >> 6) << PLLD_MSB_SHIFT);
+ aic3x_write(codec, AIC3X_PLL_PROGD_REG,
+ (pll_d & 0x3F) << PLLD_LSB_SHIFT);
+
+ return 0;
+}
+
+static int aic3x_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON;
+ u8 rdac_reg = aic3x_read_reg_cache(codec, RDAC_VOL) & ~MUTE_ON;
+
+ if (mute) {
+ aic3x_write(codec, LDAC_VOL, ldac_reg | MUTE_ON);
+ aic3x_write(codec, RDAC_VOL, rdac_reg | MUTE_ON);
+ } else {
+ aic3x_write(codec, LDAC_VOL, ldac_reg);
+ aic3x_write(codec, RDAC_VOL, rdac_reg);
+ }
+
+ return 0;
+}
+
+static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic3x_priv *aic3x = codec->private_data;
+
+ aic3x->sysclk = freq;
+ return 0;
+}
+
+static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic3x_priv *aic3x = codec->private_data;
+ u8 iface_areg, iface_breg;
+
+ iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f;
+ iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aic3x->master = 1;
+ iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aic3x->master = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * match both interface format and signal polarities since they
+ * are fixed
+ */
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_INV_MASK)) {
+ case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
+ break;
+ case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
+ iface_breg |= (0x01 << 6);
+ break;
+ case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x02 << 6);
+ break;
+ case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
+ iface_breg |= (0x03 << 6);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg);
+ aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg);
+
+ return 0;
+}
+
+static int aic3x_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct aic3x_priv *aic3x = codec->private_data;
+ u8 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* all power is driven by DAPM system */
+ if (aic3x->master) {
+ /* enable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg | PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /*
+ * all power is driven by DAPM system,
+ * so output power is safe if bypass was set
+ */
+ if (aic3x->master) {
+ /* disable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg & ~PLL_ENABLE);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* force all power off */
+ reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL);
+ aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL);
+ aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, DAC_PWR);
+ aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON));
+
+ reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL);
+ aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, HPROUT_CTRL);
+ aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL);
+ aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL);
+ aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL);
+ aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON);
+
+ reg = aic3x_read_reg_cache(codec, LLOPM_CTRL);
+ aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON);
+ reg = aic3x_read_reg_cache(codec, RLOPM_CTRL);
+ aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON);
+
+ if (aic3x->master) {
+ /* disable pll */
+ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG);
+ aic3x_write(codec, AIC3X_PLL_PROGA_REG,
+ reg & ~PLL_ENABLE);
+ }
+ break;
+ }
+ codec->bias_level = level;
+
+ return 0;
+}
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 bit = gpio ? 3: 0;
+ u8 val = aic3x_read_reg_cache(codec, reg) & ~(1 << bit);
+ aic3x_write(codec, reg, val | (!!state << bit));
+}
+EXPORT_SYMBOL_GPL(aic3x_set_gpio);
+
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio)
+{
+ u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+ u8 val, bit = gpio ? 2: 1;
+
+ aic3x_read(codec, reg, &val);
+ return (val >> bit) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_get_gpio);
+
+int aic3x_headset_detected(struct snd_soc_codec *codec)
+{
+ u8 val;
+ aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val);
+ return (val >> 2) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_headset_detected);
+
+#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000
+#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai aic3x_dai = {
+ .name = "tlv320aic3x",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC3X_RATES,
+ .formats = AIC3X_FORMATS,},
+ .ops = {
+ .hw_params = aic3x_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = aic3x_mute,
+ .set_sysclk = aic3x_set_dai_sysclk,
+ .set_fmt = aic3x_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(aic3x_dai);
+
+static int aic3x_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;
+
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int aic3x_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];
+ u8 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) {
+ data[0] = i;
+ data[1] = cache[i];
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ aic3x_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+/*
+ * initialise the AIC3X driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int aic3x_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct aic3x_setup_data *setup = socdev->codec_data;
+ int reg, ret = 0;
+
+ codec->name = "tlv320aic3x";
+ codec->owner = THIS_MODULE;
+ codec->read = aic3x_read_reg_cache;
+ codec->write = aic3x_write;
+ codec->set_bias_level = aic3x_set_bias_level;
+ codec->dai = &aic3x_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(aic3x_reg);
+ codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT);
+ aic3x_write(codec, AIC3X_RESET, SOFT_RESET);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "aic3x: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* DAC default volume and mute */
+ aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON);
+ aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON);
+
+ /* DAC to HP default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Line Out default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ /* DAC to Mono Line Out default volume and route to Output mixer */
+ aic3x_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+ aic3x_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+
+ /* unmute all outputs */
+ reg = aic3x_read_reg_cache(codec, LLOPM_CTRL);
+ aic3x_write(codec, LLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, RLOPM_CTRL);
+ aic3x_write(codec, RLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL);
+ aic3x_write(codec, MONOLOPM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL);
+ aic3x_write(codec, HPLOUT_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPROUT_CTRL);
+ aic3x_write(codec, HPROUT_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL);
+ aic3x_write(codec, HPLCOM_CTRL, reg | UNMUTE);
+ reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL);
+ aic3x_write(codec, HPRCOM_CTRL, reg | UNMUTE);
+
+ /* ADC default volume and unmute */
+ aic3x_write(codec, LADC_VOL, DEFAULT_GAIN);
+ aic3x_write(codec, RADC_VOL, DEFAULT_GAIN);
+ /* By default route Line1 to ADC PGA mixer */
+ aic3x_write(codec, LINE1L_2_LADC_CTRL, 0x0);
+ aic3x_write(codec, LINE1R_2_RADC_CTRL, 0x0);
+
+ /* PGA to HP Bypass default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* PGA to Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL);
+ /* PGA to Mono Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ /* Line2 to HP Bypass default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL);
+ /* Line2 Line Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL);
+ /* Line2 to Mono Out default volume, disconnect from Output Mixer */
+ aic3x_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL);
+ aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+ /* off, with power on */
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* setup GPIO functions */
+ aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4);
+ aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4);
+
+ aic3x_add_controls(codec);
+ aic3x_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "aic3x: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *aic3x_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * AIC3X 2 wire address can be up to 4 devices with device addresses
+ * 0x18, 0x19, 0x1A, 0x1B
+ */
+
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int aic3x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = aic3x_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = aic3x_init(socdev);
+ if (ret < 0)
+ printk(KERN_ERR "aic3x: failed to initialise AIC3X\n");
+ return ret;
+}
+
+static int aic3x_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id aic3x_i2c_id[] = {
+ { "tlv320aic3x", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
+
+/* machine i2c codec control layer */
+static struct i2c_driver aic3x_i2c_driver = {
+ .driver = {
+ .name = "aic3x I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic3x_i2c_probe,
+ .remove = aic3x_i2c_remove,
+ .id_table = aic3x_i2c_id,
+};
+
+static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len)
+{
+ value[0] = i2c_smbus_read_byte_data(client, value[0]);
+ return (len == 1);
+}
+
+static int aic3x_add_i2c_device(struct platform_device *pdev,
+ const struct aic3x_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&aic3x_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&aic3x_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int aic3x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct aic3x_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct aic3x_priv *aic3x;
+ int ret = 0;
+
+ printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
+ if (aic3x == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = aic3x;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ aic3x_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->hw_read = (hw_read_t) aic3x_i2c_read;
+ ret = aic3x_add_i2c_device(pdev, setup);
+ }
+#else
+ /* Add other interfaces here */
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+static int aic3x_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* power down chip */
+ if (codec->control_data)
+ aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&aic3x_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_aic3x = {
+ .probe = aic3x_probe,
+ .remove = aic3x_remove,
+ .suspend = aic3x_suspend,
+ .resume = aic3x_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h
new file mode 100644
index 0000000..00a195a
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic3x.h
@@ -0,0 +1,235 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AIC3X_H
+#define _AIC3X_H
+
+/* AIC3X register space */
+#define AIC3X_CACHEREGNUM 103
+
+/* Page select register */
+#define AIC3X_PAGE_SELECT 0
+/* Software reset register */
+#define AIC3X_RESET 1
+/* Codec Sample rate select register */
+#define AIC3X_SAMPLE_RATE_SEL_REG 2
+/* PLL progrramming register A */
+#define AIC3X_PLL_PROGA_REG 3
+/* PLL progrramming register B */
+#define AIC3X_PLL_PROGB_REG 4
+/* PLL progrramming register C */
+#define AIC3X_PLL_PROGC_REG 5
+/* PLL progrramming register D */
+#define AIC3X_PLL_PROGD_REG 6
+/* Codec datapath setup register */
+#define AIC3X_CODEC_DATAPATH_REG 7
+/* Audio serial data interface control register A */
+#define AIC3X_ASD_INTF_CTRLA 8
+/* Audio serial data interface control register B */
+#define AIC3X_ASD_INTF_CTRLB 9
+/* Audio overflow status and PLL R value programming register */
+#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11
+/* Audio codec digital filter control register */
+#define AIC3X_CODEC_DFILT_CTRL 12
+
+/* ADC PGA Gain control registers */
+#define LADC_VOL 15
+#define RADC_VOL 16
+/* MIC3 control registers */
+#define MIC3LR_2_LADC_CTRL 17
+#define MIC3LR_2_RADC_CTRL 18
+/* Line1 Input control registers */
+#define LINE1L_2_LADC_CTRL 19
+#define LINE1R_2_RADC_CTRL 22
+/* Line2 Input control registers */
+#define LINE2L_2_LADC_CTRL 20
+#define LINE2R_2_RADC_CTRL 23
+/* MICBIAS Control Register */
+#define MICBIAS_CTRL 25
+
+/* AGC Control Registers A, B, C */
+#define LAGC_CTRL_A 26
+#define LAGC_CTRL_B 27
+#define LAGC_CTRL_C 28
+#define RAGC_CTRL_A 29
+#define RAGC_CTRL_B 30
+#define RAGC_CTRL_C 31
+
+/* DAC Power and Left High Power Output control registers */
+#define DAC_PWR 37
+#define HPLCOM_CFG 37
+/* Right High Power Output control registers */
+#define HPRCOM_CFG 38
+/* DAC Output Switching control registers */
+#define DAC_LINE_MUX 41
+/* High Power Output Driver Pop Reduction registers */
+#define HPOUT_POP_REDUCTION 42
+/* DAC Digital control registers */
+#define LDAC_VOL 43
+#define RDAC_VOL 44
+/* High Power Output control registers */
+#define LINE2L_2_HPLOUT_VOL 45
+#define LINE2R_2_HPROUT_VOL 62
+#define PGAL_2_HPLOUT_VOL 46
+#define PGAR_2_HPROUT_VOL 63
+#define DACL1_2_HPLOUT_VOL 47
+#define DACR1_2_HPROUT_VOL 64
+#define HPLOUT_CTRL 51
+#define HPROUT_CTRL 65
+/* High Power COM control registers */
+#define LINE2L_2_HPLCOM_VOL 52
+#define LINE2R_2_HPRCOM_VOL 69
+#define PGAL_2_HPLCOM_VOL 53
+#define PGAR_2_HPRCOM_VOL 70
+#define DACL1_2_HPLCOM_VOL 54
+#define DACR1_2_HPRCOM_VOL 71
+#define HPLCOM_CTRL 58
+#define HPRCOM_CTRL 72
+/* Mono Line Output Plus/Minus control registers */
+#define LINE2L_2_MONOLOPM_VOL 73
+#define LINE2R_2_MONOLOPM_VOL 76
+#define PGAL_2_MONOLOPM_VOL 74
+#define PGAR_2_MONOLOPM_VOL 77
+#define DACL1_2_MONOLOPM_VOL 75
+#define DACR1_2_MONOLOPM_VOL 78
+#define MONOLOPM_CTRL 79
+/* Line Output Plus/Minus control registers */
+#define LINE2L_2_LLOPM_VOL 80
+#define LINE2R_2_RLOPM_VOL 90
+#define PGAL_2_LLOPM_VOL 81
+#define PGAR_2_RLOPM_VOL 91
+#define DACL1_2_LLOPM_VOL 82
+#define DACR1_2_RLOPM_VOL 92
+#define LLOPM_CTRL 86
+#define RLOPM_CTRL 93
+/* GPIO/IRQ registers */
+#define AIC3X_STICKY_IRQ_FLAGS_REG 96
+#define AIC3X_RT_IRQ_FLAGS_REG 97
+#define AIC3X_GPIO1_REG 98
+#define AIC3X_GPIO2_REG 99
+#define AIC3X_GPIOA_REG 100
+#define AIC3X_GPIOB_REG 101
+/* Clock generation control register */
+#define AIC3X_CLKGEN_CTRL_REG 102
+
+/* Page select register bits */
+#define PAGE0_SELECT 0
+#define PAGE1_SELECT 1
+
+/* Audio serial data interface control register A bits */
+#define BIT_CLK_MASTER 0x80
+#define WORD_CLK_MASTER 0x40
+
+/* Codec Datapath setup register 7 */
+#define FSREF_44100 (1 << 7)
+#define FSREF_48000 (0 << 7)
+#define DUAL_RATE_MODE ((1 << 5) | (1 << 6))
+#define LDAC2LCH (0x1 << 3)
+#define RDAC2RCH (0x1 << 1)
+
+/* PLL registers bitfields */
+#define PLLP_SHIFT 0
+#define PLLQ_SHIFT 3
+#define PLLR_SHIFT 0
+#define PLLJ_SHIFT 2
+#define PLLD_MSB_SHIFT 0
+#define PLLD_LSB_SHIFT 2
+
+/* Clock generation register bits */
+#define CODEC_CLKIN_PLLDIV 0
+#define CODEC_CLKIN_CLKDIV 1
+#define PLL_CLKIN_SHIFT 4
+#define MCLK_SOURCE 0x0
+#define PLL_CLKDIV_SHIFT 0
+
+/* Software reset register bits */
+#define SOFT_RESET 0x80
+
+/* PLL progrramming register A bits */
+#define PLL_ENABLE 0x80
+
+/* Route bits */
+#define ROUTE_ON 0x80
+
+/* Mute bits */
+#define UNMUTE 0x08
+#define MUTE_ON 0x80
+
+/* Power bits */
+#define LADC_PWR_ON 0x04
+#define RADC_PWR_ON 0x04
+#define LDAC_PWR_ON 0x80
+#define RDAC_PWR_ON 0x40
+#define HPLOUT_PWR_ON 0x01
+#define HPROUT_PWR_ON 0x01
+#define HPLCOM_PWR_ON 0x01
+#define HPRCOM_PWR_ON 0x01
+#define MONOLOPM_PWR_ON 0x01
+#define LLOPM_PWR_ON 0x01
+#define RLOPM_PWR_ON 0x01
+
+#define INVERT_VOL(val) (0x7f - val)
+
+/* Default output volume (inverted) */
+#define DEFAULT_VOL INVERT_VOL(0x50)
+/* Default input volume */
+#define DEFAULT_GAIN 0x20
+
+/* GPIO API */
+enum {
+ AIC3X_GPIO1_FUNC_DISABLED = 0,
+ AIC3X_GPIO1_FUNC_AUDIO_WORDCLK_ADC = 1,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX = 2,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV2 = 3,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV4 = 4,
+ AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV8 = 5,
+ AIC3X_GPIO1_FUNC_SHORT_CIRCUIT_IRQ = 6,
+ AIC3X_GPIO1_FUNC_AGC_NOISE_IRQ = 7,
+ AIC3X_GPIO1_FUNC_INPUT = 8,
+ AIC3X_GPIO1_FUNC_OUTPUT = 9,
+ AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK = 10,
+ AIC3X_GPIO1_FUNC_AUDIO_WORDCLK = 11,
+ AIC3X_GPIO1_FUNC_BUTTON_IRQ = 12,
+ AIC3X_GPIO1_FUNC_HEADSET_DETECT_IRQ = 13,
+ AIC3X_GPIO1_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 14,
+ AIC3X_GPIO1_FUNC_ALL_IRQ = 16
+};
+
+enum {
+ AIC3X_GPIO2_FUNC_DISABLED = 0,
+ AIC3X_GPIO2_FUNC_HEADSET_DETECT_IRQ = 2,
+ AIC3X_GPIO2_FUNC_INPUT = 3,
+ AIC3X_GPIO2_FUNC_OUTPUT = 4,
+ AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT = 5,
+ AIC3X_GPIO2_FUNC_AUDIO_BITCLK = 8,
+ AIC3X_GPIO2_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 9,
+ AIC3X_GPIO2_FUNC_ALL_IRQ = 10,
+ AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_OR_AGC_IRQ = 11,
+ AIC3X_GPIO2_FUNC_HEADSET_OR_BUTTON_PRESS_OR_SHORT_CIRCUIT_IRQ = 12,
+ AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_IRQ = 13,
+ AIC3X_GPIO2_FUNC_AGC_NOISE_IRQ = 14,
+ AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15
+};
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state);
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio);
+int aic3x_headset_detected(struct snd_soc_codec *codec);
+
+struct aic3x_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+ unsigned int gpio_func[2];
+};
+
+extern struct snd_soc_dai aic3x_dai;
+extern struct snd_soc_codec_device soc_codec_dev_aic3x;
+
+#endif /* _AIC3X_H */
diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c
new file mode 100644
index 0000000..a69ee72
--- /dev/null
+++ b/sound/soc/codecs/uda1380.c
@@ -0,0 +1,849 @@
+/*
+ * uda1380.c - Philips UDA1380 ALSA SoC audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>
+ * Improved support for DAPM and audio routing/mixing capabilities,
+ * added TLV support.
+ *
+ * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC
+ * codec model.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ * Copyright 2005 Openedhand Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include "uda1380.h"
+
+#define UDA1380_VERSION "0.6"
+
+/*
+ * uda1380 register cache
+ */
+static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = {
+ 0x0502, 0x0000, 0x0000, 0x3f3f,
+ 0x0202, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0xff00, 0x0000, 0x4800,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x8000, 0x0002, 0x0000,
+};
+
+/*
+ * read uda1380 register cache
+ */
+static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == UDA1380_RESET)
+ return 0;
+ if (reg >= UDA1380_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write uda1380 register cache
+ */
+static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= UDA1380_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the UDA1380 register space
+ */
+static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ /* data is
+ * data[0] is register offset
+ * data[1] is MS byte
+ * data[2] is LS byte
+ */
+ data[0] = reg;
+ data[1] = (value & 0xff00) >> 8;
+ data[2] = value & 0x00ff;
+
+ uda1380_write_reg_cache(codec, reg, value);
+
+ /* the interpolator & decimator regs must only be written when the
+ * codec DAI is active.
+ */
+ if (!codec->active && (reg >= UDA1380_MVOL))
+ return 0;
+ pr_debug("uda1380: hw write %x val %x\n", reg, value);
+ if (codec->hw_write(codec->control_data, data, 3) == 3) {
+ unsigned int val;
+ i2c_master_send(codec->control_data, data, 1);
+ i2c_master_recv(codec->control_data, data, 2);
+ val = (data[0]<<8) | data[1];
+ if (val != value) {
+ pr_debug("uda1380: READ BACK VAL %x\n",
+ (data[0]<<8) | data[1]);
+ return -EIO;
+ }
+ return 0;
+ } else
+ return -EIO;
+}
+
+#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0)
+
+/* declarations of ALSA reg_elem_REAL controls */
+static const char *uda1380_deemp[] = {
+ "None",
+ "32kHz",
+ "44.1kHz",
+ "48kHz",
+ "96kHz",
+};
+static const char *uda1380_input_sel[] = {
+ "Line",
+ "Mic + Line R",
+ "Line L",
+ "Mic",
+};
+static const char *uda1380_output_sel[] = {
+ "DAC",
+ "Analog Mixer",
+};
+static const char *uda1380_spf_mode[] = {
+ "Flat",
+ "Minimum1",
+ "Minimum2",
+ "Maximum"
+};
+static const char *uda1380_capture_sel[] = {
+ "ADC",
+ "Digital Mixer"
+};
+static const char *uda1380_sel_ns[] = {
+ "3rd-order",
+ "5th-order"
+};
+static const char *uda1380_mix_control[] = {
+ "off",
+ "PCM only",
+ "before sound processing",
+ "after sound processing"
+};
+static const char *uda1380_sdet_setting[] = {
+ "3200",
+ "4800",
+ "9600",
+ "19200"
+};
+static const char *uda1380_os_setting[] = {
+ "single-speed",
+ "double-speed (no mixing)",
+ "quad-speed (no mixing)"
+};
+
+static const struct soc_enum uda1380_deemp_enum[] = {
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp),
+ SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp),
+};
+static const struct soc_enum uda1380_input_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */
+static const struct soc_enum uda1380_output_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */
+static const struct soc_enum uda1380_spf_enum =
+ SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */
+static const struct soc_enum uda1380_capture_sel_enum =
+ SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */
+static const struct soc_enum uda1380_sel_ns_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */
+static const struct soc_enum uda1380_mix_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */
+static const struct soc_enum uda1380_sdet_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */
+static const struct soc_enum uda1380_os_enum =
+ SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */
+
+/*
+ * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1);
+
+/*
+ * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored),
+ * from -66 dB in 0.5 dB steps (2 dB steps, really) and
+ * from -52 dB in 0.25 dB steps
+ */
+static const unsigned int mvol_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1),
+ 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0),
+ 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0),
+};
+
+/*
+ * from -72 dB in 1.5 dB steps (6 dB steps really),
+ * from -66 dB in 0.75 dB steps (3 dB steps really),
+ * from -60 dB in 0.5 dB steps (2 dB steps really) and
+ * from -46 dB in 0.25 dB steps
+ */
+static const unsigned int vc_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1),
+ 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0),
+ 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0),
+ 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0),
+};
+
+/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */
+static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0);
+
+/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts
+ * off at 18 dB max) */
+static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0);
+
+/* from -63 to 24 dB in 0.5 dB steps (-128...48) */
+static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1);
+
+/* from 0 to 24 dB in 3 dB steps */
+static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0);
+
+/* from 0 to 30 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0);
+
+static const struct snd_kcontrol_new uda1380_snd_controls[] = {
+ SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */
+ SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */
+ SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */
+ SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */
+ SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */
+ SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */
+ SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */
+/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */
+ SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */
+ SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */
+ SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */
+ SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */
+ SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */
+ SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */
+ SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */
+ SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */
+ SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */
+ SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */
+ SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */
+ SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */
+/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */
+ SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */
+ SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */
+ SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */
+ SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */
+ SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */
+ SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */
+ SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */
+ /* -5.5, -8, -11.5, -14 dBFS */
+ SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0),
+};
+
+/* add non dapm controls */
+static int uda1380_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Input mux */
+static const struct snd_kcontrol_new uda1380_input_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
+
+/* Output mux */
+static const struct snd_kcontrol_new uda1380_output_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_output_sel_enum);
+
+/* Capture mux */
+static const struct snd_kcontrol_new uda1380_capture_mux_control =
+ SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum);
+
+
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_input_mux_control),
+ SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_output_mux_control),
+ SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0,
+ &uda1380_capture_mux_control),
+ SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0),
+ SND_SOC_DAPM_INPUT("VINM"),
+ SND_SOC_DAPM_INPUT("VINL"),
+ SND_SOC_DAPM_INPUT("VINR"),
+ SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("VOUTLHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTRHP"),
+ SND_SOC_DAPM_OUTPUT("VOUTL"),
+ SND_SOC_DAPM_OUTPUT("VOUTR"),
+ SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0),
+ SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* output mux */
+ {"HeadPhone Driver", NULL, "Output Mux"},
+ {"VOUTR", NULL, "Output Mux"},
+ {"VOUTL", NULL, "Output Mux"},
+
+ {"Analog Mixer", NULL, "VINR"},
+ {"Analog Mixer", NULL, "VINL"},
+ {"Analog Mixer", NULL, "DAC"},
+
+ {"Output Mux", "DAC", "DAC"},
+ {"Output Mux", "Analog Mixer", "Analog Mixer"},
+
+ /* {"DAC", "Digital Mixer", "I2S" } */
+
+ /* headphone driver */
+ {"VOUTLHP", NULL, "HeadPhone Driver"},
+ {"VOUTRHP", NULL, "HeadPhone Driver"},
+
+ /* input mux */
+ {"Left ADC", NULL, "Input Mux"},
+ {"Input Mux", "Mic", "Mic LNA"},
+ {"Input Mux", "Mic + Line R", "Mic LNA"},
+ {"Input Mux", "Line L", "Left PGA"},
+ {"Input Mux", "Line", "Left PGA"},
+
+ /* right input */
+ {"Right ADC", "Mic + Line R", "Right PGA"},
+ {"Right ADC", "Line", "Right PGA"},
+
+ /* inputs */
+ {"Mic LNA", NULL, "VINM"},
+ {"Left PGA", NULL, "VINL"},
+ {"Right PGA", NULL, "VINR"},
+};
+
+static int uda1380_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+ ARRAY_SIZE(uda1380_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK);
+
+ /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORI_I2S | R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORI_LSB16 | R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORI_MSB | R01_SFORO_I2S;
+ }
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
+ iface |= R01_SIM;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+/*
+ * Flush reg cache
+ * We can only write the interpolator and decimator registers
+ * when the DAI is being clocked by the CPU DAI. It's up to the
+ * machine and cpu DAI driver to do this before we are called.
+ */
+static int uda1380_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, reg_start, reg_end, clk;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg_start = UDA1380_MVOL;
+ reg_end = UDA1380_MIXER;
+ } else {
+ reg_start = UDA1380_DEC;
+ reg_end = UDA1380_AGC;
+ }
+
+ /* FIXME disable DAC_CLK */
+ clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+ uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK);
+
+ for (reg = reg_start; reg <= reg_end; reg++) {
+ pr_debug("uda1380: flush reg %x val %x:", reg,
+ uda1380_read_reg_cache(codec, reg));
+ uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg));
+ }
+
+ /* FIXME enable DAC_CLK */
+ uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK);
+
+ return 0;
+}
+
+static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* set WSPLL power and divider if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ int rate = params_rate(params);
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ clk &= ~0x3; /* clear SEL_LOOP_DIV */
+ switch (rate) {
+ case 6250 ... 12500:
+ clk |= 0x0;
+ break;
+ case 12501 ... 25000:
+ clk |= 0x1;
+ break;
+ case 25001 ... 50000:
+ clk |= 0x2;
+ break;
+ case 50001 ... 100000:
+ clk |= 0x3;
+ break;
+ }
+ uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk |= R00_EN_DAC | R00_EN_INT;
+ else
+ clk |= R00_EN_ADC | R00_EN_DEC;
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+ return 0;
+}
+
+static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+ /* shut down WSPLL power if running from this clock */
+ if (clk & R00_DAC_CLK) {
+ u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+ uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk &= ~(R00_EN_DAC | R00_EN_INT);
+ else
+ clk &= ~(R00_EN_ADC | R00_EN_DEC);
+
+ uda1380_write(codec, UDA1380_CLK, clk);
+}
+
+static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM;
+
+ /* FIXME: mute(codec,0) is called when the magician clock is already
+ * set to WSPLL, but for some unknown reason writing to interpolator
+ * registers works only when clocked by SYSCLK */
+ u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+ uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk);
+ if (mute)
+ uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM);
+ else
+ uda1380_write(codec, UDA1380_DEEMP, mute_reg);
+ uda1380_write(codec, UDA1380_CLK, clk);
+ return 0;
+}
+
+static int uda1380_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ uda1380_write(codec, UDA1380_PM, R02_PON_BIAS);
+ break;
+ case SND_SOC_BIAS_OFF:
+ uda1380_write(codec, UDA1380_PM, 0x0);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai uda1380_dai[] = {
+{
+ .name = "UDA1380",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .digital_mute = uda1380_mute,
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+{ /* playback only - dual interface */
+ .name = "UDA1380",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .digital_mute = uda1380_mute,
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+{ /* capture only - dual interface*/
+ .name = "UDA1380",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = UDA1380_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .prepare = uda1380_pcm_prepare,
+ },
+ .dai_ops = {
+ .set_fmt = uda1380_set_dai_fmt,
+ },
+},
+};
+EXPORT_SYMBOL_GPL(uda1380_dai);
+
+static int uda1380_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int uda1380_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ uda1380_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the UDA1380 driver
+ * register mixer and dsp interfaces with the kernel
+ */
+static int uda1380_init(struct snd_soc_device *socdev, int dac_clk)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "UDA1380";
+ codec->owner = THIS_MODULE;
+ codec->read = uda1380_read_reg_cache;
+ codec->write = uda1380_write;
+ codec->set_bias_level = uda1380_set_bias_level;
+ codec->dai = uda1380_dai;
+ codec->num_dai = ARRAY_SIZE(uda1380_dai);
+ codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+ codec->reg_cache_size = ARRAY_SIZE(uda1380_reg);
+ codec->reg_cache_step = 1;
+ uda1380_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ pr_err("uda1380: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ /* set clock input */
+ switch (dac_clk) {
+ case UDA1380_DAC_CLK_SYSCLK:
+ uda1380_write(codec, UDA1380_CLK, 0);
+ break;
+ case UDA1380_DAC_CLK_WSPLL:
+ uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK);
+ break;
+ }
+
+ /* uda1380 init */
+ uda1380_add_controls(codec);
+ uda1380_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ pr_err("uda1380: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *uda1380_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static int uda1380_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = uda1380_socdev;
+ struct uda1380_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = uda1380_init(socdev, setup->dac_clk);
+ if (ret < 0)
+ pr_err("uda1380: failed to initialise UDA1380\n");
+
+ return ret;
+}
+
+static int uda1380_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id uda1380_i2c_id[] = {
+ { "uda1380", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id);
+
+static struct i2c_driver uda1380_i2c_driver = {
+ .driver = {
+ .name = "UDA1380 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = uda1380_i2c_probe,
+ .remove = uda1380_i2c_remove,
+ .id_table = uda1380_i2c_id,
+};
+
+static int uda1380_add_i2c_device(struct platform_device *pdev,
+ const struct uda1380_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&uda1380_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "uda1380", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&uda1380_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int uda1380_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct uda1380_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret;
+
+ pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ uda1380_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = uda1380_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0)
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int uda1380_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&uda1380_i2c_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_uda1380 = {
+ .probe = uda1380_probe,
+ .remove = uda1380_remove,
+ .suspend = uda1380_suspend,
+ .resume = uda1380_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380);
+
+MODULE_AUTHOR("Giorgio Padrin");
+MODULE_DESCRIPTION("Audio support for codec Philips UDA1380");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h
new file mode 100644
index 0000000..c55c17a
--- /dev/null
+++ b/sound/soc/codecs/uda1380.h
@@ -0,0 +1,90 @@
+/*
+ * Audio support for Philips UDA1380
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ */
+
+#ifndef _UDA1380_H
+#define _UDA1380_H
+
+#define UDA1380_CLK 0x00
+#define UDA1380_IFACE 0x01
+#define UDA1380_PM 0x02
+#define UDA1380_AMIX 0x03
+#define UDA1380_HP 0x04
+#define UDA1380_MVOL 0x10
+#define UDA1380_MIXVOL 0x11
+#define UDA1380_MODE 0x12
+#define UDA1380_DEEMP 0x13
+#define UDA1380_MIXER 0x14
+#define UDA1380_INTSTAT 0x18
+#define UDA1380_DEC 0x20
+#define UDA1380_PGA 0x21
+#define UDA1380_ADC 0x22
+#define UDA1380_AGC 0x23
+#define UDA1380_DECSTAT 0x28
+#define UDA1380_RESET 0x7f
+
+#define UDA1380_CACHEREGNUM 0x24
+
+/* Register flags */
+#define R00_EN_ADC 0x0800
+#define R00_EN_DEC 0x0400
+#define R00_EN_DAC 0x0200
+#define R00_EN_INT 0x0100
+#define R00_DAC_CLK 0x0010
+#define R01_SFORI_I2S 0x0000
+#define R01_SFORI_LSB16 0x0100
+#define R01_SFORI_LSB18 0x0200
+#define R01_SFORI_LSB20 0x0300
+#define R01_SFORI_MSB 0x0500
+#define R01_SFORI_MASK 0x0700
+#define R01_SFORO_I2S 0x0000
+#define R01_SFORO_LSB16 0x0001
+#define R01_SFORO_LSB18 0x0002
+#define R01_SFORO_LSB20 0x0003
+#define R01_SFORO_LSB24 0x0004
+#define R01_SFORO_MSB 0x0005
+#define R01_SFORO_MASK 0x0007
+#define R01_SEL_SOURCE 0x0040
+#define R01_SIM 0x0010
+#define R02_PON_PLL 0x8000
+#define R02_PON_HP 0x2000
+#define R02_PON_DAC 0x0400
+#define R02_PON_BIAS 0x0100
+#define R02_EN_AVC 0x0080
+#define R02_PON_AVC 0x0040
+#define R02_PON_LNA 0x0010
+#define R02_PON_PGAL 0x0008
+#define R02_PON_ADCL 0x0004
+#define R02_PON_PGAR 0x0002
+#define R02_PON_ADCR 0x0001
+#define R13_MTM 0x4000
+#define R14_SILENCE 0x0080
+#define R14_SDET_ON 0x0040
+#define R21_MT_ADC 0x8000
+#define R22_SEL_LNA 0x0008
+#define R22_SEL_MIC 0x0004
+#define R22_SKIP_DCFIL 0x0002
+#define R23_AGC_EN 0x0001
+
+struct uda1380_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+ int dac_clk;
+#define UDA1380_DAC_CLK_SYSCLK 0
+#define UDA1380_DAC_CLK_WSPLL 1
+};
+
+#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */
+#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */
+#define UDA1380_DAI_CAPTURE 2 /* capture DAI */
+
+extern struct snd_soc_dai uda1380_dai[3];
+extern struct snd_soc_codec_device soc_codec_dev_uda1380;
+
+#endif /* _UDA1380_H */
diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c
new file mode 100644
index 0000000..d8ca2da
--- /dev/null
+++ b/sound/soc/codecs/wm8510.c
@@ -0,0 +1,895 @@
+/*
+ * wm8510.c -- WM8510 ALSA Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8510.h"
+
+#define WM8510_VERSION "0.6"
+
+struct snd_soc_codec_device soc_codec_dev_wm8510;
+
+/*
+ * wm8510 register cache
+ * We can't read the WM8510 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8510_reg[WM8510_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0050, 0x0000, 0x0140, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x00ff,
+ 0x0000, 0x0000, 0x0100, 0x00ff,
+ 0x0000, 0x0000, 0x012c, 0x002c,
+ 0x002c, 0x002c, 0x002c, 0x0000,
+ 0x0032, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0038, 0x000b, 0x0032, 0x0000,
+ 0x0008, 0x000c, 0x0093, 0x00e9,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0003, 0x0010, 0x0000, 0x0000,
+ 0x0000, 0x0002, 0x0001, 0x0000,
+ 0x0000, 0x0000, 0x0039, 0x0000,
+ 0x0001,
+};
+
+#define WM8510_POWER1_BIASEN 0x08
+#define WM8510_POWER1_BUFIOEN 0x10
+
+/*
+ * read wm8510 register cache
+ */
+static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == WM8510_RESET)
+ return 0;
+ if (reg >= WM8510_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8510 register cache
+ */
+static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= WM8510_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8510 register space
+ */
+static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8510 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8510_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0)
+
+static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8510_alc[] = { "ALC", "Limiter" };
+
+static const struct soc_enum wm8510_enum[] = {
+ SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */
+ SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */
+ SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp),
+ SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc),
+};
+
+static const struct snd_kcontrol_new wm8510_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8510_enum[1]),
+SOC_ENUM("ADC Companding", wm8510_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8510_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0),
+
+SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8510_enum[3]),
+SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0),
+SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1),
+SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0),
+SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1),
+};
+
+/* add non dapm controls */
+static int wm8510_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8510_snd_controls[i], codec,
+ NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_boost_controls[] = {
+SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1),
+SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0),
+SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_micpga_controls[] = {
+SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0),
+SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0,
+ &wm8510_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm8510_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0,
+ &wm8510_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8510_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0,
+ &wm8510_micpga_controls[0],
+ ARRAY_SIZE(wm8510_micpga_controls)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0,
+ &wm8510_boost_controls[0],
+ ARRAY_SIZE(wm8510_boost_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Mono output mixer */
+ {"Mono Mixer", "PCM Playback Switch", "DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Speaker output mixer */
+ {"Speaker Mixer", "PCM Playback Switch", "DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+ {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+ /* Outputs */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONOOUT", NULL, "Mono Out"},
+ {"SpkN Out", NULL, "Speaker Mixer"},
+ {"SpkP Out", NULL, "Speaker Mixer"},
+ {"SPKOUTN", NULL, "SpkN Out"},
+ {"SPKOUTP", NULL, "SpkP Out"},
+
+ /* Microphone PGA */
+ {"Mic PGA", "MICN Switch", "MICN"},
+ {"Mic PGA", "MICP Switch", "MICP"},
+ { "Mic PGA", "AUX Switch", "Aux Input" },
+
+ /* Boost Mixer */
+ {"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+ {"Boost Mixer", "Mic Volume", "MICP"},
+ {"Boost Mixer", "Aux Volume", "Aux Input"},
+
+ {"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8510_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets,
+ ARRAY_SIZE(wm8510_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct pll_ {
+ unsigned int pre_div:4; /* prescale - 1 */
+ unsigned int n:4;
+ unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(unsigned int target, unsigned int source)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div.pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div.pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8510 N value %d outwith recommended range!d\n",
+ Ndiv);
+
+ pll_div.n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div.k = K;
+}
+
+static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ if (freq_in == 0 || freq_out == 0) {
+ /* Clock CODEC directly from MCLK */
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
+ wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff);
+
+ /* Turn off PLL */
+ reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
+ wm8510_write(codec, WM8510_POWER1, reg & 0x1df);
+ return 0;
+ }
+
+ pll_factors(freq_out*8, freq_in);
+
+ wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+ wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18);
+ wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
+ wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
+ reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
+ wm8510_write(codec, WM8510_POWER1, reg | 0x020);
+
+ /* Run CODEC from PLL instead of MCLK */
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
+ wm8510_write(codec, WM8510_CLOCK, reg | 0x100);
+
+ return 0;
+}
+
+/*
+ * Configure WM8510 clock dividers.
+ */
+static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8510_OPCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf;
+ wm8510_write(codec, WM8510_GPIO, reg | div);
+ break;
+ case WM8510_MCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f;
+ wm8510_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ case WM8510_ADCCLK:
+ reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7;
+ wm8510_write(codec, WM8510_ADC, reg | div);
+ break;
+ case WM8510_DACCLK:
+ reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7;
+ wm8510_write(codec, WM8510_DAC, reg | div);
+ break;
+ case WM8510_BCLKDIV:
+ reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3;
+ wm8510_write(codec, WM8510_CLOCK, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+ u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0008;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x00018;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0080;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8510_write(codec, WM8510_IFACE, iface);
+ wm8510_write(codec, WM8510_CLOCK, clk);
+ return 0;
+}
+
+static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f;
+ u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0020;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0040;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x0060;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case SNDRV_PCM_RATE_8000:
+ adn |= 0x5 << 1;
+ break;
+ case SNDRV_PCM_RATE_11025:
+ adn |= 0x4 << 1;
+ break;
+ case SNDRV_PCM_RATE_16000:
+ adn |= 0x3 << 1;
+ break;
+ case SNDRV_PCM_RATE_22050:
+ adn |= 0x2 << 1;
+ break;
+ case SNDRV_PCM_RATE_32000:
+ adn |= 0x1 << 1;
+ break;
+ case SNDRV_PCM_RATE_44100:
+ case SNDRV_PCM_RATE_48000:
+ break;
+ }
+
+ wm8510_write(codec, WM8510_IFACE, iface);
+ wm8510_write(codec, WM8510_ADD, adn);
+ return 0;
+}
+
+static int wm8510_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf;
+
+ if (mute)
+ wm8510_write(codec, WM8510_DAC, mute_reg | 0x40);
+ else
+ wm8510_write(codec, WM8510_DAC, mute_reg);
+ return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8510_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 0x1; /* VMID 50k */
+ wm8510_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN;
+
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ wm8510_write(codec, WM8510_POWER1, power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ wm8510_write(codec, WM8510_POWER1, power1);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ wm8510_write(codec, WM8510_POWER1, 0);
+ wm8510_write(codec, WM8510_POWER2, 0);
+ wm8510_write(codec, WM8510_POWER3, 0);
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai wm8510_dai = {
+ .name = "WM8510 HiFi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8510_RATES,
+ .formats = WM8510_FORMATS,},
+ .ops = {
+ .hw_params = wm8510_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8510_mute,
+ .set_fmt = wm8510_set_dai_fmt,
+ .set_clkdiv = wm8510_set_dai_clkdiv,
+ .set_pll = wm8510_set_dai_pll,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8510_dai);
+
+static int wm8510_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8510_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8510_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the WM8510 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8510_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "WM8510";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8510_read_reg_cache;
+ codec->write = wm8510_write;
+ codec->set_bias_level = wm8510_set_bias_level;
+ codec->dai = &wm8510_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8510_reg);
+ codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8510_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8510: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8510_add_controls(codec);
+ wm8510_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8510: failed to register card\n");
+ goto card_err;
+ }
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8510_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8510 2 wire address is 0x1a
+ */
+
+static int wm8510_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8510_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8510_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8510\n");
+
+ return ret;
+}
+
+static int wm8510_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8510_i2c_id[] = {
+ { "wm8510", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id);
+
+static struct i2c_driver wm8510_i2c_driver = {
+ .driver = {
+ .name = "WM8510 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_i2c_probe,
+ .remove = wm8510_i2c_remove,
+ .id_table = wm8510_i2c_id,
+};
+
+static int wm8510_add_i2c_device(struct platform_device *pdev,
+ const struct wm8510_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8510_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8510", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8510_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8510_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8510_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8510_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8510\n");
+
+ return ret;
+}
+
+static int __devexit wm8510_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8510_spi_driver = {
+ .driver = {
+ .name = "wm8510",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8510_spi_probe,
+ .remove = __devexit_p(wm8510_spi_remove),
+};
+
+static int wm8510_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif /* CONFIG_SPI_MASTER */
+
+static int wm8510_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8510_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ pr_info("WM8510 Audio Codec %s", WM8510_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8510_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8510_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8510_spi_write;
+ ret = spi_register_driver(&wm8510_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0)
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int wm8510_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec->control_data)
+ wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8510_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8510_spi_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8510 = {
+ .probe = wm8510_probe,
+ .remove = wm8510_remove,
+ .suspend = wm8510_suspend,
+ .resume = wm8510_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510);
+
+MODULE_DESCRIPTION("ASoC WM8510 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h
new file mode 100644
index 0000000..bdefcf5
--- /dev/null
+++ b/sound/soc/codecs/wm8510.h
@@ -0,0 +1,105 @@
+/*
+ * wm8510.h -- WM8510 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8510_H
+#define _WM8510_H
+
+/* WM8510 register space */
+
+#define WM8510_RESET 0x0
+#define WM8510_POWER1 0x1
+#define WM8510_POWER2 0x2
+#define WM8510_POWER3 0x3
+#define WM8510_IFACE 0x4
+#define WM8510_COMP 0x5
+#define WM8510_CLOCK 0x6
+#define WM8510_ADD 0x7
+#define WM8510_GPIO 0x8
+#define WM8510_DAC 0xa
+#define WM8510_DACVOL 0xb
+#define WM8510_ADC 0xe
+#define WM8510_ADCVOL 0xf
+#define WM8510_EQ1 0x12
+#define WM8510_EQ2 0x13
+#define WM8510_EQ3 0x14
+#define WM8510_EQ4 0x15
+#define WM8510_EQ5 0x16
+#define WM8510_DACLIM1 0x18
+#define WM8510_DACLIM2 0x19
+#define WM8510_NOTCH1 0x1b
+#define WM8510_NOTCH2 0x1c
+#define WM8510_NOTCH3 0x1d
+#define WM8510_NOTCH4 0x1e
+#define WM8510_ALC1 0x20
+#define WM8510_ALC2 0x21
+#define WM8510_ALC3 0x22
+#define WM8510_NGATE 0x23
+#define WM8510_PLLN 0x24
+#define WM8510_PLLK1 0x25
+#define WM8510_PLLK2 0x26
+#define WM8510_PLLK3 0x27
+#define WM8510_ATTEN 0x28
+#define WM8510_INPUT 0x2c
+#define WM8510_INPPGA 0x2d
+#define WM8510_ADCBOOST 0x2f
+#define WM8510_OUTPUT 0x31
+#define WM8510_SPKMIX 0x32
+#define WM8510_SPKVOL 0x36
+#define WM8510_MONOMIX 0x38
+
+#define WM8510_CACHEREGNUM 57
+
+/* Clock divider Id's */
+#define WM8510_OPCLKDIV 0
+#define WM8510_MCLKDIV 1
+#define WM8510_ADCCLK 2
+#define WM8510_DACCLK 3
+#define WM8510_BCLKDIV 4
+
+/* DAC clock dividers */
+#define WM8510_DACCLK_F2 (1 << 3)
+#define WM8510_DACCLK_F4 (0 << 3)
+
+/* ADC clock dividers */
+#define WM8510_ADCCLK_F2 (1 << 3)
+#define WM8510_ADCCLK_F4 (0 << 3)
+
+/* PLL Out dividers */
+#define WM8510_OPCLKDIV_1 (0 << 4)
+#define WM8510_OPCLKDIV_2 (1 << 4)
+#define WM8510_OPCLKDIV_3 (2 << 4)
+#define WM8510_OPCLKDIV_4 (3 << 4)
+
+/* BCLK clock dividers */
+#define WM8510_BCLKDIV_1 (0 << 2)
+#define WM8510_BCLKDIV_2 (1 << 2)
+#define WM8510_BCLKDIV_4 (2 << 2)
+#define WM8510_BCLKDIV_8 (3 << 2)
+#define WM8510_BCLKDIV_16 (4 << 2)
+#define WM8510_BCLKDIV_32 (5 << 2)
+
+/* MCLK clock dividers */
+#define WM8510_MCLKDIV_1 (0 << 5)
+#define WM8510_MCLKDIV_1_5 (1 << 5)
+#define WM8510_MCLKDIV_2 (2 << 5)
+#define WM8510_MCLKDIV_3 (3 << 5)
+#define WM8510_MCLKDIV_4 (4 << 5)
+#define WM8510_MCLKDIV_6 (5 << 5)
+#define WM8510_MCLKDIV_8 (6 << 5)
+#define WM8510_MCLKDIV_12 (7 << 5)
+
+struct wm8510_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8510_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8510;
+
+#endif
diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c
new file mode 100644
index 0000000..627ebfb
--- /dev/null
+++ b/sound/soc/codecs/wm8580.c
@@ -0,0 +1,1053 @@
+/*
+ * wm8580.c -- WM8580 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * 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 WM8580 is a multichannel codec with S/PDIF support, featuring six
+ * DAC channels and two ADC channels.
+ *
+ * Currently only the primary audio interface is supported - S/PDIF and
+ * the secondary audio interfaces are not.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.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/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "wm8580.h"
+
+#define WM8580_VERSION "0.1"
+
+struct pll_state {
+ unsigned int in;
+ unsigned int out;
+};
+
+/* codec private data */
+struct wm8580_priv {
+ struct pll_state a;
+ struct pll_state b;
+};
+
+/* WM8580 register space */
+#define WM8580_PLLA1 0x00
+#define WM8580_PLLA2 0x01
+#define WM8580_PLLA3 0x02
+#define WM8580_PLLA4 0x03
+#define WM8580_PLLB1 0x04
+#define WM8580_PLLB2 0x05
+#define WM8580_PLLB3 0x06
+#define WM8580_PLLB4 0x07
+#define WM8580_CLKSEL 0x08
+#define WM8580_PAIF1 0x09
+#define WM8580_PAIF2 0x0A
+#define WM8580_SAIF1 0x0B
+#define WM8580_PAIF3 0x0C
+#define WM8580_PAIF4 0x0D
+#define WM8580_SAIF2 0x0E
+#define WM8580_DAC_CONTROL1 0x0F
+#define WM8580_DAC_CONTROL2 0x10
+#define WM8580_DAC_CONTROL3 0x11
+#define WM8580_DAC_CONTROL4 0x12
+#define WM8580_DAC_CONTROL5 0x13
+#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14
+#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15
+#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16
+#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17
+#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18
+#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19
+#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C
+#define WM8580_ADC_CONTROL1 0x1D
+#define WM8580_SPDTXCHAN0 0x1E
+#define WM8580_SPDTXCHAN1 0x1F
+#define WM8580_SPDTXCHAN2 0x20
+#define WM8580_SPDTXCHAN3 0x21
+#define WM8580_SPDTXCHAN4 0x22
+#define WM8580_SPDTXCHAN5 0x23
+#define WM8580_SPDMODE 0x24
+#define WM8580_INTMASK 0x25
+#define WM8580_GPO1 0x26
+#define WM8580_GPO2 0x27
+#define WM8580_GPO3 0x28
+#define WM8580_GPO4 0x29
+#define WM8580_GPO5 0x2A
+#define WM8580_INTSTAT 0x2B
+#define WM8580_SPDRXCHAN1 0x2C
+#define WM8580_SPDRXCHAN2 0x2D
+#define WM8580_SPDRXCHAN3 0x2E
+#define WM8580_SPDRXCHAN4 0x2F
+#define WM8580_SPDRXCHAN5 0x30
+#define WM8580_SPDSTAT 0x31
+#define WM8580_PWRDN1 0x32
+#define WM8580_PWRDN2 0x33
+#define WM8580_READBACK 0x34
+#define WM8580_RESET 0x35
+
+/* PLLB4 (register 7h) */
+#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60
+#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20
+#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40
+#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60
+
+#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180
+#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080
+#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100
+#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180
+
+/* CLKSEL (register 8h) */
+#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02
+
+/* AIF control 1 (registers 9h-bh) */
+#define WM8580_AIF_RATE_MASK 0x7
+#define WM8580_AIF_RATE_128 0x0
+#define WM8580_AIF_RATE_192 0x1
+#define WM8580_AIF_RATE_256 0x2
+#define WM8580_AIF_RATE_384 0x3
+#define WM8580_AIF_RATE_512 0x4
+#define WM8580_AIF_RATE_768 0x5
+#define WM8580_AIF_RATE_1152 0x6
+
+#define WM8580_AIF_BCLKSEL_MASK 0x18
+#define WM8580_AIF_BCLKSEL_64 0x00
+#define WM8580_AIF_BCLKSEL_128 0x08
+#define WM8580_AIF_BCLKSEL_256 0x10
+#define WM8580_AIF_BCLKSEL_SYSCLK 0x18
+
+#define WM8580_AIF_MS 0x20
+
+#define WM8580_AIF_CLKSRC_MASK 0xc0
+#define WM8580_AIF_CLKSRC_PLLA 0x40
+#define WM8580_AIF_CLKSRC_PLLB 0x40
+#define WM8580_AIF_CLKSRC_MCLK 0xc0
+
+/* AIF control 2 (registers ch-eh) */
+#define WM8580_AIF_FMT_MASK 0x03
+#define WM8580_AIF_FMT_RIGHTJ 0x00
+#define WM8580_AIF_FMT_LEFTJ 0x01
+#define WM8580_AIF_FMT_I2S 0x02
+#define WM8580_AIF_FMT_DSP 0x03
+
+#define WM8580_AIF_LENGTH_MASK 0x0c
+#define WM8580_AIF_LENGTH_16 0x00
+#define WM8580_AIF_LENGTH_20 0x04
+#define WM8580_AIF_LENGTH_24 0x08
+#define WM8580_AIF_LENGTH_32 0x0c
+
+#define WM8580_AIF_LRP 0x10
+#define WM8580_AIF_BCP 0x20
+
+/* Powerdown Register 1 (register 32h) */
+#define WM8580_PWRDN1_PWDN 0x001
+#define WM8580_PWRDN1_ALLDACPD 0x040
+
+/* Powerdown Register 2 (register 33h) */
+#define WM8580_PWRDN2_OSSCPD 0x001
+#define WM8580_PWRDN2_PLLAPD 0x002
+#define WM8580_PWRDN2_PLLBPD 0x004
+#define WM8580_PWRDN2_SPDIFPD 0x008
+#define WM8580_PWRDN2_SPDIFTXD 0x010
+#define WM8580_PWRDN2_SPDIFRXD 0x020
+
+#define WM8580_DAC_CONTROL5_MUTEALL 0x10
+
+/*
+ * wm8580 register cache
+ * We can't read the WM8580 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8580_reg[] = {
+ 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/
+ 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/
+ 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/
+ 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/
+ 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/
+ 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/
+ 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/
+ 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/
+ 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/
+ 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/
+ 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/
+ 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/
+ 0x0000, 0x0000 /*R53*/
+};
+
+/*
+ * read wm8580 register cache
+ */
+static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > ARRAY_SIZE(wm8580_reg));
+ return cache[reg];
+}
+
+/*
+ * write wm8580 register cache
+ */
+static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8580 register space
+ */
+static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ BUG_ON(reg > ARRAY_SIZE(wm8580_reg));
+
+ /* Registers are 9 bits wide */
+ value &= 0x1ff;
+
+ switch (reg) {
+ case WM8580_RESET:
+ /* Uncached */
+ break;
+ default:
+ if (value == wm8580_read_reg_cache(codec, reg))
+ return 0;
+ }
+
+ /* data is
+ * D15..D9 WM8580 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8580_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static inline unsigned int wm8580_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ default:
+ return wm8580_read_reg_cache(codec, reg);
+ }
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+
+static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int reg2 = (kcontrol->private_value >> 24) & 0xff;
+ int ret;
+ u16 val;
+
+ /* Clear the register cache so we write without VU set */
+ wm8580_write_reg_cache(codec, reg, 0);
+ wm8580_write_reg_cache(codec, reg2, 0);
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* Now write again with the volume update bit set */
+ val = wm8580_read_reg_cache(codec, reg);
+ wm8580_write(codec, reg, val | 0x0100);
+
+ val = wm8580_read_reg_cache(codec, reg2);
+ wm8580_write(codec, reg2, val | 0x0100);
+
+ return 0;
+}
+
+#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \
+ .private_value = (reg_left) | ((shift) << 8) | \
+ ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) }
+
+static const struct snd_kcontrol_new wm8580_snd_controls[] = {
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL1,
+ WM8580_DIGITAL_ATTENUATION_DACR1,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL2,
+ WM8580_DIGITAL_ATTENUATION_DACR2,
+ 0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume",
+ WM8580_DIGITAL_ATTENUATION_DACL3,
+ WM8580_DIGITAL_ATTENUATION_DACR3,
+ 0, 0xff, 0, dac_tlv),
+
+SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0),
+SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0),
+SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0),
+
+SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0),
+SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0),
+SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0),
+
+SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0),
+SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 0),
+SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 0),
+SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 0),
+
+SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0),
+SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0),
+};
+
+/* Add non-DAPM controls */
+static int wm8580_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8580_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("VOUT1L"),
+SND_SOC_DAPM_OUTPUT("VOUT1R"),
+SND_SOC_DAPM_OUTPUT("VOUT2L"),
+SND_SOC_DAPM_OUTPUT("VOUT2R"),
+SND_SOC_DAPM_OUTPUT("VOUT3L"),
+SND_SOC_DAPM_OUTPUT("VOUT3R"),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1),
+
+SND_SOC_DAPM_INPUT("AINL"),
+SND_SOC_DAPM_INPUT("AINR"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "VOUT1L", NULL, "DAC1" },
+ { "VOUT1R", NULL, "DAC1" },
+
+ { "VOUT2L", NULL, "DAC2" },
+ { "VOUT2R", NULL, "DAC2" },
+
+ { "VOUT3L", NULL, "DAC3" },
+ { "VOUT3R", NULL, "DAC3" },
+
+ { "ADC", NULL, "AINL" },
+ { "ADC", NULL, "AINR" },
+};
+
+static int wm8580_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets,
+ ARRAY_SIZE(wm8580_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 prescale:1;
+ u32 postscale:1;
+ u32 freqmode:2;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the pll divide */
+#define FIXED_PLL_SIZE (1 << 22)
+
+/* PLL rate to output rate divisions */
+static struct {
+ unsigned int div;
+ unsigned int freqmode;
+ unsigned int postscale;
+} post_table[] = {
+ { 2, 0, 0 },
+ { 4, 0, 1 },
+ { 4, 1, 0 },
+ { 8, 1, 1 },
+ { 8, 2, 0 },
+ { 16, 2, 1 },
+ { 12, 3, 0 },
+ { 24, 3, 1 }
+};
+
+static int pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+ int i;
+
+ pr_debug("wm8580: PLL %dHz->%dHz\n", source, target);
+
+ /* Scale the output frequency up; the PLL should run in the
+ * region of 90-100MHz.
+ */
+ for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+ if (target * post_table[i].div >= 90000000 &&
+ target * post_table[i].div <= 100000000) {
+ pll_div->freqmode = post_table[i].freqmode;
+ pll_div->postscale = post_table[i].postscale;
+ target *= post_table[i].div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(post_table)) {
+ printk(KERN_ERR "wm8580: Unable to scale output frequency "
+ "%u\n", target);
+ return -EINVAL;
+ }
+
+ Ndiv = target / source;
+
+ if (Ndiv < 5) {
+ source /= 2;
+ pll_div->prescale = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->prescale = 0;
+
+ if ((Ndiv < 5) || (Ndiv > 13)) {
+ printk(KERN_ERR
+ "WM8580 N=%d outside supported range\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ pll_div->k = K;
+
+ pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n",
+ pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode,
+ pll_div->postscale);
+
+ return 0;
+}
+
+static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ int offset;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8580_priv *wm8580 = codec->private_data;
+ struct pll_state *state;
+ struct _pll_div pll_div;
+ unsigned int reg;
+ unsigned int pwr_mask;
+ int ret;
+
+ /* GCC isn't able to work out the ifs below for initialising/using
+ * pll_div so suppress warnings.
+ */
+ memset(&pll_div, 0, sizeof(pll_div));
+
+ switch (pll_id) {
+ case WM8580_PLLA:
+ state = &wm8580->a;
+ offset = 0;
+ pwr_mask = WM8580_PWRDN2_PLLAPD;
+ break;
+ case WM8580_PLLB:
+ state = &wm8580->b;
+ offset = 4;
+ pwr_mask = WM8580_PWRDN2_PLLBPD;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(&pll_div, freq_out, freq_in);
+ if (ret != 0)
+ return ret;
+ }
+
+ state->in = freq_in;
+ state->out = freq_out;
+
+ /* Always disable the PLL - it is not safe to leave it running
+ * while reprogramming it.
+ */
+ reg = wm8580_read(codec, WM8580_PWRDN2);
+ wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
+ wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff);
+ wm8580_write(codec, WM8580_PLLA3 + offset,
+ (pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
+
+ reg = wm8580_read(codec, WM8580_PLLA4 + offset);
+ reg &= ~0x3f;
+ reg |= pll_div.prescale | pll_div.postscale << 1 |
+ pll_div.freqmode << 4;
+
+ wm8580_write(codec, WM8580_PLLA4 + offset, reg);
+
+ /* All done, turn it on */
+ reg = wm8580_read(codec, WM8580_PWRDN2);
+ wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8580_paif_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_dai_link *dai = rtd->dai;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->codec_dai->id);
+
+ paifb &= ~WM8580_AIF_LENGTH_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ paifb |= WM8580_AIF_LENGTH_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ paifb |= WM8580_AIF_LENGTH_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ paifb |= WM8580_AIF_LENGTH_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb);
+ return 0;
+}
+
+static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int aifa;
+ unsigned int aifb;
+ int can_invert_lrclk;
+
+ aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id);
+ aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id);
+
+ aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aifa &= ~WM8580_AIF_MS;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aifa |= WM8580_AIF_MS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_RIGHTJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ can_invert_lrclk = 1;
+ aifb |= WM8580_AIF_FMT_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ can_invert_lrclk = 0;
+ aifb |= WM8580_AIF_FMT_DSP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+
+ case SND_SOC_DAIFMT_IB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_BCP;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ case SND_SOC_DAIFMT_IB_NF:
+ aifb |= WM8580_AIF_BCP;
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ if (!can_invert_lrclk)
+ return -EINVAL;
+ aifb |= WM8580_AIF_LRP;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa);
+ wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb);
+
+ return 0;
+}
+
+static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8580_MCLK:
+ reg = wm8580_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_MCLK:
+ /* Input */
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA;
+ break;
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_MCLKOUTSRC_OSC;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ case WM8580_DAC_CLKSEL:
+ reg = wm8580_read(codec, WM8580_CLKSEL);
+ reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_MCLK:
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLA;
+ break;
+
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLB;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_CLKSEL, reg);
+ break;
+
+ case WM8580_CLKOUTSRC:
+ reg = wm8580_read(codec, WM8580_PLLB4);
+ reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK;
+
+ switch (div) {
+ case WM8580_CLKSRC_NONE:
+ break;
+
+ case WM8580_CLKSRC_PLLA:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK;
+ break;
+
+ case WM8580_CLKSRC_PLLB:
+ reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK;
+ break;
+
+ case WM8580_CLKSRC_OSC:
+ reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ wm8580_write(codec, WM8580_PLLB4, reg);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ reg = wm8580_read(codec, WM8580_DAC_CONTROL5);
+
+ if (mute)
+ reg |= WM8580_DAC_CONTROL5_MUTEALL;
+ else
+ reg &= ~WM8580_DAC_CONTROL5_MUTEALL;
+
+ wm8580_write(codec, WM8580_DAC_CONTROL5, reg);
+
+ return 0;
+}
+
+static int wm8580_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ case SND_SOC_BIAS_OFF:
+ reg = wm8580_read(codec, WM8580_PWRDN1);
+ wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai wm8580_dai[] = {
+ {
+ .name = "WM8580 PAIFRX",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8580_paif_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+ .digital_mute = wm8580_digital_mute,
+ },
+ },
+ {
+ .name = "WM8580 PAIFTX",
+ .id = 1,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = WM8580_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8580_paif_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = wm8580_set_paif_dai_fmt,
+ .set_clkdiv = wm8580_set_dai_clkdiv,
+ .set_pll = wm8580_set_dai_pll,
+ },
+ },
+};
+EXPORT_SYMBOL_GPL(wm8580_dai);
+
+/*
+ * initialise the WM8580 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8580_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ codec->name = "WM8580";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8580_read_reg_cache;
+ codec->write = wm8580_write;
+ codec->set_bias_level = wm8580_set_bias_level;
+ codec->dai = wm8580_dai;
+ codec->num_dai = ARRAY_SIZE(wm8580_dai);
+ codec->reg_cache_size = ARRAY_SIZE(wm8580_reg);
+ codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg),
+ GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ /* Get the codec into a known state */
+ wm8580_write(codec, WM8580_RESET, 0);
+
+ /* Power up and get individual control of the DACs */
+ wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) &
+ ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD));
+
+ /* Make VMID high impedence */
+ wm8580_write(codec, WM8580_ADC_CONTROL1,
+ wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1,
+ SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8580: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ wm8580_add_controls(codec);
+ wm8580_add_widgets(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8580: 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 *wm8580_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8580 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 wm8580_i2c_driver;
+static struct i2c_client client_template;
+
+static int wm8580_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = wm8580_socdev;
+ struct wm8580_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) {
+ dev_err(&i2c->dev, "failed to attach codec at addr %x\n", addr);
+ goto err;
+ }
+
+ ret = wm8580_init(socdev);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to initialise WM8580\n");
+ goto err;
+ }
+
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+static int wm8580_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 wm8580_i2c_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, wm8580_codec_probe);
+}
+
+/* corgi i2c codec control layer */
+static struct i2c_driver wm8580_i2c_driver = {
+ .driver = {
+ .name = "WM8580 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .attach_adapter = wm8580_i2c_attach,
+ .detach_client = wm8580_i2c_detach,
+ .command = NULL,
+};
+
+static struct i2c_client client_template = {
+ .name = "WM8580",
+ .driver = &wm8580_i2c_driver,
+};
+#endif
+
+static int wm8580_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8580_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8580_priv *wm8580;
+ int ret = 0;
+
+ pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL);
+ if (wm8580 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8580;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8580_socdev = socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ normal_i2c[0] = setup->i2c_address;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8580_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+ }
+#else
+ /* Add other interfaces here */
+#endif
+ return ret;
+}
+
+/* power down chip */
+static int wm8580_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)
+ wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8580_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8580 = {
+ .probe = wm8580_probe,
+ .remove = wm8580_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580);
+
+MODULE_DESCRIPTION("ASoC WM8580 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h
new file mode 100644
index 0000000..589ddab
--- /dev/null
+++ b/sound/soc/codecs/wm8580.h
@@ -0,0 +1,42 @@
+/*
+ * wm8580.h -- audio driver for WM8580
+ *
+ * Copyright 2008 Samsung Electronics.
+ * Author: Ryu Euiyoul
+ * ryu.real@gmail.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 _WM8580_H
+#define _WM8580_H
+
+#define WM8580_PLLA 1
+#define WM8580_PLLB 2
+
+#define WM8580_MCLK 1
+#define WM8580_DAC_CLKSEL 2
+#define WM8580_CLKOUTSRC 3
+
+#define WM8580_CLKSRC_MCLK 1
+#define WM8580_CLKSRC_PLLA 2
+#define WM8580_CLKSRC_PLLB 3
+#define WM8580_CLKSRC_OSC 4
+#define WM8580_CLKSRC_NONE 5
+
+struct wm8580_setup_data {
+ unsigned short i2c_address;
+};
+
+#define WM8580_DAI_PAIFRX 0
+#define WM8580_DAI_PAIFTX 1
+
+extern struct snd_soc_dai wm8580_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_wm8580;
+
+#endif
+
diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c
new file mode 100644
index 0000000..7f8a7e3
--- /dev/null
+++ b/sound/soc/codecs/wm8731.c
@@ -0,0 +1,797 @@
+/*
+ * wm8731.c -- WM8731 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 "wm8731.h"
+
+#define WM8731_VERSION "0.13"
+
+struct snd_soc_codec_device soc_codec_dev_wm8731;
+
+/* codec private data */
+struct wm8731_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8731 register cache
+ * We can't read the WM8731 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8731_reg[WM8731_CACHEREGNUM] = {
+ 0x0097, 0x0097, 0x0079, 0x0079,
+ 0x000a, 0x0008, 0x009f, 0x000a,
+ 0x0000, 0x0000
+};
+
+/*
+ * read wm8731 register cache
+ */
+static inline unsigned int wm8731_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg == WM8731_RESET)
+ return 0;
+ if (reg >= WM8731_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8731 register cache
+ */
+static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= WM8731_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8731 register space
+ */
+static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ /* data is
+ * D15..D9 WM8731 register offset
+ * D8...D0 register data
+ */
+ data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+ data[1] = value & 0x00ff;
+
+ wm8731_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0)
+
+static const char *wm8731_input_select[] = {"Line In", "Mic"};
+static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum wm8731_enum[] = {
+ SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select),
+ SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph),
+};
+
+static const struct snd_kcontrol_new wm8731_snd_controls[] = {
+
+SOC_DOUBLE_R("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V,
+ 7, 1, 0),
+
+SOC_DOUBLE_R("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0),
+SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1),
+
+SOC_SINGLE("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8731_enum[1]),
+};
+
+/* add non dapm controls */
+static int wm8731_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8731_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8731_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new wm8731_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", wm8731_enum[0]);
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1,
+ &wm8731_output_mixer_controls[0],
+ ARRAY_SIZE(wm8731_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* output mixer */
+ {"Output Mixer", "Line Bypass Switch", "Line Input"},
+ {"Output Mixer", "HiFi Playback Switch", "DAC"},
+ {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+ /* outputs */
+ {"RHPOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+ {"LHPOUT", NULL, "Output Mixer"},
+ {"LOUT", NULL, "Output Mixer"},
+
+ /* input mux */
+ {"Input Mux", "Line In", "Line Input"},
+ {"Input Mux", "Mic", "Mic Bias"},
+ {"ADC", NULL, "Input Mux"},
+
+ /* inputs */
+ {"Line Input", NULL, "LLINEIN"},
+ {"Line Input", NULL, "RLINEIN"},
+ {"Mic Bias", NULL, "MICIN"},
+};
+
+static int wm8731_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:4;
+ u8 bosr:1;
+ u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0, 0x0},
+ {18432000, 48000, 384, 0x0, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0x6, 0x0, 0x0},
+ {18432000, 32000, 576, 0x6, 0x1, 0x0},
+ {12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+ /* 8k */
+ {12288000, 8000, 1536, 0x3, 0x0, 0x0},
+ {18432000, 8000, 2304, 0x3, 0x1, 0x0},
+ {11289600, 8000, 1408, 0xb, 0x0, 0x0},
+ {16934400, 8000, 2112, 0xb, 0x1, 0x0},
+ {12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0x7, 0x0, 0x0},
+ {18432000, 96000, 192, 0x7, 0x1, 0x0},
+ {12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x8, 0x0, 0x0},
+ {16934400, 44100, 384, 0x8, 0x1, 0x0},
+ {12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0xf, 0x0, 0x0},
+ {16934400, 88200, 192, 0xf, 0x1, 0x0},
+ {12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+ return 0;
+}
+
+static int wm8731_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 wm8731_priv *wm8731 = codec->private_data;
+ u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3;
+ int i = get_coeff(wm8731->sysclk, params_rate(params));
+ u16 srate = (coeff_div[i].sr << 2) |
+ (coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+ wm8731_write(codec, WM8731_SRATE, srate);
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ }
+
+ wm8731_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* set active */
+ wm8731_write(codec, WM8731_ACTIVE, 0x0001);
+
+ return 0;
+}
+
+static void wm8731_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ /* deactivate */
+ if (!codec->active) {
+ udelay(50);
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ }
+}
+
+static int wm8731_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7;
+
+ if (mute)
+ wm8731_write(codec, WM8731_APDIGI, mute_reg | 0x8);
+ else
+ wm8731_write(codec, WM8731_APDIGI, mute_reg);
+ return 0;
+}
+
+static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8731_priv *wm8731 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8731->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+
+static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ wm8731_write(codec, WM8731_IFACE, iface);
+ return 0;
+}
+
+static int wm8731_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* vref/mid, osc on, dac unmute */
+ wm8731_write(codec, WM8731_PWR, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ wm8731_write(codec, WM8731_PWR, reg | 0x0040);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ wm8731_write(codec, WM8731_PWR, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8731_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000)
+
+#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8731_dai = {
+ .name = "WM8731",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8731_RATES,
+ .formats = WM8731_FORMATS,},
+ .ops = {
+ .prepare = wm8731_pcm_prepare,
+ .hw_params = wm8731_hw_params,
+ .shutdown = wm8731_shutdown,
+ },
+ .dai_ops = {
+ .digital_mute = wm8731_mute,
+ .set_sysclk = wm8731_set_dai_sysclk,
+ .set_fmt = wm8731_set_dai_fmt,
+ }
+};
+EXPORT_SYMBOL_GPL(wm8731_dai);
+
+static int wm8731_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;
+
+ wm8731_write(codec, WM8731_ACTIVE, 0x0);
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8731_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8731_set_bias_level(codec, codec->suspend_bias_level);
+ return 0;
+}
+
+/*
+ * initialise the WM8731 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8731_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8731";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8731_read_reg_cache;
+ codec->write = wm8731_write;
+ codec->set_bias_level = wm8731_set_bias_level;
+ codec->dai = &wm8731_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8731_reg);
+ codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8731_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8731: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* set the update bits */
+ reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V);
+ wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V);
+ wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_LINVOL);
+ wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100);
+ reg = wm8731_read_reg_cache(codec, WM8731_RINVOL);
+ wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100);
+
+ wm8731_add_controls(codec);
+ wm8731_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8731: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8731_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8731 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static int wm8731_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8731_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8731_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8731\n");
+
+ return ret;
+}
+
+static int wm8731_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8731_i2c_id[] = {
+ { "wm8731", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id);
+
+static struct i2c_driver wm8731_i2c_driver = {
+ .driver = {
+ .name = "WM8731 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_i2c_probe,
+ .remove = wm8731_i2c_remove,
+ .id_table = wm8731_i2c_id,
+};
+
+static int wm8731_add_i2c_device(struct platform_device *pdev,
+ const struct wm8731_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8731_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8731", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8731_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8731_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8731_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8731_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8731\n");
+
+ return ret;
+}
+
+static int __devexit wm8731_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8731_spi_driver = {
+ .driver = {
+ .name = "wm8731",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8731_spi_probe,
+ .remove = __devexit_p(wm8731_spi_remove),
+};
+
+static int wm8731_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif /* CONFIG_SPI_MASTER */
+
+static int wm8731_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8731_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8731_priv *wm8731;
+ int ret = 0;
+
+ pr_info("WM8731 Audio Codec %s", WM8731_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+ if (wm8731 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8731;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8731_socdev = socdev;
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8731_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8731_spi_write;
+ ret = spi_register_driver(&wm8731_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8731_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)
+ wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8731_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8731_spi_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8731 = {
+ .probe = wm8731_probe,
+ .remove = wm8731_remove,
+ .suspend = wm8731_suspend,
+ .resume = wm8731_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731);
+
+MODULE_DESCRIPTION("ASoC WM8731 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h
new file mode 100644
index 0000000..95190e9
--- /dev/null
+++ b/sound/soc/codecs/wm8731.h
@@ -0,0 +1,46 @@
+/*
+ * wm8731.h -- WM8731 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8731_H
+#define _WM8731_H
+
+/* WM8731 register space */
+
+#define WM8731_LINVOL 0x00
+#define WM8731_RINVOL 0x01
+#define WM8731_LOUT1V 0x02
+#define WM8731_ROUT1V 0x03
+#define WM8731_APANA 0x04
+#define WM8731_APDIGI 0x05
+#define WM8731_PWR 0x06
+#define WM8731_IFACE 0x07
+#define WM8731_SRATE 0x08
+#define WM8731_ACTIVE 0x09
+#define WM8731_RESET 0x0f
+
+#define WM8731_CACHEREGNUM 10
+
+#define WM8731_SYSCLK 0
+#define WM8731_DAI 0
+
+struct wm8731_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8731_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8731;
+
+#endif
diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c
new file mode 100644
index 0000000..9b7296e
--- /dev/null
+++ b/sound/soc/codecs/wm8750.c
@@ -0,0 +1,1091 @@
+/*
+ * wm8750.c -- WM8750 ALSA SoC audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 "wm8750.h"
+
+#define WM8750_VERSION "0.12"
+
+/* codec private data */
+struct wm8750_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8750 register cache
+ * We can't read the WM8750 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8750_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+/*
+ * read wm8750 register cache
+ */
+static inline unsigned int wm8750_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg > WM8750_CACHE_REGNUM)
+ return -1;
+ return cache[reg];
+}
+
+/*
+ * write wm8750 register cache
+ */
+static inline void wm8750_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg > WM8750_CACHE_REGNUM)
+ return;
+ cache[reg] = value;
+}
+
+static int wm8750_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;
+
+ wm8750_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8750_reset(c) wm8750_write(c, WM8750_RESET, 0)
+
+/*
+ * WM8750 Controls
+ */
+static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const char *wm8750_treble[] = {"8kHz", "4kHz"};
+static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8750_3d_func[] = {"Capture", "Playback"};
+static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8750_ng_type[] = {"Constant PGA Gain",
+ "Mute ADC Output"};
+static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA",
+ "Differential"};
+static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3",
+ "Differential"};
+static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut",
+ "ROUT1"};
+static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"};
+static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+
+static const struct soc_enum wm8750_enum[] = {
+SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass),
+SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter),
+SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble),
+SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc),
+SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc),
+SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func),
+SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func),
+SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type),
+SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */
+SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel),
+SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */
+
+};
+
+static const struct snd_kcontrol_new wm8750_snd_controls[] = {
+
+SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0),
+SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V,
+ WM8750_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V,
+ WM8750_ROUT2V, 7, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8750_enum[15]),
+
+SOC_ENUM("Capture Polarity", wm8750_enum[14]),
+SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0),
+
+SOC_ENUM("Bass Boost", wm8750_enum[0]),
+SOC_ENUM("Bass Filter", wm8750_enum[1]),
+SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8750_enum[2]),
+
+SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]),
+SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]),
+SOC_ENUM("3D Mode", wm8750_enum[5]),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8750_enum[6]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0),
+
+SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0),
+SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0),
+SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0),
+
+SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0),
+
+/* Unimplemented */
+/* ADCDAC Bit 0 - ADCHPD */
+/* ADCDAC Bit 4 - HPOR */
+/* ADCTL1 Bit 2,3 - DATSEL */
+/* ADCTL1 Bit 4,5 - DMONOMIX */
+/* ADCTL1 Bit 6,7 - VSEL */
+/* ADCTL2 Bit 2 - LRCM */
+/* ADCTL2 Bit 3 - TRI */
+/* ADCTL3 Bit 5 - HPFLREN */
+/* ADCTL3 Bit 6 - VROI */
+/* ADCTL3 Bit 7,8 - ADCLRM */
+/* ADCIN Bit 4 - LDCM */
+/* ADCIN Bit 5 - RDCM */
+
+SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0),
+
+SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1,
+ WM8750_LOUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1,
+ WM8750_ROUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1,
+ WM8750_MOUTM2, 4, 7, 1),
+
+SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0),
+
+SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V,
+ 0, 127, 0),
+SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V,
+ 0, 127, 0),
+
+SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8750_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8750_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8750_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8750_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8750_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8750_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8750_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[11]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8750_out3_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[12]);
+
+/* Differential Mux */
+static const struct snd_kcontrol_new wm8750_diffmux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[13]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8750_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[16]);
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_mixer_controls[0],
+ ARRAY_SIZE(wm8750_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_mixer_controls[0],
+ ARRAY_SIZE(wm8750_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0,
+ &wm8750_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8750_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0,
+ &wm8750_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0,
+ &wm8750_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls),
+ SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_diffmux_controls),
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8750_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO1"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("VREF"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("LINPUT2"),
+ SND_SOC_DAPM_INPUT("LINPUT3"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT2"),
+ SND_SOC_DAPM_INPUT("RINPUT3"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out 1", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out 1"},
+
+ /* out 3 */
+ {"Out3 Mux", "VREF", "VREF"},
+ {"Out3 Mux", "ROUT1 + Vol", "ROUT1"},
+ {"Out3 Mux", "ROUT1", "Right Mixer"},
+ {"Out3 Mux", "MonoOut", "MONO1"},
+ {"Out 3", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line 1", "LINPUT1"},
+ {"Left Line Mux", "Line 2", "LINPUT2"},
+ {"Left Line Mux", "Line 3", "LINPUT3"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line 1", "RINPUT1"},
+ {"Right Line Mux", "Line 2", "RINPUT2"},
+ {"Right Line Mux", "Line 3", "RINPUT3"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line 1", "LINPUT1"},
+ {"Left PGA Mux", "Line 2", "LINPUT2"},
+ {"Left PGA Mux", "Line 3", "LINPUT3"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line 1", "RINPUT1"},
+ {"Right PGA Mux", "Line 2", "RINPUT2"},
+ {"Right PGA Mux", "Line 3", "RINPUT3"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line 1", "LINPUT1"},
+ {"Differential Mux", "Line 1", "RINPUT1"},
+ {"Differential Mux", "Line 2", "LINPUT2"},
+ {"Differential Mux", "Line 2", "RINPUT2"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8750_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+ return i;
+ }
+
+ printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n",
+ mclk, rate);
+ return -EINVAL;
+}
+
+static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8750_priv *wm8750 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8750->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8750_write(codec, WM8750_IFACE, iface);
+ return 0;
+}
+
+static int wm8750_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 wm8750_priv *wm8750 = codec->private_data;
+ u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3;
+ u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8750->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ wm8750_write(codec, WM8750_IFACE, iface);
+ if (coeff >= 0)
+ wm8750_write(codec, WM8750_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8750_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7;
+
+ if (mute)
+ wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
+ else
+ wm8750_write(codec, WM8750_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8750_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* set vmid to 5k for quick power up */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8750_write(codec, WM8750_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8750_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 WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8750_dai = {
+ .name = "WM8750",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8750_RATES,
+ .formats = WM8750_FORMATS,},
+ .ops = {
+ .hw_params = wm8750_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8750_mute,
+ .set_fmt = wm8750_set_dai_fmt,
+ .set_sysclk = wm8750_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8750_dai);
+
+static void wm8750_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8750_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8750_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;
+
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8750_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+ u8 data[2];
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) {
+ if (i == WM8750_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8750 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(1000));
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8750 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8750_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8750";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8750_read_reg_cache;
+ codec->write = wm8750_write;
+ codec->set_bias_level = wm8750_set_bias_level;
+ codec->dai = &wm8750_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8750_reg);
+ codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8750_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000));
+
+ /* set the update bits */
+ reg = wm8750_read_reg_cache(codec, WM8750_LDAC);
+ wm8750_write(codec, WM8750_LDAC, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_RDAC);
+ wm8750_write(codec, WM8750_RDAC, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V);
+ wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V);
+ wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V);
+ wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V);
+ wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_LINVOL);
+ wm8750_write(codec, WM8750_LINVOL, reg | 0x0100);
+ reg = wm8750_read_reg_cache(codec, WM8750_RINVOL);
+ wm8750_write(codec, WM8750_RINVOL, reg | 0x0100);
+
+ wm8750_add_controls(codec);
+ wm8750_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8750: 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 *wm8750_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM8750 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+
+static int wm8750_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8750_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8750_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8750\n");
+
+ return ret;
+}
+
+static int wm8750_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8750_i2c_id[] = {
+ { "wm8750", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);
+
+static struct i2c_driver wm8750_i2c_driver = {
+ .driver = {
+ .name = "WM8750 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_i2c_probe,
+ .remove = wm8750_i2c_remove,
+ .id_table = wm8750_i2c_id,
+};
+
+static int wm8750_add_i2c_device(struct platform_device *pdev,
+ const struct wm8750_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8750_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8750", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8750_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8750_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8750_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8750_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8750\n");
+
+ return ret;
+}
+
+static int __devexit wm8750_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8750_spi_driver = {
+ .driver = {
+ .name = "wm8750",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8750_spi_probe,
+ .remove = __devexit_p(wm8750_spi_remove),
+};
+
+static int wm8750_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#endif
+
+static int wm8750_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8750_setup_data *setup = socdev->codec_data;
+ struct snd_soc_codec *codec;
+ struct wm8750_priv *wm8750;
+ int ret;
+
+ pr_info("WM8750 Audio Codec %s", WM8750_VERSION);
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+ if (wm8750 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8750;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8750_socdev = socdev;
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work);
+
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8750_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8750_spi_write;
+ ret = spi_register_driver(&wm8750_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ 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 wm8750_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)
+ wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ 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_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8750_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8750_spi_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8750 = {
+ .probe = wm8750_probe,
+ .remove = wm8750_remove,
+ .suspend = wm8750_suspend,
+ .resume = wm8750_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750);
+
+MODULE_DESCRIPTION("ASoC WM8750 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h
new file mode 100644
index 0000000..1dc100e
--- /dev/null
+++ b/sound/soc/codecs/wm8750.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8750_H
+#define _WM8750_H
+
+/* WM8750 register space */
+
+#define WM8750_LINVOL 0x00
+#define WM8750_RINVOL 0x01
+#define WM8750_LOUT1V 0x02
+#define WM8750_ROUT1V 0x03
+#define WM8750_ADCDAC 0x05
+#define WM8750_IFACE 0x07
+#define WM8750_SRATE 0x08
+#define WM8750_LDAC 0x0a
+#define WM8750_RDAC 0x0b
+#define WM8750_BASS 0x0c
+#define WM8750_TREBLE 0x0d
+#define WM8750_RESET 0x0f
+#define WM8750_3D 0x10
+#define WM8750_ALC1 0x11
+#define WM8750_ALC2 0x12
+#define WM8750_ALC3 0x13
+#define WM8750_NGATE 0x14
+#define WM8750_LADC 0x15
+#define WM8750_RADC 0x16
+#define WM8750_ADCTL1 0x17
+#define WM8750_ADCTL2 0x18
+#define WM8750_PWR1 0x19
+#define WM8750_PWR2 0x1a
+#define WM8750_ADCTL3 0x1b
+#define WM8750_ADCIN 0x1f
+#define WM8750_LADCIN 0x20
+#define WM8750_RADCIN 0x21
+#define WM8750_LOUTM1 0x22
+#define WM8750_LOUTM2 0x23
+#define WM8750_ROUTM1 0x24
+#define WM8750_ROUTM2 0x25
+#define WM8750_MOUTM1 0x26
+#define WM8750_MOUTM2 0x27
+#define WM8750_LOUT2V 0x28
+#define WM8750_ROUT2V 0x29
+#define WM8750_MOUTV 0x2a
+
+#define WM8750_CACHE_REGNUM 0x2a
+
+#define WM8750_SYSCLK 0
+
+struct wm8750_setup_data {
+ int spi;
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8750_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8750;
+
+#endif
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
new file mode 100644
index 0000000..d426eaa
--- /dev/null
+++ b/sound/soc/codecs/wm8753.c
@@ -0,0 +1,1882 @@
+/*
+ * wm8753.c -- WM8753 ALSA Soc Audio driver
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.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 <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8753.h"
+
+#define WM8753_VERSION "0.16"
+
+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 char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"};
+
+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),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase),
+};
+
+
+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 DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const unsigned int out_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ /* 0000000 - 0101111 = "Analogue mute" */
+ 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0),
+ 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0),
+};
+static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+
+static const struct snd_kcontrol_new wm8753_snd_controls[] = {
+SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0,
+ adc_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0,
+ 127, 0, out_tlv),
+
+SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7,
+ 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4,
+ 7, 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7,
+ 1, voice_mix_tlv),
+
+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_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv),
+SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1,
+ mix_tlv),
+SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1,
+ voice_mix_tlv),
+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_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1,
+ rec_mix_tlv),
+SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1,
+ rec_mix_tlv),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0,
+ pga_tlv),
+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_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv),
+SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv),
+
+SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),
+
+SOC_ENUM("ADC Data Select", wm8753_enum[27]),
+SOC_ENUM("ROUT2 Phase", wm8753_enum[28]),
+};
+
+/* 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 struct snd_soc_dapm_route audio_map[] = {
+ /* 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", "Playback 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", "Playback 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"},
+};
+
+static int wm8753_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+ ARRAY_SIZE(wm8753_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ 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: unsupported 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_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_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_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_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_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_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_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_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_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_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_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_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_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ /* set vmid to 5k for quick power up */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8753_write(codec, WM8753_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ 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_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_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_set_bias_level(codec, codec->bias_level);
+}
+
+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_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ 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_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8753 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ 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->set_bias_level = wm8753_set_bias_level;
+ codec->dai = wm8753_dai;
+ codec->num_dai = 2;
+ codec->reg_cache_size = ARRAY_SIZE(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_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ 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 int wm8753_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8753_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8753_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8753\n");
+
+ return ret;
+}
+
+static int wm8753_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8753_i2c_id[] = {
+ { "wm8753", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id);
+
+static struct i2c_driver wm8753_i2c_driver = {
+ .driver = {
+ .name = "WM8753 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_i2c_probe,
+ .remove = wm8753_i2c_remove,
+ .id_table = wm8753_i2c_id,
+};
+
+static int wm8753_add_i2c_device(struct platform_device *pdev,
+ const struct wm8753_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8753_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8753", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8753_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8753_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_device *socdev = wm8753_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ codec->control_data = spi;
+
+ ret = wm8753_init(socdev);
+ if (ret < 0)
+ dev_err(&spi->dev, "failed to initialise WM8753\n");
+
+ return ret;
+}
+
+static int __devexit wm8753_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+static struct spi_driver wm8753_spi_driver = {
+ .driver = {
+ .name = "wm8753",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8753_spi_probe,
+ .remove = __devexit_p(wm8753_spi_remove),
+};
+
+static int wm8753_spi_write(struct spi_device *spi, const char *data, int len)
+{
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+
+ spi_message_init(&m);
+ memset(&t, 0, (sizeof t));
+
+ t.tx_buf = &msg[0];
+ t.len = len;
+
+ spi_message_add_tail(&t, &m);
+ spi_sync(spi, &m);
+
+ return len;
+}
+#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;
+
+ pr_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) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8753_add_i2c_device(pdev, setup);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ if (setup->spi) {
+ codec->hw_write = (hw_write_t)wm8753_spi_write;
+ ret = spi_register_driver(&wm8753_spi_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add spi driver");
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ 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_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ 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_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8753_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&wm8753_spi_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 0000000..f55704c
--- /dev/null
+++ b/sound/soc/codecs/wm8753.h
@@ -0,0 +1,127 @@
+/*
+ * wm8753.h -- audio driver for WM8753
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ *
+ */
+
+#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 {
+ int spi;
+ int i2c_bus;
+ 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_dai wm8753_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_wm8753;
+
+#endif
diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c
new file mode 100644
index 0000000..3b326c9
--- /dev/null
+++ b/sound/soc/codecs/wm8900.c
@@ -0,0 +1,1541 @@
+/*
+ * wm8900.c -- WM8900 ALSA Soc Audio driver
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - Tristating.
+ * - TDM.
+ * - Jack detect.
+ * - FLL source configuration, currently only MCLK is supported.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.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/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 <sound/tlv.h>
+
+#include "wm8900.h"
+
+/* WM8900 register space */
+#define WM8900_REG_RESET 0x0
+#define WM8900_REG_ID 0x0
+#define WM8900_REG_POWER1 0x1
+#define WM8900_REG_POWER2 0x2
+#define WM8900_REG_POWER3 0x3
+#define WM8900_REG_AUDIO1 0x4
+#define WM8900_REG_AUDIO2 0x5
+#define WM8900_REG_CLOCKING1 0x6
+#define WM8900_REG_CLOCKING2 0x7
+#define WM8900_REG_AUDIO3 0x8
+#define WM8900_REG_AUDIO4 0x9
+#define WM8900_REG_DACCTRL 0xa
+#define WM8900_REG_LDAC_DV 0xb
+#define WM8900_REG_RDAC_DV 0xc
+#define WM8900_REG_SIDETONE 0xd
+#define WM8900_REG_ADCCTRL 0xe
+#define WM8900_REG_LADC_DV 0xf
+#define WM8900_REG_RADC_DV 0x10
+#define WM8900_REG_GPIO 0x12
+#define WM8900_REG_INCTL 0x15
+#define WM8900_REG_LINVOL 0x16
+#define WM8900_REG_RINVOL 0x17
+#define WM8900_REG_INBOOSTMIX1 0x18
+#define WM8900_REG_INBOOSTMIX2 0x19
+#define WM8900_REG_ADCPATH 0x1a
+#define WM8900_REG_AUXBOOST 0x1b
+#define WM8900_REG_ADDCTL 0x1e
+#define WM8900_REG_FLLCTL1 0x24
+#define WM8900_REG_FLLCTL2 0x25
+#define WM8900_REG_FLLCTL3 0x26
+#define WM8900_REG_FLLCTL4 0x27
+#define WM8900_REG_FLLCTL5 0x28
+#define WM8900_REG_FLLCTL6 0x29
+#define WM8900_REG_LOUTMIXCTL1 0x2c
+#define WM8900_REG_ROUTMIXCTL1 0x2d
+#define WM8900_REG_BYPASS1 0x2e
+#define WM8900_REG_BYPASS2 0x2f
+#define WM8900_REG_AUXOUT_CTL 0x30
+#define WM8900_REG_LOUT1CTL 0x33
+#define WM8900_REG_ROUT1CTL 0x34
+#define WM8900_REG_LOUT2CTL 0x35
+#define WM8900_REG_ROUT2CTL 0x36
+#define WM8900_REG_HPCTL1 0x3a
+#define WM8900_REG_OUTBIASCTL 0x73
+
+#define WM8900_MAXREG 0x80
+
+#define WM8900_REG_ADDCTL_OUT1_DIS 0x80
+#define WM8900_REG_ADDCTL_OUT2_DIS 0x40
+#define WM8900_REG_ADDCTL_VMID_DIS 0x20
+#define WM8900_REG_ADDCTL_BIAS_SRC 0x10
+#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04
+#define WM8900_REG_ADDCTL_TEMP_SD 0x02
+
+#define WM8900_REG_GPIO_TEMP_ENA 0x2
+
+#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100
+#define WM8900_REG_POWER1_BIAS_ENA 0x0008
+#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004
+#define WM8900_REG_POWER1_FLL_ENA 0x0040
+
+#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000
+#define WM8900_REG_POWER2_ADCL_ENA 0x0002
+#define WM8900_REG_POWER2_ADCR_ENA 0x0001
+
+#define WM8900_REG_POWER3_DACL_ENA 0x0002
+#define WM8900_REG_POWER3_DACR_ENA 0x0001
+
+#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018
+#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080
+#define WM8900_REG_AUDIO1_BCLK_INV 0x0100
+
+#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1
+#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100
+#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e)
+#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000)
+
+#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0
+#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c
+
+#define WM8900_REG_DACCTRL_MUTE 0x004
+#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400
+
+#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800
+
+#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800
+
+#define WM8900_REG_FLLCTL1_OSC_ENA 0x100
+
+#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100
+
+#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80
+#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40
+#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20
+#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10
+#define WM8900_REG_HPCTL1_HP_SHORT 0x08
+#define WM8900_REG_HPCTL1_HP_SHORT2 0x04
+
+#define WM8900_LRC_MASK 0xfc00
+
+struct snd_soc_codec_device soc_codec_dev_wm8900;
+
+struct wm8900_priv {
+ u32 fll_in; /* FLL input frequency */
+ u32 fll_out; /* FLL output frequency */
+};
+
+/*
+ * wm8900 register cache. We can't read the entire register space and we
+ * have slow control buses so we cache the registers.
+ */
+static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
+ 0x8900, 0x0000,
+ 0xc000, 0x0000,
+ 0x4050, 0x4000,
+ 0x0008, 0x0000,
+ 0x0040, 0x0040,
+ 0x1004, 0x00c0,
+ 0x00c0, 0x0000,
+ 0x0100, 0x00c0,
+ 0x00c0, 0x0000,
+ 0xb001, 0x0000,
+ 0x0000, 0x0044,
+ 0x004c, 0x004c,
+ 0x0044, 0x0044,
+ 0x0000, 0x0044,
+ 0x0000, 0x0000,
+ 0x0002, 0x0000,
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+ 0x0008, 0x0000,
+ 0x0000, 0x0008,
+ 0x0097, 0x0100,
+ 0x0000, 0x0000,
+ 0x0050, 0x0050,
+ 0x0055, 0x0055,
+ 0x0055, 0x0000,
+ 0x0000, 0x0079,
+ 0x0079, 0x0079,
+ 0x0079, 0x0000,
+ /* Remaining registers all zero */
+};
+
+/*
+ * read wm8900 register cache
+ */
+static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= WM8900_MAXREG);
+
+ if (reg == WM8900_REG_ID)
+ return 0;
+
+ return cache[reg];
+}
+
+/*
+ * write wm8900 register cache
+ */
+static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= WM8900_MAXREG);
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the WM8900 register space
+ */
+static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ if (value == wm8900_read_reg_cache(codec, reg))
+ return 0;
+
+ /* data is
+ * D15..D9 WM8900 register offset
+ * D8...D0 register data
+ */
+ data[0] = reg;
+ data[1] = value >> 8;
+ data[2] = value & 0x00ff;
+
+ wm8900_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 3) == 3)
+ return 0;
+ else
+ return -EIO;
+}
+
+/*
+ * Read from the wm8900.
+ */
+static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg)
+{
+ struct i2c_msg xfer[2];
+ u16 data;
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1);
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 2;
+ xfer[1].buf = (u8 *)&data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret != 2) {
+ printk(KERN_CRIT "i2c_transfer returned %d\n", ret);
+ return 0;
+ }
+
+ return (data >> 8) | ((data & 0xff) << 8);
+}
+
+/*
+ * Read from the WM8900 register space. Most registers can't be read
+ * and are therefore supplied from cache.
+ */
+static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case WM8900_REG_ID:
+ return wm8900_chip_read(codec, reg);
+ default:
+ return wm8900_read_reg_cache(codec, reg);
+ }
+}
+
+static void wm8900_reset(struct snd_soc_codec *codec)
+{
+ wm8900_write(codec, WM8900_REG_RESET, 0);
+
+ memcpy(codec->reg_cache, wm8900_reg_defaults,
+ sizeof(codec->reg_cache));
+}
+
+static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Clamp headphone outputs */
+ hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMU:
+ /* Enable the input stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT |
+ WM8900_REG_HPCTL1_HP_SHORT2 |
+ WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ msleep(400);
+
+ /* Enable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Remove the shorts */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Short the output */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Disable the output stage */
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+ /* Clamp the outputs and power down input */
+ hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP |
+ WM8900_REG_HPCTL1_HP_CLAMP_OP;
+ hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+ wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ /* Disable everything */
+ wm8900_write(codec, WM8900_REG_HPCTL1, 0);
+ break;
+
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+
+static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" };
+
+static const struct soc_enum mic_bias_level =
+SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt);
+
+static const char *dac_mute_rate_txt[] = { "Fast", "Slow" };
+
+static const struct soc_enum dac_mute_rate =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt);
+
+static const char *dac_deemphasis_txt[] = {
+ "Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt);
+
+static const char *adc_hpf_cut_txt[] = {
+ "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"
+};
+
+static const struct soc_enum adc_hpf_cut =
+SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt);
+
+static const char *lr_txt[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt);
+
+static const struct soc_enum aifr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt);
+
+static const struct soc_enum dacl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt);
+
+static const struct soc_enum dacr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt);
+
+static const char *sidetone_txt[] = {
+ "Disabled", "Left ADC", "Right ADC"
+};
+
+static const struct soc_enum dacl_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt);
+
+static const struct soc_enum dacr_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt);
+
+static const struct snd_kcontrol_new wm8900_snd_controls[] = {
+SOC_ENUM("Mic Bias Level", mic_bias_level),
+
+SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1),
+SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0),
+
+SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0,
+ in_pga_tlv),
+SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1),
+SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0),
+
+SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1),
+SOC_ENUM("DAC Mute Rate", dac_mute_rate),
+SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0),
+SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL,
+ 12, 1, 0),
+
+SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0),
+SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut),
+SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0),
+SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0,
+ adc_svol_tlv),
+SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0,
+ adc_svol_tlv),
+SOC_ENUM("Left Digital Audio Source", aifl_src),
+SOC_ENUM("Right Digital Audio Source", aifr_src),
+
+SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0,
+ dac_boost_tlv),
+SOC_ENUM("Left DAC Source", dacl_src),
+SOC_ENUM("Right DAC Source", dacr_src),
+SOC_ENUM("Left DAC Sidetone", dacl_sidetone),
+SOC_ENUM("Right DAC Sidetone", dacr_sidetone),
+SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume",
+ WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV,
+ 1, 96, 0, dac_tlv),
+SOC_DOUBLE_R_TLV("Digital Capture Volume",
+ WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv),
+
+SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0,
+ out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0,
+ out_mix_tlv),
+
+SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0,
+ in_boost_tlv),
+SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0,
+ in_boost_tlv),
+
+SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("LINEOUT2 Volume",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL,
+ 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT2 Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT2 ZC Switch",
+ WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0),
+SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1,
+ 0, 1, 1),
+
+};
+
+/* add non dapm controls */
+static int wm8900_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8900_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new wm8900_dapm_loutput2_control =
+SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_dapm_routput2_control =
+SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
+SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_routmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linpga_controls[] = {
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinpga_controls[] = {
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0),
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0),
+};
+
+static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" };
+
+static const struct soc_enum wm8900_lineout2_lp_mux =
+SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux);
+
+static const struct snd_kcontrol_new wm8900_lineout2_lp =
+SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux);
+
+static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
+
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("LINEOUT1L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1R"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2R"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_VMID("VMID"),
+
+/* Input */
+SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0,
+ wm8900_linpga_controls,
+ ARRAY_SIZE(wm8900_linpga_controls)),
+SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0,
+ wm8900_rinpga_controls,
+ ARRAY_SIZE(wm8900_rinpga_controls)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
+ wm8900_linmix_controls,
+ ARRAY_SIZE(wm8900_linmix_controls)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0,
+ wm8900_rinmix_controls,
+ ARRAY_SIZE(wm8900_rinmix_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0),
+
+/* Output */
+SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0,
+ wm8900_hp_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp),
+SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
+ wm8900_loutmix_controls,
+ ARRAY_SIZE(wm8900_loutmix_controls)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0,
+ wm8900_routmix_controls,
+ ARRAY_SIZE(wm8900_routmix_controls)),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+/* Inputs */
+{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"},
+{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"},
+
+{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"},
+{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"},
+
+{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"},
+{"Left Input Mixer", "AUX Switch", "AUX"},
+{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"},
+
+{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"},
+{"Right Input Mixer", "AUX Switch", "AUX"},
+{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"},
+
+{"ADCL", NULL, "Left Input Mixer"},
+{"ADCR", NULL, "Right Input Mixer"},
+
+/* Outputs */
+{"LINEOUT1L", NULL, "LINEOUT1L PGA"},
+{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT1R", NULL, "LINEOUT1R PGA"},
+{"LINEOUT1R PGA", NULL, "Right Output Mixer"},
+
+{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"},
+{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
+{"LINEOUT2L", NULL, "LINEOUT2 LP"},
+
+{"LINEOUT2R PGA", NULL, "Right Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"},
+{"LINEOUT2 LP", "Enabled", "Right Output Mixer"},
+{"LINEOUT2R", NULL, "LINEOUT2 LP"},
+
+{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
+{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Left Output Mixer", "DACL Switch", "DACL"},
+
+{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"},
+{"Right Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Right Output Mixer", "DACR Switch", "DACR"},
+
+/* Note that the headphone output stage needs to be connected
+ * externally to LINEOUT2 via DC blocking capacitors. Other
+ * configurations are not supported.
+ *
+ * Note also that left and right headphone paths are treated as a
+ * mono path.
+ */
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"HP_L", NULL, "Headphone Amplifier"},
+{"HP_R", NULL, "Headphone Amplifier"},
+};
+
+static int wm8900_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,
+ ARRAY_SIZE(wm8900_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int wm8900_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+
+ reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x60;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8900_write(codec, WM8900_REG_AUDIO1, reg);
+
+ return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+ u16 fll_ratio;
+ u16 fllclk_div;
+ u16 fll_slow_lock_ref;
+ u16 n;
+ u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+ unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+ unsigned int div;
+
+ BUG_ON(!Fout);
+
+ /* The FLL must run at 90-100MHz which is then scaled down to
+ * the output value by FLLCLK_DIV. */
+ target = Fout;
+ div = 1;
+ while (target < 90000000) {
+ div *= 2;
+ target *= 2;
+ }
+
+ if (target > 100000000)
+ printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d"
+ " Fout=%d\n", target, Fref, Fout);
+ if (div > 32) {
+ printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
+ "Fref=%d, Fout=%d, target=%d\n",
+ div, Fref, Fout, target);
+ return -EINVAL;
+ }
+
+ fll_div->fllclk_div = div >> 2;
+
+ if (Fref < 48000)
+ fll_div->fll_slow_lock_ref = 1;
+ else
+ fll_div->fll_slow_lock_ref = 0;
+
+ Ndiv = target / Fref;
+
+ if (Fref < 1000000)
+ fll_div->fll_ratio = 8;
+ else
+ fll_div->fll_ratio = 1;
+
+ fll_div->n = Ndiv / fll_div->fll_ratio;
+ Nmod = (target / fll_div->fll_ratio) % Fref;
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, Fref);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ fll_div->k = K / 10;
+
+ BUG_ON(target != Fout * (fll_div->fllclk_div << 2));
+ BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n);
+
+ return 0;
+}
+
+static int wm8900_set_fll(struct snd_soc_codec *codec,
+ int fll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm8900_priv *wm8900 = codec->private_data;
+ struct _fll_div fll_div;
+ unsigned int reg;
+
+ if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out)
+ return 0;
+
+ /* The digital side should be disabled during any change. */
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg & (~WM8900_REG_POWER1_FLL_ENA));
+
+ /* Disable the FLL? */
+ if (!freq_in || !freq_out) {
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ reg & (~WM8900_REG_CLOCKING1_MCLK_SRC));
+
+ reg = wm8900_read(codec, WM8900_REG_FLLCTL1);
+ wm8900_write(codec, WM8900_REG_FLLCTL1,
+ reg & (~WM8900_REG_FLLCTL1_OSC_ENA));
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ return 0;
+ }
+
+ if (fll_factors(&fll_div, freq_in, freq_out) != 0)
+ goto reenable;
+
+ wm8900->fll_in = freq_in;
+ wm8900->fll_out = freq_out;
+
+ /* The osclilator *MUST* be enabled before we enable the
+ * digital circuit. */
+ wm8900_write(codec, WM8900_REG_FLLCTL1,
+ fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA);
+
+ wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
+ wm8900_write(codec, WM8900_REG_FLLCTL5,
+ (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f));
+
+ if (fll_div.k) {
+ wm8900_write(codec, WM8900_REG_FLLCTL2,
+ (fll_div.k >> 8) | 0x100);
+ wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
+ } else
+ wm8900_write(codec, WM8900_REG_FLLCTL2, 0);
+
+ if (fll_div.fll_slow_lock_ref)
+ wm8900_write(codec, WM8900_REG_FLLCTL6,
+ WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF);
+ else
+ wm8900_write(codec, WM8900_REG_FLLCTL6, 0);
+
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg | WM8900_REG_POWER1_FLL_ENA);
+
+reenable:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ reg | WM8900_REG_CLOCKING1_MCLK_SRC);
+
+ return 0;
+}
+
+static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out);
+}
+
+static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (div_id) {
+ case WM8900_BCLK_DIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK));
+ break;
+ case WM8900_OPCLK_DIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ wm8900_write(codec, WM8900_REG_CLOCKING1,
+ div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK));
+ break;
+ case WM8900_DAC_LRCLK:
+ reg = wm8900_read(codec, WM8900_REG_AUDIO4);
+ wm8900_write(codec, WM8900_REG_AUDIO4,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_ADC_LRCLK:
+ reg = wm8900_read(codec, WM8900_REG_AUDIO3);
+ wm8900_write(codec, WM8900_REG_AUDIO3,
+ div | (reg & WM8900_LRC_MASK));
+ break;
+ case WM8900_DAC_CLKDIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
+ wm8900_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV));
+ break;
+ case WM8900_ADC_CLKDIV:
+ reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
+ wm8900_write(codec, WM8900_REG_CLOCKING2,
+ div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV));
+ break;
+ case WM8900_LRCLK_MODE:
+ reg = wm8900_read(codec, WM8900_REG_DACCTRL);
+ wm8900_write(codec, WM8900_REG_DACCTRL,
+ div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int clocking1, aif1, aif3, aif4;
+
+ clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1);
+ aif1 = wm8900_read(codec, WM8900_REG_AUDIO1);
+ aif3 = wm8900_read(codec, WM8900_REG_AUDIO3);
+ aif4 = wm8900_read(codec, WM8900_REG_AUDIO4);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+ aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+ aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+ aif1 |= 0x8;
+ 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:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+ aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1);
+ wm8900_write(codec, WM8900_REG_AUDIO1, aif1);
+ wm8900_write(codec, WM8900_REG_AUDIO3, aif3);
+ wm8900_write(codec, WM8900_REG_AUDIO4, aif4);
+
+ return 0;
+}
+
+static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = wm8900_read(codec, WM8900_REG_DACCTRL);
+
+ if (mute)
+ reg |= WM8900_REG_DACCTRL_MUTE;
+ else
+ reg &= ~WM8900_REG_DACCTRL_MUTE;
+
+ wm8900_write(codec, WM8900_REG_DACCTRL, reg);
+
+ return 0;
+}
+
+#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8900_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+struct snd_soc_dai wm8900_dai = {
+ .name = "WM8900 HiFi",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8900_RATES,
+ .formats = WM8900_PCM_FORMATS,
+ },
+ .ops = {
+ .hw_params = wm8900_hw_params,
+ },
+ .dai_ops = {
+ .set_clkdiv = wm8900_set_dai_clkdiv,
+ .set_pll = wm8900_set_dai_pll,
+ .set_fmt = wm8900_set_dai_fmt,
+ .digital_mute = wm8900_digital_mute,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8900_dai);
+
+static int wm8900_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* Enable thermal shutdown */
+ reg = wm8900_read(codec, WM8900_REG_GPIO);
+ wm8900_write(codec, WM8900_REG_GPIO,
+ reg | WM8900_REG_GPIO_TEMP_ENA);
+ reg = wm8900_read(codec, WM8900_REG_ADDCTL);
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ reg | WM8900_REG_ADDCTL_TEMP_SD);
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ /* Charge capacitors if initial power up */
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* STARTUP_BIAS_ENA on */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+
+ /* Startup bias mode */
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* VMID 2x50k */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1);
+
+ /* Allow capacitors to charge */
+ schedule_timeout_interruptible(msecs_to_jiffies(400));
+
+ /* Enable bias */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+
+ wm8900_write(codec, WM8900_REG_ADDCTL, 0);
+
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ }
+
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ (reg & WM8900_REG_POWER1_FLL_ENA) |
+ WM8900_REG_POWER1_BIAS_ENA | 0x1);
+ wm8900_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ wm8900_write(codec, WM8900_REG_POWER3, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Startup bias enable */
+ reg = wm8900_read(codec, WM8900_REG_POWER1);
+ wm8900_write(codec, WM8900_REG_POWER1,
+ reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ wm8900_write(codec, WM8900_REG_ADDCTL,
+ WM8900_REG_ADDCTL_BIAS_SRC |
+ WM8900_REG_ADDCTL_VMID_SOFTST);
+
+ /* Discharge caps */
+ wm8900_write(codec, WM8900_REG_POWER1,
+ WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+ schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+ /* Remove clamp */
+ wm8900_write(codec, WM8900_REG_HPCTL1, 0);
+
+ /* Power down */
+ wm8900_write(codec, WM8900_REG_ADDCTL, 0);
+ wm8900_write(codec, WM8900_REG_POWER1, 0);
+ wm8900_write(codec, WM8900_REG_POWER2, 0);
+ wm8900_write(codec, WM8900_REG_POWER3, 0);
+
+ /* Need to let things settle before stopping the clock
+ * to ensure that restart works, see "Stopping the
+ * master clock" in the datasheet. */
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ wm8900_write(codec, WM8900_REG_POWER2,
+ WM8900_REG_POWER2_SYSCLK_ENA);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm8900_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;
+ struct wm8900_priv *wm8900 = codec->private_data;
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+ int ret;
+
+ /* Stop the FLL in an orderly fashion */
+ ret = wm8900_set_fll(codec, 0, 0, 0);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to stop FLL\n");
+ return ret;
+ }
+
+ wm8900->fll_out = fll_out;
+ wm8900->fll_in = fll_in;
+
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8900_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8900_priv *wm8900 = codec->private_data;
+ u16 *cache;
+ int i, ret;
+
+ cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults),
+ GFP_KERNEL);
+
+ wm8900_reset(codec);
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restart the FLL? */
+ if (wm8900->fll_out) {
+ int fll_out = wm8900->fll_out;
+ int fll_in = wm8900->fll_in;
+
+ wm8900->fll_in = 0;
+ wm8900->fll_out = 0;
+
+ ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to restart FLL\n");
+ return ret;
+ }
+ }
+
+ if (cache) {
+ for (i = 0; i < WM8900_MAXREG; i++)
+ wm8900_write(codec, i, cache[i]);
+ kfree(cache);
+ } else
+ dev_err(&pdev->dev, "Unable to allocate register cache\n");
+
+ return 0;
+}
+
+/*
+ * initialise the WM8900 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8900_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+ unsigned int reg;
+ struct i2c_client *i2c_client = socdev->codec->control_data;
+
+ codec->name = "WM8900";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8900_read;
+ codec->write = wm8900_write;
+ codec->dai = &wm8900_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = WM8900_MAXREG;
+ codec->reg_cache = kmemdup(wm8900_reg_defaults,
+ sizeof(wm8900_reg_defaults), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ reg = wm8900_read(codec, WM8900_REG_ID);
+ if (reg != 0x8900) {
+ dev_err(&i2c_client->dev, "Device is not a WM8900 - ID %x\n",
+ reg);
+ return -ENODEV;
+ }
+
+ codec->private_data = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+ if (codec->private_data == NULL) {
+ ret = -ENOMEM;
+ goto priv_err;
+ }
+
+ /* Read back from the chip */
+ reg = wm8900_chip_read(codec, WM8900_REG_POWER1);
+ reg = (reg >> 12) & 0xf;
+ dev_info(&i2c_client->dev, "WM8900 revision %d\n", reg);
+
+ wm8900_reset(codec);
+
+ /* Latch the volume update bits */
+ wm8900_write(codec, WM8900_REG_LINVOL,
+ wm8900_read(codec, WM8900_REG_LINVOL) | 0x100);
+ wm8900_write(codec, WM8900_REG_RINVOL,
+ wm8900_read(codec, WM8900_REG_RINVOL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LOUT1CTL,
+ wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_ROUT1CTL,
+ wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LOUT2CTL,
+ wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_ROUT2CTL,
+ wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
+ wm8900_write(codec, WM8900_REG_LDAC_DV,
+ wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_RDAC_DV,
+ wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_LADC_DV,
+ wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100);
+ wm8900_write(codec, WM8900_REG_RADC_DV,
+ wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100);
+
+ /* Set the DAC and mixer output bias */
+ wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
+
+ /* Register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Failed to register new PCMs\n");
+ goto pcm_err;
+ }
+
+ /* Turn the chip on */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ wm8900_add_controls(codec);
+ wm8900_add_widgets(codec);
+
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "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);
+priv_err:
+ kfree(codec->private_data);
+ return ret;
+}
+
+static struct snd_soc_device *wm8900_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Magic definition of all other variables and things */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver wm8900_i2c_driver;
+static struct i2c_client client_template;
+
+/* If the i2c layer weren't so broken, we could pass this kind of data
+ around */
+static int wm8900_codec_probe(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct snd_soc_device *socdev = wm8900_socdev;
+ struct wm8900_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;
+
+ dev_err(&adap->dev, "Probe on %x\n", addr);
+
+ 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) {
+ dev_err(&adap->dev,
+ "failed to attach codec at addr %x\n", addr);
+ goto err;
+ }
+
+ ret = wm8900_init(socdev);
+ if (ret < 0) {
+ dev_err(&adap->dev, "failed to initialise WM8900\n");
+ goto err;
+ }
+ return ret;
+
+err:
+ kfree(codec);
+ kfree(i2c);
+ return ret;
+}
+
+static int wm8900_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 wm8900_i2c_attach(struct i2c_adapter *adap)
+{
+ return i2c_probe(adap, &addr_data, wm8900_codec_probe);
+}
+
+/* corgi i2c codec control layer */
+static struct i2c_driver wm8900_i2c_driver = {
+ .driver = {
+ .name = "WM8900 I2C codec",
+ .owner = THIS_MODULE,
+ },
+ .attach_adapter = wm8900_i2c_attach,
+ .detach_client = wm8900_i2c_detach,
+ .command = NULL,
+};
+
+static struct i2c_client client_template = {
+ .name = "WM8900",
+ .driver = &wm8900_i2c_driver,
+};
+#endif
+
+static int wm8900_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8900_setup_data *setup;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "WM8900 Audio Codec\n");
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ socdev->codec = codec;
+
+ codec->set_bias_level = wm8900_set_bias_level;
+
+ wm8900_socdev = socdev;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ normal_i2c[0] = setup->i2c_address;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8900_i2c_driver);
+ if (ret != 0)
+ printk(KERN_ERR "can't add i2c driver");
+ }
+#else
+#error Non-I2C interfaces not yet supported
+#endif
+ return ret;
+}
+
+/* power down chip */
+static int wm8900_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)
+ wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8900_i2c_driver);
+#endif
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8900 = {
+ .probe = wm8900_probe,
+ .remove = wm8900_remove,
+ .suspend = wm8900_suspend,
+ .resume = wm8900_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900);
+
+MODULE_DESCRIPTION("ASoC WM8900 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h
new file mode 100644
index 0000000..ba450d9
--- /dev/null
+++ b/sound/soc/codecs/wm8900.h
@@ -0,0 +1,64 @@
+/*
+ * wm8900.h -- WM890 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8900_H
+#define _WM8900_H
+
+#define WM8900_FLL 1
+
+#define WM8900_BCLK_DIV 1
+#define WM8900_ADC_CLKDIV 2
+#define WM8900_DAC_CLKDIV 3
+#define WM8900_ADC_LRCLK 4
+#define WM8900_DAC_LRCLK 5
+#define WM8900_OPCLK_DIV 6
+#define WM8900_LRCLK_MODE 7
+
+#define WM8900_BCLK_DIV_1 0x00
+#define WM8900_BCLK_DIV_1_5 0x02
+#define WM8900_BCLK_DIV_2 0x04
+#define WM8900_BCLK_DIV_3 0x06
+#define WM8900_BCLK_DIV_4 0x08
+#define WM8900_BCLK_DIV_5_5 0x0a
+#define WM8900_BCLK_DIV_6 0x0c
+#define WM8900_BCLK_DIV_8 0x0e
+#define WM8900_BCLK_DIV_11 0x10
+#define WM8900_BCLK_DIV_12 0x12
+#define WM8900_BCLK_DIV_16 0x14
+#define WM8900_BCLK_DIV_22 0x16
+#define WM8900_BCLK_DIV_24 0x18
+#define WM8900_BCLK_DIV_32 0x1a
+#define WM8900_BCLK_DIV_44 0x1c
+#define WM8900_BCLK_DIV_48 0x1e
+
+#define WM8900_ADC_CLKDIV_1 0x00
+#define WM8900_ADC_CLKDIV_1_5 0x20
+#define WM8900_ADC_CLKDIV_2 0x40
+#define WM8900_ADC_CLKDIV_3 0x60
+#define WM8900_ADC_CLKDIV_4 0x80
+#define WM8900_ADC_CLKDIV_5_5 0xa0
+#define WM8900_ADC_CLKDIV_6 0xc0
+
+#define WM8900_DAC_CLKDIV_1 0x00
+#define WM8900_DAC_CLKDIV_1_5 0x04
+#define WM8900_DAC_CLKDIV_2 0x08
+#define WM8900_DAC_CLKDIV_3 0x0c
+#define WM8900_DAC_CLKDIV_4 0x10
+#define WM8900_DAC_CLKDIV_5_5 0x14
+#define WM8900_DAC_CLKDIV_6 0x18
+
+#define WM8900_
+
+struct wm8900_setup_data {
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8900_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8900;
+
+#endif
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
new file mode 100644
index 0000000..ce40d78
--- /dev/null
+++ b/sound/soc/codecs/wm8903.c
@@ -0,0 +1,1813 @@
+/*
+ * wm8903.c -- WM8903 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ * - TDM mode configuration.
+ * - Mic detect.
+ * - Digital microphone support.
+ * - Interrupt support (mic detect and sequencer).
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8903.h"
+
+struct wm8903_priv {
+ int sysclk;
+
+ /* Reference counts */
+ int charge_pump_users;
+ int class_w_users;
+ int playback_active;
+ int capture_active;
+
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+/* Register defaults at reset */
+static u16 wm8903_reg_defaults[] = {
+ 0x8903, /* R0 - SW Reset and ID */
+ 0x0000, /* R1 - Revision Number */
+ 0x0000, /* R2 */
+ 0x0000, /* R3 */
+ 0x0018, /* R4 - Bias Control 0 */
+ 0x0000, /* R5 - VMID Control 0 */
+ 0x0000, /* R6 - Mic Bias Control 0 */
+ 0x0000, /* R7 */
+ 0x0001, /* R8 - Analogue DAC 0 */
+ 0x0000, /* R9 */
+ 0x0001, /* R10 - Analogue ADC 0 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 - Power Management 0 */
+ 0x0000, /* R13 - Power Management 1 */
+ 0x0000, /* R14 - Power Management 2 */
+ 0x0000, /* R15 - Power Management 3 */
+ 0x0000, /* R16 - Power Management 4 */
+ 0x0000, /* R17 - Power Management 5 */
+ 0x0000, /* R18 - Power Management 6 */
+ 0x0000, /* R19 */
+ 0x0400, /* R20 - Clock Rates 0 */
+ 0x0D07, /* R21 - Clock Rates 1 */
+ 0x0000, /* R22 - Clock Rates 2 */
+ 0x0000, /* R23 */
+ 0x0050, /* R24 - Audio Interface 0 */
+ 0x0242, /* R25 - Audio Interface 1 */
+ 0x0008, /* R26 - Audio Interface 2 */
+ 0x0022, /* R27 - Audio Interface 3 */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x00C0, /* R30 - DAC Digital Volume Left */
+ 0x00C0, /* R31 - DAC Digital Volume Right */
+ 0x0000, /* R32 - DAC Digital 0 */
+ 0x0000, /* R33 - DAC Digital 1 */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x00C0, /* R36 - ADC Digital Volume Left */
+ 0x00C0, /* R37 - ADC Digital Volume Right */
+ 0x0000, /* R38 - ADC Digital 0 */
+ 0x0073, /* R39 - Digital Microphone 0 */
+ 0x09BF, /* R40 - DRC 0 */
+ 0x3241, /* R41 - DRC 1 */
+ 0x0020, /* R42 - DRC 2 */
+ 0x0000, /* R43 - DRC 3 */
+ 0x0085, /* R44 - Analogue Left Input 0 */
+ 0x0085, /* R45 - Analogue Right Input 0 */
+ 0x0044, /* R46 - Analogue Left Input 1 */
+ 0x0044, /* R47 - Analogue Right Input 1 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0008, /* R50 - Analogue Left Mix 0 */
+ 0x0004, /* R51 - Analogue Right Mix 0 */
+ 0x0000, /* R52 - Analogue Spk Mix Left 0 */
+ 0x0000, /* R53 - Analogue Spk Mix Left 1 */
+ 0x0000, /* R54 - Analogue Spk Mix Right 0 */
+ 0x0000, /* R55 - Analogue Spk Mix Right 1 */
+ 0x0000, /* R56 */
+ 0x002D, /* R57 - Analogue OUT1 Left */
+ 0x002D, /* R58 - Analogue OUT1 Right */
+ 0x0039, /* R59 - Analogue OUT2 Left */
+ 0x0039, /* R60 - Analogue OUT2 Right */
+ 0x0100, /* R61 */
+ 0x0139, /* R62 - Analogue OUT3 Left */
+ 0x0139, /* R63 - Analogue OUT3 Right */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 - Analogue SPK Output Control 0 */
+ 0x0000, /* R66 */
+ 0x0010, /* R67 - DC Servo 0 */
+ 0x0100, /* R68 */
+ 0x00A4, /* R69 - DC Servo 2 */
+ 0x0807, /* R70 */
+ 0x0000, /* R71 */
+ 0x0000, /* R72 */
+ 0x0000, /* R73 */
+ 0x0000, /* R74 */
+ 0x0000, /* R75 */
+ 0x0000, /* R76 */
+ 0x0000, /* R77 */
+ 0x0000, /* R78 */
+ 0x000E, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 */
+ 0x0000, /* R85 */
+ 0x0000, /* R86 */
+ 0x0006, /* R87 */
+ 0x0000, /* R88 */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 - Analogue HP 0 */
+ 0x0060, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 - Analogue Lineout 0 */
+ 0x0060, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - Charge Pump 0 */
+ 0x1F25, /* R99 */
+ 0x2B19, /* R100 */
+ 0x01C0, /* R101 */
+ 0x01EF, /* R102 */
+ 0x2B00, /* R103 */
+ 0x0000, /* R104 - Class W 0 */
+ 0x01C0, /* R105 */
+ 0x1C10, /* R106 */
+ 0x0000, /* R107 */
+ 0x0000, /* R108 - Write Sequencer 0 */
+ 0x0000, /* R109 - Write Sequencer 1 */
+ 0x0000, /* R110 - Write Sequencer 2 */
+ 0x0000, /* R111 - Write Sequencer 3 */
+ 0x0000, /* R112 - Write Sequencer 4 */
+ 0x0000, /* R113 */
+ 0x0000, /* R114 - Control Interface */
+ 0x0000, /* R115 */
+ 0x00A8, /* R116 - GPIO Control 1 */
+ 0x00A8, /* R117 - GPIO Control 2 */
+ 0x00A8, /* R118 - GPIO Control 3 */
+ 0x0220, /* R119 - GPIO Control 4 */
+ 0x01A0, /* R120 - GPIO Control 5 */
+ 0x0000, /* R121 - Interrupt Status 1 */
+ 0xFFFF, /* R122 - Interrupt Status 1 Mask */
+ 0x0000, /* R123 - Interrupt Polarity 1 */
+ 0x0000, /* R124 */
+ 0x0003, /* R125 */
+ 0x0000, /* R126 - Interrupt Control */
+ 0x0000, /* R127 */
+ 0x0005, /* R128 */
+ 0x0000, /* R129 - Control Interface Test 1 */
+ 0x0000, /* R130 */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 */
+ 0x03FF, /* R135 */
+ 0x0007, /* R136 */
+ 0x0040, /* R137 */
+ 0x0000, /* R138 */
+ 0x0000, /* R139 */
+ 0x0000, /* R140 */
+ 0x0000, /* R141 */
+ 0x0000, /* R142 */
+ 0x0000, /* R143 */
+ 0x0000, /* R144 */
+ 0x0000, /* R145 */
+ 0x0000, /* R146 */
+ 0x0000, /* R147 */
+ 0x4000, /* R148 */
+ 0x6810, /* R149 - Charge Pump Test 1 */
+ 0x0004, /* R150 */
+ 0x0000, /* R151 */
+ 0x0000, /* R152 */
+ 0x0000, /* R153 */
+ 0x0000, /* R154 */
+ 0x0000, /* R155 */
+ 0x0000, /* R156 */
+ 0x0000, /* R157 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0028, /* R164 - Clock Rate Test 4 */
+ 0x0004, /* R165 */
+ 0x0000, /* R166 */
+ 0x0060, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 - Analogue Output Bias 0 */
+};
+
+static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
+
+ return cache[reg];
+}
+
+static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg)
+{
+ struct i2c_msg xfer[2];
+ u16 data;
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 2;
+ xfer[1].buf = (u8 *)&data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret != 2) {
+ pr_err("i2c_transfer returned %d\n", ret);
+ return 0;
+ }
+
+ return (data >> 8) | ((data & 0xff) << 8);
+}
+
+static unsigned int wm8903_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case WM8903_SW_RESET_AND_ID:
+ case WM8903_REVISION_NUMBER:
+ case WM8903_INTERRUPT_STATUS_1:
+ case WM8903_WRITE_SEQUENCER_4:
+ return wm8903_hw_read(codec, reg);
+
+ default:
+ return wm8903_read_reg_cache(codec, reg);
+ }
+}
+
+static void wm8903_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
+
+ switch (reg) {
+ case WM8903_SW_RESET_AND_ID:
+ case WM8903_REVISION_NUMBER:
+ break;
+
+ default:
+ cache[reg] = value;
+ break;
+ }
+}
+
+static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ wm8903_write_reg_cache(codec, reg, value);
+
+ /* Data format is 1 byte of address followed by 2 bytes of data */
+ data[0] = reg;
+ data[1] = (value >> 8) & 0xff;
+ data[2] = value & 0xff;
+
+ if (codec->hw_write(codec->control_data, data, 3) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
+{
+ u16 reg[5];
+ struct i2c_client *i2c = codec->control_data;
+
+ BUG_ON(start > 48);
+
+ /* Enable the sequencer */
+ reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0);
+ reg[0] |= WM8903_WSEQ_ENA;
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
+
+ dev_dbg(&i2c->dev, "Starting sequence at %d\n", start);
+
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_3,
+ start | WM8903_WSEQ_START);
+
+ /* Wait for it to complete. If we have the interrupt wired up then
+ * we could block waiting for an interrupt, though polling may still
+ * be desirable for diagnostic purposes.
+ */
+ do {
+ msleep(10);
+
+ reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4);
+ } while (reg[4] & WM8903_WSEQ_BUSY);
+
+ dev_dbg(&i2c->dev, "Sequence complete\n");
+
+ /* Disable the sequencer again */
+ wm8903_write(codec, WM8903_WRITE_SEQUENCER_0,
+ reg[0] & ~WM8903_WSEQ_ENA);
+
+ return 0;
+}
+
+static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache)
+{
+ int i;
+
+ /* There really ought to be something better we can do here :/ */
+ for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+ cache[i] = wm8903_hw_read(codec, i);
+}
+
+static void wm8903_reset(struct snd_soc_codec *codec)
+{
+ wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0);
+}
+
+#define WM8903_OUTPUT_SHORT 0x8
+#define WM8903_OUTPUT_OUT 0x4
+#define WM8903_OUTPUT_INT 0x2
+#define WM8903_OUTPUT_IN 0x1
+
+/*
+ * Event for headphone and line out amplifier power changes. Special
+ * power up/down sequences are required in order to maximise pop/click
+ * performance.
+ */
+static int wm8903_output_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ u16 val;
+ u16 reg;
+ int shift;
+ u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0);
+
+ switch (w->reg) {
+ case WM8903_POWER_MANAGEMENT_2:
+ reg = WM8903_ANALOGUE_HP_0;
+ break;
+ case WM8903_POWER_MANAGEMENT_3:
+ reg = WM8903_ANALOGUE_LINEOUT_0;
+ break;
+ default:
+ BUG();
+ }
+
+ switch (w->shift) {
+ case 0:
+ shift = 0;
+ break;
+ case 1:
+ shift = 4;
+ break;
+ default:
+ BUG();
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMU) {
+ val = wm8903_read(codec, reg);
+
+ /* Short the output */
+ val &= ~(WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+
+ wm8903->charge_pump_users++;
+
+ dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
+ wm8903->charge_pump_users);
+
+ if (wm8903->charge_pump_users == 1) {
+ dev_dbg(&i2c->dev, "Enabling charge pump\n");
+ wm8903_write(codec, WM8903_CHARGE_PUMP_0,
+ cp_reg | WM8903_CP_ENA);
+ mdelay(4);
+ }
+ }
+
+ if (event & SND_SOC_DAPM_POST_PMU) {
+ val = wm8903_read(codec, reg);
+
+ val |= (WM8903_OUTPUT_IN << shift);
+ wm8903_write(codec, reg, val);
+
+ val |= (WM8903_OUTPUT_INT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Turn on the output ENA_OUTP */
+ val |= (WM8903_OUTPUT_OUT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Remove the short */
+ val |= (WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+ }
+
+ if (event & SND_SOC_DAPM_PRE_PMD) {
+ val = wm8903_read(codec, reg);
+
+ /* Short the output */
+ val &= ~(WM8903_OUTPUT_SHORT << shift);
+ wm8903_write(codec, reg, val);
+
+ /* Then disable the intermediate and output stages */
+ val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
+ WM8903_OUTPUT_IN) << shift);
+ wm8903_write(codec, reg, val);
+ }
+
+ if (event & SND_SOC_DAPM_POST_PMD) {
+ wm8903->charge_pump_users--;
+
+ dev_dbg(&i2c->dev, "Charge pump use count now %d\n",
+ wm8903->charge_pump_users);
+
+ if (wm8903->charge_pump_users == 0) {
+ dev_dbg(&i2c->dev, "Disabling charge pump\n");
+ wm8903_write(codec, WM8903_CHARGE_PUMP_0,
+ cp_reg & ~WM8903_CP_ENA);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * When used with DAC outputs only the WM8903 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources. Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * All the relevant controls are simple switches.
+ */
+static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_codec *codec = widget->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ u16 reg;
+ int ret;
+
+ reg = wm8903_read(codec, WM8903_CLASS_W_0);
+
+ /* Turn it off if we're about to enable bypass */
+ if (ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 0) {
+ dev_dbg(&i2c->dev, "Disabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg &
+ ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V));
+ }
+ wm8903->class_w_users++;
+ }
+
+ /* Implement the change */
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ /* If we've just disabled the last bypass path turn Class W on */
+ if (!ucontrol->value.integer.value[0]) {
+ if (wm8903->class_w_users == 1) {
+ dev_dbg(&i2c->dev, "Enabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg |
+ WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ }
+ wm8903->class_w_users--;
+ }
+
+ dev_dbg(&i2c->dev, "Bypass use count now %d\n",
+ wm8903->class_w_users);
+
+ return ret;
+}
+
+#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+/* ALSA can only do steps of .01dB */
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0);
+
+static const char *drc_slope_text[] = {
+ "1", "1/2", "1/4", "1/8", "1/16", "0"
+};
+
+static const struct soc_enum drc_slope_r0 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text);
+
+static const struct soc_enum drc_slope_r1 =
+ SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text);
+
+static const char *drc_attack_text[] = {
+ "instantaneous",
+ "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms",
+ "46.4ms", "92.8ms", "185.6ms"
+};
+
+static const struct soc_enum drc_attack =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+ "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s",
+ "23.87s", "47.56s"
+};
+
+static const struct soc_enum drc_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text);
+
+static const char *drc_ff_delay_text[] = {
+ "5 samples", "9 samples"
+};
+
+static const struct soc_enum drc_ff_delay =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text);
+
+static const char *drc_qr_decay_text[] = {
+ "0.725ms", "1.45ms", "5.8ms"
+};
+
+static const struct soc_enum drc_qr_decay =
+ SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text);
+
+static const char *drc_smoothing_text[] = {
+ "Low", "Medium", "High"
+};
+
+static const struct soc_enum drc_smoothing =
+ SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text);
+
+static const char *soft_mute_text[] = {
+ "Fast (fs/2)", "Slow (fs/32)"
+};
+
+static const struct soc_enum soft_mute =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text);
+
+static const char *mute_mode_text[] = {
+ "Hard", "Soft"
+};
+
+static const struct soc_enum mute_mode =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text);
+
+static const char *dac_deemphasis_text[] = {
+ "Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+ SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text);
+
+static const char *companding_text[] = {
+ "ulaw", "alaw"
+};
+
+static const struct soc_enum dac_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text);
+
+static const struct soc_enum adc_companding =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text);
+
+static const char *input_mode_text[] = {
+ "Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum linput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rinput_mode_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *linput_mux_text[] = {
+ "IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum linput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text);
+
+static const struct soc_enum linput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text);
+
+static const char *rinput_mux_text[] = {
+ "IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rinput_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text);
+
+static const struct soc_enum rinput_inv_enum =
+ SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
+
+
+static const struct snd_kcontrol_new wm8903_snd_controls[] = {
+
+/* Input PGAs - No TLV since the scale depends on PGA mode */
+SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1,
+ 6, 1, 0),
+
+SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 7, 1, 1),
+SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0,
+ 0, 31, 0),
+SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1,
+ 6, 1, 0),
+
+/* ADCs */
+SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0),
+SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1),
+SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8903_DRC_3, 5, 124, 1,
+ drc_tlv_thresh),
+SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp),
+SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min),
+SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff_delay),
+SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0),
+SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0),
+SOC_SINGLE_TLV("DRC QR Threashold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC QR Decay Rate", drc_qr_decay),
+SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0),
+SOC_ENUM("DRC Smoothing Threashold", drc_smoothing),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
+ WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+SOC_ENUM("ADC Companding Mode", adc_companding),
+SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
+
+/* DAC */
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
+ WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
+SOC_ENUM("DAC Soft Mute Rate", soft_mute),
+SOC_ENUM("DAC Mute Mode", mute_mode),
+SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0),
+SOC_ENUM("DAC De-emphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sloping Stopband Filter Switch",
+ WM8903_DAC_DIGITAL_1, 11, 1, 0),
+SOC_ENUM("DAC Companding Mode", dac_companding),
+SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0),
+
+/* Headphones */
+SOC_DOUBLE_R("Headphone Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Headphone Volume",
+ WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Line out */
+SOC_DOUBLE_R("Line Out Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 8, 1, 1),
+SOC_DOUBLE_R("Line Out ZC Switch",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 6, 1, 0),
+SOC_DOUBLE_R_TLV("Line Out Volume",
+ WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+ 0, 63, 0, out_tlv),
+
+/* Speaker */
+SOC_DOUBLE_R("Speaker Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Speaker ZC Switch",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+ WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT,
+ 0, 63, 0, out_tlv),
+};
+
+static int wm8903_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8903_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new linput_mode_mux =
+ SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum);
+
+static const struct snd_kcontrol_new rinput_mode_mux =
+ SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum);
+
+static const struct snd_kcontrol_new linput_mux =
+ SOC_DAPM_ENUM("Left Input Mux", linput_enum);
+
+static const struct snd_kcontrol_new linput_inv_mux =
+ SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum);
+
+static const struct snd_kcontrol_new rinput_mux =
+ SOC_DAPM_ENUM("Right Input Mux", rinput_enum);
+
+static const struct snd_kcontrol_new rinput_inv_mux =
+ SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum);
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0,
+ 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+ 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux),
+SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &linput_inv_mux),
+SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux),
+
+SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux),
+SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &rinput_inv_mux),
+SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux),
+
+SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
+ left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0,
+ right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0,
+ left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
+ right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+ 1, 0, NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+ 0, 0, NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
+ NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
+ NULL, 0, wm8903_output_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
+ NULL, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+ { "Left Input Mux", "IN1L", "IN1L" },
+ { "Left Input Mux", "IN2L", "IN2L" },
+ { "Left Input Mux", "IN3L", "IN3L" },
+
+ { "Left Input Inverting Mux", "IN1L", "IN1L" },
+ { "Left Input Inverting Mux", "IN2L", "IN2L" },
+ { "Left Input Inverting Mux", "IN3L", "IN3L" },
+
+ { "Right Input Mux", "IN1R", "IN1R" },
+ { "Right Input Mux", "IN2R", "IN2R" },
+ { "Right Input Mux", "IN3R", "IN3R" },
+
+ { "Right Input Inverting Mux", "IN1R", "IN1R" },
+ { "Right Input Inverting Mux", "IN2R", "IN2R" },
+ { "Right Input Inverting Mux", "IN3R", "IN3R" },
+
+ { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Line",
+ "Left Input Inverting Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Mux" },
+ { "Left Input Mode Mux", "Differential Mic",
+ "Left Input Inverting Mux" },
+
+ { "Right Input Mode Mux", "Single-Ended",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Line",
+ "Right Input Inverting Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Mux" },
+ { "Right Input Mode Mux", "Differential Mic",
+ "Right Input Inverting Mux" },
+
+ { "Left Input PGA", NULL, "Left Input Mode Mux" },
+ { "Right Input PGA", NULL, "Right Input Mode Mux" },
+
+ { "ADCL", NULL, "Left Input PGA" },
+ { "ADCR", NULL, "Right Input PGA" },
+
+ { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Output Mixer", "DACL Switch", "DACL" },
+ { "Left Output Mixer", "DACR Switch", "DACR" },
+
+ { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Output Mixer", "DACL Switch", "DACL" },
+ { "Right Output Mixer", "DACR Switch", "DACR" },
+
+ { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Left Speaker Mixer", "DACL Switch", "DACL" },
+ { "Left Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+ { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+ { "Right Speaker Mixer", "DACL Switch", "DACL" },
+ { "Right Speaker Mixer", "DACR Switch", "DACR" },
+
+ { "Left Line Output PGA", NULL, "Left Output Mixer" },
+ { "Right Line Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Headphone Output PGA", NULL, "Left Output Mixer" },
+ { "Right Headphone Output PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker PGA", NULL, "Left Speaker Mixer" },
+ { "Right Speaker PGA", NULL, "Right Speaker Mixer" },
+
+ { "HPOUTL", NULL, "Left Headphone Output PGA" },
+ { "HPOUTR", NULL, "Right Headphone Output PGA" },
+
+ { "LINEOUTL", NULL, "Left Line Output PGA" },
+ { "LINEOUTR", NULL, "Right Line Output PGA" },
+
+ { "LOP", NULL, "Left Speaker PGA" },
+ { "LON", NULL, "Left Speaker PGA" },
+
+ { "ROP", NULL, "Right Speaker PGA" },
+ { "RON", NULL, "Right Speaker PGA" },
+};
+
+static int wm8903_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets,
+ ARRAY_SIZE(wm8903_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int wm8903_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct i2c_client *i2c = codec->control_data;
+ u16 reg, reg2;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
+ reg &= ~(WM8903_VMID_RES_MASK);
+ reg |= WM8903_VMID_RES_50K;
+ wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ wm8903_run_sequence(codec, 0);
+ wm8903_sync_reg_cache(codec, codec->reg_cache);
+
+ /* Enable low impedence charge pump output */
+ reg = wm8903_read(codec,
+ WM8903_CONTROL_INTERFACE_TEST_1);
+ wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+ reg | WM8903_TEST_KEY);
+ reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1);
+ wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1,
+ reg2 | WM8903_CP_SW_KELVIN_MODE_MASK);
+ wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+ reg);
+
+ /* By default no bypass paths are enabled so
+ * enable Class W support.
+ */
+ dev_dbg(&i2c->dev, "Enabling Class W\n");
+ wm8903_write(codec, WM8903_CLASS_W_0, reg |
+ WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ }
+
+ reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
+ reg &= ~(WM8903_VMID_RES_MASK);
+ reg |= WM8903_VMID_RES_250K;
+ wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ wm8903_run_sequence(codec, 32);
+ break;
+ }
+
+ codec->bias_level = level;
+
+ return 0;
+}
+
+static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+
+ wm8903->sysclk = freq;
+
+ return 0;
+}
+
+static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
+
+ aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK |
+ WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif1 |= WM8903_LRCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8903_BCLK_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= 0x3 | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ aif1 |= 0x1;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ 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:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ case SND_SOC_DAIFMT_LEFT_J:
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8903_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8903_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+
+ return 0;
+}
+
+static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
+
+ if (mute)
+ reg |= WM8903_DAC_MUTE;
+ else
+ reg &= ~WM8903_DAC_MUTE;
+
+ wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg);
+
+ return 0;
+}
+
+/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended
+ * for optimal performance so we list the lower rates first and match
+ * on the last match we find. */
+static struct {
+ int div;
+ int rate;
+ int mode;
+ int mclk_div;
+} clk_sys_ratios[] = {
+ { 64, 0x0, 0x0, 1 },
+ { 68, 0x0, 0x1, 1 },
+ { 125, 0x0, 0x2, 1 },
+ { 128, 0x1, 0x0, 1 },
+ { 136, 0x1, 0x1, 1 },
+ { 192, 0x2, 0x0, 1 },
+ { 204, 0x2, 0x1, 1 },
+
+ { 64, 0x0, 0x0, 2 },
+ { 68, 0x0, 0x1, 2 },
+ { 125, 0x0, 0x2, 2 },
+ { 128, 0x1, 0x0, 2 },
+ { 136, 0x1, 0x1, 2 },
+ { 192, 0x2, 0x0, 2 },
+ { 204, 0x2, 0x1, 2 },
+
+ { 250, 0x2, 0x2, 1 },
+ { 256, 0x3, 0x0, 1 },
+ { 272, 0x3, 0x1, 1 },
+ { 384, 0x4, 0x0, 1 },
+ { 408, 0x4, 0x1, 1 },
+ { 375, 0x4, 0x2, 1 },
+ { 512, 0x5, 0x0, 1 },
+ { 544, 0x5, 0x1, 1 },
+ { 500, 0x5, 0x2, 1 },
+ { 768, 0x6, 0x0, 1 },
+ { 816, 0x6, 0x1, 1 },
+ { 750, 0x6, 0x2, 1 },
+ { 1024, 0x7, 0x0, 1 },
+ { 1088, 0x7, 0x1, 1 },
+ { 1000, 0x7, 0x2, 1 },
+ { 1408, 0x8, 0x0, 1 },
+ { 1496, 0x8, 0x1, 1 },
+ { 1536, 0x9, 0x0, 1 },
+ { 1632, 0x9, 0x1, 1 },
+ { 1500, 0x9, 0x2, 1 },
+
+ { 250, 0x2, 0x2, 2 },
+ { 256, 0x3, 0x0, 2 },
+ { 272, 0x3, 0x1, 2 },
+ { 384, 0x4, 0x0, 2 },
+ { 408, 0x4, 0x1, 2 },
+ { 375, 0x4, 0x2, 2 },
+ { 512, 0x5, 0x0, 2 },
+ { 544, 0x5, 0x1, 2 },
+ { 500, 0x5, 0x2, 2 },
+ { 768, 0x6, 0x0, 2 },
+ { 816, 0x6, 0x1, 2 },
+ { 750, 0x6, 0x2, 2 },
+ { 1024, 0x7, 0x0, 2 },
+ { 1088, 0x7, 0x1, 2 },
+ { 1000, 0x7, 0x2, 2 },
+ { 1408, 0x8, 0x0, 2 },
+ { 1496, 0x8, 0x1, 2 },
+ { 1536, 0x9, 0x0, 2 },
+ { 1632, 0x9, 0x1, 2 },
+ { 1500, 0x9, 0x2, 2 },
+};
+
+/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */
+static struct {
+ int ratio;
+ int div;
+} bclk_divs[] = {
+ { 10, 0 },
+ { 15, 1 },
+ { 20, 2 },
+ { 30, 3 },
+ { 40, 4 },
+ { 50, 5 },
+ { 55, 6 },
+ { 60, 7 },
+ { 80, 8 },
+ { 100, 9 },
+ { 110, 10 },
+ { 120, 11 },
+ { 160, 12 },
+ { 200, 13 },
+ { 220, 14 },
+ { 240, 15 },
+ { 250, 16 },
+ { 300, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+/* Sample rates for DSP */
+static struct {
+ int rate;
+ int value;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 2 },
+ { 16000, 3 },
+ { 22050, 4 },
+ { 24000, 5 },
+ { 32000, 6 },
+ { 44100, 7 },
+ { 48000, 8 },
+ { 88200, 9 },
+ { 96000, 10 },
+ { 0, 0 },
+};
+
+static int wm8903_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ wm8903->playback_active++;
+ else
+ wm8903->capture_active++;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (wm8903->master_substream) {
+ master_runtime = wm8903->master_substream->runtime;
+
+ dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
+ master_runtime->sample_bits,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ wm8903->slave_substream = substream;
+ } else
+ wm8903->master_substream = substream;
+
+ return 0;
+}
+
+static void wm8903_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm8903_priv *wm8903 = codec->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ wm8903->playback_active--;
+ else
+ wm8903->capture_active--;
+
+ if (wm8903->master_substream == substream)
+ wm8903->master_substream = wm8903->slave_substream;
+
+ wm8903->slave_substream = NULL;
+}
+
+static int wm8903_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 wm8903_priv *wm8903 = codec->private_data;
+ struct i2c_client *i2c = codec->control_data;
+ int fs = params_rate(params);
+ int bclk;
+ int bclk_div;
+ int i;
+ int dsp_config;
+ int clk_config;
+ int best_val;
+ int cur_val;
+ int clk_sys;
+
+ u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
+ u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2);
+ u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3);
+ u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0);
+ u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1);
+
+ if (substream == wm8903->slave_substream) {
+ dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ /* Configure sample rate logic for DSP - choose nearest rate */
+ dsp_config = 0;
+ best_val = abs(sample_rates[dsp_config].rate - fs);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ cur_val = abs(sample_rates[i].rate - fs);
+ if (cur_val <= best_val) {
+ dsp_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ /* Constraints should stop us hitting this but let's make sure */
+ if (wm8903->capture_active)
+ switch (sample_rates[dsp_config].rate) {
+ case 88200:
+ case 96000:
+ dev_err(&i2c->dev, "%dHz unsupported by ADC\n",
+ fs);
+ return -EINVAL;
+
+ default:
+ break;
+ }
+
+ dev_dbg(&i2c->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate);
+ clock1 &= ~WM8903_SAMPLE_RATE_MASK;
+ clock1 |= sample_rates[dsp_config].value;
+
+ aif1 &= ~WM8903_AIF_WL_MASK;
+ bclk = 2 * fs;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ bclk *= 16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ bclk *= 20;
+ aif1 |= 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ bclk *= 24;
+ aif1 |= 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ bclk *= 32;
+ aif1 |= 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n",
+ wm8903->sysclk, fs);
+
+ /* We may not have an MCLK which allows us to generate exactly
+ * the clock we want, particularly with USB derived inputs, so
+ * approximate.
+ */
+ clk_config = 0;
+ best_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[0].mclk_div *
+ clk_sys_ratios[0].div)) - fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) {
+ cur_val = abs((wm8903->sysclk /
+ (clk_sys_ratios[i].mclk_div *
+ clk_sys_ratios[i].div)) - fs);
+
+ if (cur_val <= best_val) {
+ clk_config = i;
+ best_val = cur_val;
+ }
+ }
+
+ if (clk_sys_ratios[clk_config].mclk_div == 2) {
+ clock0 |= WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk / 2;
+ } else {
+ clock0 &= ~WM8903_MCLKDIV2;
+ clk_sys = wm8903->sysclk;
+ }
+
+ clock1 &= ~(WM8903_CLK_SYS_RATE_MASK |
+ WM8903_CLK_SYS_MODE_MASK);
+ clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT;
+ clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT;
+
+ dev_dbg(&i2c->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n",
+ clk_sys_ratios[clk_config].rate,
+ clk_sys_ratios[clk_config].mode,
+ clk_sys_ratios[clk_config].div);
+
+ dev_dbg(&i2c->dev, "Actual CLK_SYS = %dHz\n", clk_sys);
+
+ /* We may not get quite the right frequency if using
+ * approximate clocks so look for the closest match that is
+ * higher than the target (we need to ensure that there enough
+ * BCLKs to clock out the samples).
+ */
+ bclk_div = 0;
+ best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk;
+ i = 1;
+ while (i < ARRAY_SIZE(bclk_divs)) {
+ cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk;
+ if (cur_val < 0) /* BCLK table is sorted */
+ break;
+ bclk_div = i;
+ best_val = cur_val;
+ i++;
+ }
+
+ aif2 &= ~WM8903_BCLK_DIV_MASK;
+ aif3 &= ~WM8903_LRCLK_RATE_MASK;
+
+ dev_dbg(&i2c->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n",
+ bclk_divs[bclk_div].ratio / 10, bclk,
+ (clk_sys * 10) / bclk_divs[bclk_div].ratio);
+
+ aif2 |= bclk_divs[bclk_div].div;
+ aif3 |= bclk / fs;
+
+ wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0);
+ wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
+ wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
+
+ return 0;
+}
+
+#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8903_dai = {
+ .name = "WM8903",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_PLAYBACK_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8903_CAPTURE_RATES,
+ .formats = WM8903_FORMATS,
+ },
+ .ops = {
+ .startup = wm8903_startup,
+ .shutdown = wm8903_shutdown,
+ .hw_params = wm8903_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8903_digital_mute,
+ .set_fmt = wm8903_set_dai_fmt,
+ .set_sysclk = wm8903_set_dai_sysclk
+ }
+};
+EXPORT_SYMBOL_GPL(wm8903_dai);
+
+static int wm8903_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;
+
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8903_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c = codec->control_data;
+ int i;
+ u16 *reg_cache = codec->reg_cache;
+ u16 *tmp_cache = kmemdup(codec->reg_cache, sizeof(wm8903_reg_defaults),
+ GFP_KERNEL);
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm8903_set_bias_level(codec, codec->suspend_bias_level);
+
+ /* Sync back everything else */
+ if (tmp_cache) {
+ for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+ if (tmp_cache[i] != reg_cache[i])
+ wm8903_write(codec, i, tmp_cache[i]);
+ } else {
+ dev_err(&i2c->dev, "Failed to allocate temporary cache\n");
+ }
+
+ return 0;
+}
+
+/*
+ * initialise the WM8903 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8903_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct i2c_client *i2c = codec->control_data;
+ int ret = 0;
+ u16 val;
+
+ val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID);
+ if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) {
+ dev_err(&i2c->dev,
+ "Device with ID register %x is not a WM8903\n", val);
+ return -ENODEV;
+ }
+
+ codec->name = "WM8903";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8903_read;
+ codec->write = wm8903_write;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = wm8903_set_bias_level;
+ codec->dai = &wm8903_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults);
+ codec->reg_cache = kmemdup(wm8903_reg_defaults,
+ sizeof(wm8903_reg_defaults),
+ GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ dev_err(&i2c->dev, "Failed to allocate register cache\n");
+ return -ENOMEM;
+ }
+
+ val = wm8903_read(codec, WM8903_REVISION_NUMBER);
+ dev_info(&i2c->dev, "WM8903 revision %d\n",
+ val & WM8903_CHIP_REV_MASK);
+
+ wm8903_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* SYSCLK is required for pretty much anything */
+ wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA);
+
+ /* power on device */
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Latch volume update bits */
+ val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_ADCVU;
+ wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
+ wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
+ val |= WM8903_DACVU;
+ wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
+ wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
+ val |= WM8903_HPOUTVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
+ val |= WM8903_LINEOUTVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
+
+ val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
+ val |= WM8903_SPKVU;
+ wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
+ wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
+
+ /* Enable DAC soft mute by default */
+ val = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
+ val |= WM8903_DAC_MUTEMODE;
+ wm8903_write(codec, WM8903_DAC_DIGITAL_1, val);
+
+ wm8903_add_controls(codec);
+ wm8903_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ dev_err(&i2c->dev, "wm8903: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+static struct snd_soc_device *wm8903_socdev;
+
+static int wm8903_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8903_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8903_init(socdev);
+ if (ret < 0)
+ dev_err(&i2c->dev, "Device initialisation failed\n");
+
+ return ret;
+}
+
+static int wm8903_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+/* i2c codec control layer */
+static const struct i2c_device_id wm8903_i2c_id[] = {
+ { "wm8903", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id);
+
+static struct i2c_driver wm8903_i2c_driver = {
+ .driver = {
+ .name = "WM8903",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8903_i2c_probe,
+ .remove = wm8903_i2c_remove,
+ .id_table = wm8903_i2c_id,
+};
+
+static int wm8903_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8903_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8903_priv *wm8903;
+ struct i2c_board_info board_info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *i2c_client;
+ int ret = 0;
+
+ setup = socdev->codec_data;
+
+ if (!setup->i2c_address) {
+ dev_err(&pdev->dev, "No codec address provided\n");
+ return -ENODEV;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL);
+ if (wm8903 == NULL) {
+ ret = -ENOMEM;
+ goto err_codec;
+ }
+
+ codec->private_data = wm8903;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ wm8903_socdev = socdev;
+
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = i2c_add_driver(&wm8903_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ goto err_priv;
+ } else {
+ memset(&board_info, 0, sizeof(board_info));
+ strlcpy(board_info.type, "wm8903", I2C_NAME_SIZE);
+ board_info.addr = setup->i2c_address;
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "Can't get I2C bus %d\n",
+ setup->i2c_bus);
+ ret = -ENODEV;
+ goto err_adapter;
+ }
+
+ i2c_client = i2c_new_device(adapter, &board_info);
+ i2c_put_adapter(adapter);
+ if (i2c_client == NULL) {
+ dev_err(&pdev->dev,
+ "I2C driver registration failed\n");
+ ret = -ENODEV;
+ goto err_adapter;
+ }
+ }
+
+ return ret;
+
+err_adapter:
+ i2c_del_driver(&wm8903_i2c_driver);
+err_priv:
+ kfree(codec->private_data);
+err_codec:
+ kfree(codec);
+ return ret;
+}
+
+/* power down chip */
+static int wm8903_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)
+ wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ i2c_unregister_device(socdev->codec->control_data);
+ i2c_del_driver(&wm8903_i2c_driver);
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8903 = {
+ .probe = wm8903_probe,
+ .remove = wm8903_remove,
+ .suspend = wm8903_suspend,
+ .resume = wm8903_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903);
+
+MODULE_DESCRIPTION("ASoC WM8903 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h
new file mode 100644
index 0000000..cec622f
--- /dev/null
+++ b/sound/soc/codecs/wm8903.h
@@ -0,0 +1,1463 @@
+/*
+ * wm8903.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.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 _WM8903_H
+#define _WM8903_H
+
+#include <linux/i2c.h>
+
+extern struct snd_soc_dai wm8903_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8903;
+
+struct wm8903_setup_data {
+ int i2c_bus;
+ int i2c_address;
+};
+
+#define WM8903_MCLK_DIV_2 1
+#define WM8903_CLK_SYS 2
+#define WM8903_BCLK 3
+#define WM8903_LRCLK 4
+
+/*
+ * Register values.
+ */
+#define WM8903_SW_RESET_AND_ID 0x00
+#define WM8903_REVISION_NUMBER 0x01
+#define WM8903_BIAS_CONTROL_0 0x04
+#define WM8903_VMID_CONTROL_0 0x05
+#define WM8903_MIC_BIAS_CONTROL_0 0x06
+#define WM8903_ANALOGUE_DAC_0 0x08
+#define WM8903_ANALOGUE_ADC_0 0x0A
+#define WM8903_POWER_MANAGEMENT_0 0x0C
+#define WM8903_POWER_MANAGEMENT_1 0x0D
+#define WM8903_POWER_MANAGEMENT_2 0x0E
+#define WM8903_POWER_MANAGEMENT_3 0x0F
+#define WM8903_POWER_MANAGEMENT_4 0x10
+#define WM8903_POWER_MANAGEMENT_5 0x11
+#define WM8903_POWER_MANAGEMENT_6 0x12
+#define WM8903_CLOCK_RATES_0 0x14
+#define WM8903_CLOCK_RATES_1 0x15
+#define WM8903_CLOCK_RATES_2 0x16
+#define WM8903_AUDIO_INTERFACE_0 0x18
+#define WM8903_AUDIO_INTERFACE_1 0x19
+#define WM8903_AUDIO_INTERFACE_2 0x1A
+#define WM8903_AUDIO_INTERFACE_3 0x1B
+#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E
+#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F
+#define WM8903_DAC_DIGITAL_0 0x20
+#define WM8903_DAC_DIGITAL_1 0x21
+#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24
+#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25
+#define WM8903_ADC_DIGITAL_0 0x26
+#define WM8903_DIGITAL_MICROPHONE_0 0x27
+#define WM8903_DRC_0 0x28
+#define WM8903_DRC_1 0x29
+#define WM8903_DRC_2 0x2A
+#define WM8903_DRC_3 0x2B
+#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C
+#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D
+#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E
+#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F
+#define WM8903_ANALOGUE_LEFT_MIX_0 0x32
+#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37
+#define WM8903_ANALOGUE_OUT1_LEFT 0x39
+#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A
+#define WM8903_ANALOGUE_OUT2_LEFT 0x3B
+#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C
+#define WM8903_ANALOGUE_OUT3_LEFT 0x3E
+#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F
+#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41
+#define WM8903_DC_SERVO_0 0x43
+#define WM8903_DC_SERVO_2 0x45
+#define WM8903_ANALOGUE_HP_0 0x5A
+#define WM8903_ANALOGUE_LINEOUT_0 0x5E
+#define WM8903_CHARGE_PUMP_0 0x62
+#define WM8903_CLASS_W_0 0x68
+#define WM8903_WRITE_SEQUENCER_0 0x6C
+#define WM8903_WRITE_SEQUENCER_1 0x6D
+#define WM8903_WRITE_SEQUENCER_2 0x6E
+#define WM8903_WRITE_SEQUENCER_3 0x6F
+#define WM8903_WRITE_SEQUENCER_4 0x70
+#define WM8903_CONTROL_INTERFACE 0x72
+#define WM8903_GPIO_CONTROL_1 0x74
+#define WM8903_GPIO_CONTROL_2 0x75
+#define WM8903_GPIO_CONTROL_3 0x76
+#define WM8903_GPIO_CONTROL_4 0x77
+#define WM8903_GPIO_CONTROL_5 0x78
+#define WM8903_INTERRUPT_STATUS_1 0x79
+#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A
+#define WM8903_INTERRUPT_POLARITY_1 0x7B
+#define WM8903_INTERRUPT_CONTROL 0x7E
+#define WM8903_CONTROL_INTERFACE_TEST_1 0x81
+#define WM8903_CHARGE_PUMP_TEST_1 0x95
+#define WM8903_CLOCK_RATE_TEST_4 0xA4
+#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC
+
+#define WM8903_REGISTER_COUNT 75
+#define WM8903_MAX_REGISTER 0xAC
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision Number
+ */
+#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8903_POBCTRL 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */
+#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */
+#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */
+#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */
+#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */
+#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */
+#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */
+#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */
+#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+
+#define WM8903_VMID_RES_50K 2
+#define WM8903_VMID_RES_250K 3
+#define WM8903_VMID_RES_5K 4
+
+/*
+ * R6 (0x06) - Mic Bias Control 0
+ */
+#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */
+#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */
+#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */
+#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */
+#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */
+#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */
+#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */
+#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */
+#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */
+#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */
+#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8903_INL_ENA 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */
+#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */
+#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8903_INR_ENA 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */
+#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */
+#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */
+
+/*
+ * R13 (0x0D) - Power Management 1
+ */
+#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */
+#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */
+#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */
+
+/*
+ * R16 (0x10) - Power Management 4
+ */
+#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */
+#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */
+
+/*
+ * R17 (0x11) - Power Management 5
+ */
+#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */
+#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
+#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */
+#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */
+#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */
+#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */
+#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */
+#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */
+#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8903_TO_ENA 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */
+#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */
+#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */
+#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */
+#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */
+#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */
+#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */
+#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */
+#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */
+#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */
+#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8903_DACVU 0x0100 /* DACVU */
+#define WM8903_DACVU_MASK 0x0100 /* DACVU */
+#define WM8903_DACVU_SHIFT 8 /* DACVU */
+#define WM8903_DACVU_WIDTH 1 /* DACVU */
+#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */
+#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */
+#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8903_ADCVU 0x0100 /* ADCVU */
+#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */
+#define WM8903_ADCVU_SHIFT 8 /* ADCVU */
+#define WM8903_ADCVU_WIDTH 1 /* ADCVU */
+#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */
+#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */
+#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8903_LINMUTE 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8903_RINMUTE 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */
+#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */
+#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */
+#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */
+#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */
+
+/*
+ * R50 (0x32) - Analogue Left Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */
+
+/*
+ * R51 (0x33) - Analogue Right Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */
+
+/*
+ * R52 (0x34) - Analogue Spk Mix Left 0
+ */
+#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */
+
+/*
+ * R53 (0x35) - Analogue Spk Mix Left 1
+ */
+#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */
+
+/*
+ * R54 (0x36) - Analogue Spk Mix Right 0
+ */
+#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */
+
+/*
+ * R55 (0x37) - Analogue Spk Mix Right 1
+ */
+#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */
+#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */
+#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */
+#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */
+#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */
+#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */
+#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */
+#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */
+#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */
+#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */
+#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */
+#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R62 (0x3E) - Analogue OUT3 Left
+ */
+#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKLZC 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */
+#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */
+#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */
+#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */
+
+/*
+ * R63 (0x3F) - Analogue OUT3 Right
+ */
+#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */
+#define WM8903_SPKVU 0x0080 /* SPKVU */
+#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */
+#define WM8903_SPKVU_SHIFT 7 /* SPKVU */
+#define WM8903_SPKVU_WIDTH 1 /* SPKVU */
+#define WM8903_SPKRZC 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */
+#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */
+#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */
+#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */
+
+/*
+ * R65 (0x41) - Analogue SPK Output Control 0
+ */
+#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */
+#define WM8903_VROI 0x0001 /* VROI */
+#define WM8903_VROI_MASK 0x0001 /* VROI */
+#define WM8903_VROI_SHIFT 0 /* VROI */
+#define WM8903_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */
+#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */
+#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8903_CP_ENA 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */
+#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R114 (0x72) - Control Interface
+ */
+#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */
+
+/*
+ * R116 (0x74) - GPIO Control 1
+ */
+#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */
+#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */
+#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */
+#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */
+#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */
+#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */
+#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */
+#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */
+#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */
+#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */
+#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */
+#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */
+#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */
+#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */
+#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */
+#define WM8903_GP1_PD 0x0008 /* GP1_PD */
+#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */
+#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */
+#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */
+#define WM8903_GP1_PU 0x0004 /* GP1_PU */
+#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */
+#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */
+#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */
+#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */
+#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */
+#define WM8903_GP1_DB 0x0001 /* GP1_DB */
+#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */
+#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */
+#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */
+
+/*
+ * R117 (0x75) - GPIO Control 2
+ */
+#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */
+#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */
+#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */
+#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */
+#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */
+#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */
+#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */
+#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */
+#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */
+#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */
+#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */
+#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */
+#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */
+#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */
+#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */
+#define WM8903_GP2_PD 0x0008 /* GP2_PD */
+#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */
+#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */
+#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */
+#define WM8903_GP2_PU 0x0004 /* GP2_PU */
+#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */
+#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */
+#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */
+#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */
+#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */
+#define WM8903_GP2_DB 0x0001 /* GP2_DB */
+#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */
+#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */
+#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */
+
+/*
+ * R118 (0x76) - GPIO Control 3
+ */
+#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */
+#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */
+#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */
+#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */
+#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */
+#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */
+#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */
+#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */
+#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */
+#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */
+#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */
+#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */
+#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */
+#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */
+#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */
+#define WM8903_GP3_PD 0x0008 /* GP3_PD */
+#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */
+#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */
+#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */
+#define WM8903_GP3_PU 0x0004 /* GP3_PU */
+#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */
+#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */
+#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */
+#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */
+#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */
+#define WM8903_GP3_DB 0x0001 /* GP3_DB */
+#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */
+#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */
+#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */
+
+/*
+ * R119 (0x77) - GPIO Control 4
+ */
+#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */
+#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */
+#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */
+#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */
+#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */
+#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */
+#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */
+#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */
+#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */
+#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */
+#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */
+#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */
+#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */
+#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */
+#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */
+#define WM8903_GP4_PD 0x0008 /* GP4_PD */
+#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */
+#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */
+#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */
+#define WM8903_GP4_PU 0x0004 /* GP4_PU */
+#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */
+#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */
+#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */
+#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */
+#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */
+#define WM8903_GP4_DB 0x0001 /* GP4_DB */
+#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */
+#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */
+#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */
+
+/*
+ * R120 (0x78) - GPIO Control 5
+ */
+#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */
+#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */
+#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */
+#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */
+#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */
+#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */
+#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */
+#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */
+#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */
+#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */
+#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */
+#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */
+#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */
+#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */
+#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */
+#define WM8903_GP5_PD 0x0008 /* GP5_PD */
+#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */
+#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */
+#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */
+#define WM8903_GP5_PU 0x0004 /* GP5_PU */
+#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */
+#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */
+#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */
+#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */
+#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */
+#define WM8903_GP5_DB 0x0001 /* GP5_DB */
+#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */
+#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */
+#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */
+
+/*
+ * R121 (0x79) - Interrupt Status 1
+ */
+#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */
+#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */
+#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */
+#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */
+#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */
+#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */
+#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */
+#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */
+#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */
+#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */
+#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */
+#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */
+#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */
+#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */
+#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */
+#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */
+#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */
+#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */
+#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */
+
+/*
+ * R122 (0x7A) - Interrupt Status 1 Mask
+ */
+#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */
+#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */
+#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */
+#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */
+#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */
+
+/*
+ * R123 (0x7B) - Interrupt Polarity 1
+ */
+#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */
+#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */
+#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */
+#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */
+
+/*
+ * R126 (0x7E) - Interrupt Control
+ */
+#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */
+#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */
+#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */
+
+/*
+ * R129 (0x81) - Control Interface Test 1
+ */
+#define WM8903_USER_KEY 0x0002 /* USER_KEY */
+#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */
+#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */
+#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */
+#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */
+#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */
+#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */
+#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */
+
+/*
+ * R149 (0x95) - Charge Pump Test 1
+ */
+#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */
+
+/*
+ * R164 (0xA4) - Clock Rate Test 4
+ */
+#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */
+
+/*
+ * R172 (0xAC) - Analogue Output Bias 0
+ */
+#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */
+
+#endif
diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c
new file mode 100644
index 0000000..f41a578
--- /dev/null
+++ b/sound/soc/codecs/wm8971.c
@@ -0,0 +1,941 @@
+/*
+ * wm8971.c -- WM8971 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.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 "wm8971.h"
+
+#define WM8971_VERSION "0.9"
+
+#define WM8971_REG_COUNT 43
+
+static struct workqueue_struct *wm8971_workq = NULL;
+
+/* codec private data */
+struct wm8971_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * wm8971 register cache
+ * We can't read the WM8971 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8971_reg[] = {
+ 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */
+ 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */
+ 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */
+ 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */
+ 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */
+ 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */
+ 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */
+ 0x0079, 0x0079, 0x0079, /* 40 */
+};
+
+static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < WM8971_REG_COUNT)
+ return cache[reg];
+
+ return -1;
+}
+
+static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < WM8971_REG_COUNT)
+ cache[reg] = value;
+}
+
+static int wm8971_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;
+
+ wm8971_write_reg_cache (codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0)
+
+/* WM8971 Controls */
+static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" };
+static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz",
+ "200Hz @ 48kHz" };
+static const char *wm8971_treble[] = { "8kHz", "4kHz" };
+static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" };
+static const char *wm8971_ng_type[] = { "Constant PGA Gain",
+ "Mute ADC Output" };
+static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)",
+ "Mono (Right)", "Digital Mono"};
+static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" };
+static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA",
+ "Differential"};
+static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"};
+static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"};
+static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert",
+ "L + R Invert"};
+
+static const struct soc_enum wm8971_enum[] = {
+ SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */
+ SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter),
+ SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble),
+ SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func),
+ SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux),
+ SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase),
+ SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */
+ SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux),
+ SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel),
+ SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel),
+ SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */
+ SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux),
+};
+
+static const struct snd_kcontrol_new wm8971_snd_controls[] = {
+ SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0),
+ SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL,
+ 6, 1, 0),
+ SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1),
+
+ SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V,
+ WM8971_ROUT1V, 7, 1, 0),
+ SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V,
+ WM8971_ROUT2V, 7, 1, 0),
+ SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0),
+
+ SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0),
+
+ SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1,
+ WM8971_LOUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1,
+ WM8971_ROUTM2, 4, 7, 1),
+ SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1,
+ WM8971_MOUTM2, 4, 7, 1),
+
+ SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V,
+ WM8971_ROUT1V, 0, 127, 0),
+ SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V,
+ WM8971_ROUT2V, 0, 127, 0),
+
+ SOC_ENUM("Bass Boost", wm8971_enum[0]),
+ SOC_ENUM("Bass Filter", wm8971_enum[1]),
+ SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1),
+
+ SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0),
+ SOC_ENUM("Treble Cut-off", wm8971_enum[2]),
+
+ SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1),
+
+ SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0),
+
+ SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0),
+ SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0),
+ SOC_ENUM("ALC Capture Function", wm8971_enum[3]),
+ SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0),
+ SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0),
+ SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0),
+ SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0),
+ SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0),
+ SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]),
+ SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0),
+
+ SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0),
+ SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0),
+
+ SOC_ENUM("Playback De-emphasis", wm8971_enum[5]),
+ SOC_ENUM("Playback Function", wm8971_enum[6]),
+ SOC_ENUM("Playback Phase", wm8971_enum[7]),
+
+ SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0),
+};
+
+/* add non-DAPM controls */
+static int wm8971_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8971_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8971_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8971_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8971_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8971_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[11]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8971_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[13]);
+
+static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_mixer_controls[0],
+ ARRAY_SIZE(wm8971_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_mixer_controls[0],
+ ARRAY_SIZE(wm8971_right_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0,
+ &wm8971_mono_mixer_controls[0],
+ ARRAY_SIZE(wm8971_mono_mixer_controls)),
+
+ SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0),
+ SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0),
+
+ SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0,
+ &wm8971_left_pga_controls),
+ SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0,
+ &wm8971_right_pga_controls),
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_left_line_controls),
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_right_line_controls),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &wm8971_monomux_controls),
+
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("MONO"),
+
+ SND_SOC_DAPM_INPUT("LINPUT1"),
+ SND_SOC_DAPM_INPUT("RINPUT1"),
+ SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left mixer */
+ {"Left Mixer", "Playback Switch", "Left DAC"},
+ {"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Left Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* right mixer */
+ {"Right Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Right Mixer", "Playback Switch", "Right DAC"},
+ {"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* left out 1 */
+ {"Left Out 1", NULL, "Left Mixer"},
+ {"LOUT1", NULL, "Left Out 1"},
+
+ /* left out 2 */
+ {"Left Out 2", NULL, "Left Mixer"},
+ {"LOUT2", NULL, "Left Out 2"},
+
+ /* right out 1 */
+ {"Right Out 1", NULL, "Right Mixer"},
+ {"ROUT1", NULL, "Right Out 1"},
+
+ /* right out 2 */
+ {"Right Out 2", NULL, "Right Mixer"},
+ {"ROUT2", NULL, "Right Out 2"},
+
+ /* mono mixer */
+ {"Mono Mixer", "Left Playback Switch", "Left DAC"},
+ {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+ {"Mono Mixer", "Right Playback Switch", "Right DAC"},
+ {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+ /* mono out */
+ {"Mono Out", NULL, "Mono Mixer"},
+ {"MONO1", NULL, "Mono Out"},
+
+ /* Left Line Mux */
+ {"Left Line Mux", "Line", "LINPUT1"},
+ {"Left Line Mux", "PGA", "Left PGA Mux"},
+ {"Left Line Mux", "Differential", "Differential Mux"},
+
+ /* Right Line Mux */
+ {"Right Line Mux", "Line", "RINPUT1"},
+ {"Right Line Mux", "Mic", "MIC"},
+ {"Right Line Mux", "PGA", "Right PGA Mux"},
+ {"Right Line Mux", "Differential", "Differential Mux"},
+
+ /* Left PGA Mux */
+ {"Left PGA Mux", "Line", "LINPUT1"},
+ {"Left PGA Mux", "Differential", "Differential Mux"},
+
+ /* Right PGA Mux */
+ {"Right PGA Mux", "Line", "RINPUT1"},
+ {"Right PGA Mux", "Differential", "Differential Mux"},
+
+ /* Differential Mux */
+ {"Differential Mux", "Line", "LINPUT1"},
+ {"Differential Mux", "Line", "RINPUT1"},
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Stereo", "Left PGA Mux"},
+ {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+ {"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Stereo", "Right PGA Mux"},
+ {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+ {"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8971_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets,
+ ARRAY_SIZE(wm8971_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+struct _coeff_div {
+ u32 mclk;
+ u32 rate;
+ u16 fs;
+ u8 sr:5;
+ u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /* 8k */
+ {12288000, 8000, 1536, 0x6, 0x0},
+ {11289600, 8000, 1408, 0x16, 0x0},
+ {18432000, 8000, 2304, 0x7, 0x0},
+ {16934400, 8000, 2112, 0x17, 0x0},
+ {12000000, 8000, 1500, 0x6, 0x1},
+
+ /* 11.025k */
+ {11289600, 11025, 1024, 0x18, 0x0},
+ {16934400, 11025, 1536, 0x19, 0x0},
+ {12000000, 11025, 1088, 0x19, 0x1},
+
+ /* 16k */
+ {12288000, 16000, 768, 0xa, 0x0},
+ {18432000, 16000, 1152, 0xb, 0x0},
+ {12000000, 16000, 750, 0xa, 0x1},
+
+ /* 22.05k */
+ {11289600, 22050, 512, 0x1a, 0x0},
+ {16934400, 22050, 768, 0x1b, 0x0},
+ {12000000, 22050, 544, 0x1b, 0x1},
+
+ /* 32k */
+ {12288000, 32000, 384, 0xc, 0x0},
+ {18432000, 32000, 576, 0xd, 0x0},
+ {12000000, 32000, 375, 0xa, 0x1},
+
+ /* 44.1k */
+ {11289600, 44100, 256, 0x10, 0x0},
+ {16934400, 44100, 384, 0x11, 0x0},
+ {12000000, 44100, 272, 0x11, 0x1},
+
+ /* 48k */
+ {12288000, 48000, 256, 0x0, 0x0},
+ {18432000, 48000, 384, 0x1, 0x0},
+ {12000000, 48000, 250, 0x0, 0x1},
+
+ /* 88.2k */
+ {11289600, 88200, 128, 0x1e, 0x0},
+ {16934400, 88200, 192, 0x1f, 0x0},
+ {12000000, 88200, 136, 0x1f, 0x1},
+
+ /* 96k */
+ {12288000, 96000, 128, 0xe, 0x0},
+ {18432000, 96000, 192, 0xf, 0x0},
+ {12000000, 96000, 125, 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;
+}
+
+static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8971_priv *wm8971 = codec->private_data;
+
+ switch (freq) {
+ case 11289600:
+ case 12000000:
+ case 12288000:
+ case 16934400:
+ case 18432000:
+ wm8971->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8971_write(codec, WM8971_IFACE, iface);
+ return 0;
+}
+
+static int wm8971_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 wm8971_priv *wm8971 = codec->private_data;
+ u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3;
+ u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0;
+ int coeff = get_coeff(wm8971->sysclk, params_rate(params));
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x000c;
+ break;
+ }
+
+ /* set iface & srate */
+ wm8971_write(codec, WM8971_IFACE, iface);
+ if (coeff >= 0)
+ wm8971_write(codec, WM8971_SRATE, srate |
+ (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+ return 0;
+}
+
+static int wm8971_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7;
+
+ if (mute)
+ wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
+ else
+ wm8971_write(codec, WM8971_ADCDAC, mute_reg);
+ return 0;
+}
+
+static int wm8971_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* set vmid to 50k and unmute dac */
+ wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* mute dac and set vmid to 500k, enable VREF */
+ wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
+ break;
+ case SND_SOC_BIAS_OFF:
+ wm8971_write(codec, WM8971_PWR1, 0x0001);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8971_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 WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai wm8971_dai = {
+ .name = "WM8971",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8971_RATES,
+ .formats = WM8971_FORMATS,},
+ .ops = {
+ .hw_params = wm8971_pcm_hw_params,
+ },
+ .dai_ops = {
+ .digital_mute = wm8971_mute,
+ .set_fmt = wm8971_set_dai_fmt,
+ .set_sysclk = wm8971_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8971_dai);
+
+static void wm8971_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ wm8971_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8971_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;
+
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8971_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;
+ u16 reg;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) {
+ if (i + 1 == WM8971_RESET)
+ continue;
+ data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge wm8971 caps */
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+ reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+ wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->bias_level = SND_SOC_BIAS_ON;
+ queue_delayed_work(wm8971_workq, &codec->delayed_work,
+ msecs_to_jiffies(1000));
+ }
+
+ return 0;
+}
+
+static int wm8971_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg, ret = 0;
+
+ codec->name = "WM8971";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8971_read_reg_cache;
+ codec->write = wm8971_write;
+ codec->set_bias_level = wm8971_set_bias_level;
+ codec->dai = &wm8971_dai;
+ codec->reg_cache_size = ARRAY_SIZE(wm8971_reg);
+ codec->num_dai = 1;
+ codec->reg_cache = kmemdup(wm8971_reg, sizeof(wm8971_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8971_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8971: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps - set vmid to 5k for quick power up */
+ reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
+ wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ queue_delayed_work(wm8971_workq, &codec->delayed_work,
+ msecs_to_jiffies(1000));
+
+ /* set the update bits */
+ reg = wm8971_read_reg_cache(codec, WM8971_LDAC);
+ wm8971_write(codec, WM8971_LDAC, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_RDAC);
+ wm8971_write(codec, WM8971_RDAC, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V);
+ wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V);
+ wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V);
+ wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V);
+ wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100);
+
+ reg = wm8971_read_reg_cache(codec, WM8971_LINVOL);
+ wm8971_write(codec, WM8971_LINVOL, reg | 0x0100);
+ reg = wm8971_read_reg_cache(codec, WM8971_RINVOL);
+ wm8971_write(codec, WM8971_RINVOL, reg | 0x0100);
+
+ wm8971_add_controls(codec);
+ wm8971_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8971: 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 *wm8971_socdev;
+
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+
+static int wm8971_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8971_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+
+ codec->control_data = i2c;
+
+ ret = wm8971_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8971\n");
+
+ return ret;
+}
+
+static int wm8971_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8971_i2c_id[] = {
+ { "wm8971", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id);
+
+static struct i2c_driver wm8971_i2c_driver = {
+ .driver = {
+ .name = "WM8971 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8971_i2c_probe,
+ .remove = wm8971_i2c_remove,
+ .id_table = wm8971_i2c_id,
+};
+
+static int wm8971_add_i2c_device(struct platform_device *pdev,
+ const struct wm8971_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8971_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8971", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8971_i2c_driver);
+ return -ENODEV;
+}
+
+#endif
+
+static int wm8971_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8971_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8971_priv *wm8971;
+ int ret = 0;
+
+ pr_info("WM8971 Audio Codec %s", WM8971_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL);
+ if (wm8971 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8971;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8971_socdev = socdev;
+
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work);
+ wm8971_workq = create_workqueue("wm8971");
+ if (wm8971_workq == NULL) {
+ kfree(codec->private_data);
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8971_add_i2c_device(pdev, setup);
+ }
+#endif
+ /* Add other interfaces here */
+
+ if (ret != 0) {
+ destroy_workqueue(wm8971_workq);
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+
+ return ret;
+}
+
+/* power down chip */
+static int wm8971_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)
+ wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ if (wm8971_workq)
+ destroy_workqueue(wm8971_workq);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8971_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8971 = {
+ .probe = wm8971_probe,
+ .remove = wm8971_remove,
+ .suspend = wm8971_suspend,
+ .resume = wm8971_resume,
+};
+
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971);
+
+MODULE_DESCRIPTION("ASoC WM8971 driver");
+MODULE_AUTHOR("Lab126");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h
new file mode 100644
index 0000000..ef4f08f
--- /dev/null
+++ b/sound/soc/codecs/wm8971.h
@@ -0,0 +1,64 @@
+/*
+ * wm8971.h -- audio driver for WM8971
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.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 _WM8971_H
+#define _WM8971_H
+
+#define WM8971_LINVOL 0x00
+#define WM8971_RINVOL 0x01
+#define WM8971_LOUT1V 0x02
+#define WM8971_ROUT1V 0x03
+#define WM8971_ADCDAC 0x05
+#define WM8971_IFACE 0x07
+#define WM8971_SRATE 0x08
+#define WM8971_LDAC 0x0a
+#define WM8971_RDAC 0x0b
+#define WM8971_BASS 0x0c
+#define WM8971_TREBLE 0x0d
+#define WM8971_RESET 0x0f
+#define WM8971_ALC1 0x11
+#define WM8971_ALC2 0x12
+#define WM8971_ALC3 0x13
+#define WM8971_NGATE 0x14
+#define WM8971_LADC 0x15
+#define WM8971_RADC 0x16
+#define WM8971_ADCTL1 0x17
+#define WM8971_ADCTL2 0x18
+#define WM8971_PWR1 0x19
+#define WM8971_PWR2 0x1a
+#define WM8971_ADCTL3 0x1b
+#define WM8971_ADCIN 0x1f
+#define WM8971_LADCIN 0x20
+#define WM8971_RADCIN 0x21
+#define WM8971_LOUTM1 0x22
+#define WM8971_LOUTM2 0x23
+#define WM8971_ROUTM1 0x24
+#define WM8971_ROUTM2 0x25
+#define WM8971_MOUTM1 0x26
+#define WM8971_MOUTM2 0x27
+#define WM8971_LOUT2V 0x28
+#define WM8971_ROUT2V 0x29
+#define WM8971_MOUTV 0x2A
+
+#define WM8971_SYSCLK 0
+
+struct wm8971_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai wm8971_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8971;
+
+#endif
diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c
new file mode 100644
index 0000000..572d22b
--- /dev/null
+++ b/sound/soc/codecs/wm8990.c
@@ -0,0 +1,1635 @@
+/*
+ * wm8990.c -- WM8990 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * lg@opensource.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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.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/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 <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8990.h"
+
+#define WM8990_VERSION "0.2"
+
+/* codec private data */
+struct wm8990_priv {
+ unsigned int sysclk;
+ unsigned int pcmclk;
+};
+
+/*
+ * wm8990 register cache. Note that register 0 is not included in the
+ * cache.
+ */
+static const u16 wm8990_reg[] = {
+ 0x8990, /* R0 - Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking (1) */
+ 0x0000, /* R7 - Clocking (2) */
+ 0x0040, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0100, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x1000, /* R19 - GPIO1 & GPIO2 */
+ 0x1010, /* R20 - GPIO3 & GPIO4 */
+ 0x1010, /* R21 - GPIO5 & GPIO6 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x0000, /* R28 - Left Output Volume */
+ 0x0000, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0022, /* R31 - Out3/4 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - Speaker Volume */
+ 0x0003, /* R35 - ClassD1 */
+ 0x0000, /* R36 */
+ 0x0100, /* R37 - ClassD3 */
+ 0x0079, /* R38 - ClassD4 */
+ 0x0000, /* R39 - Input Mixer1 */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0180, /* R51 - Out3/4 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0008, /* R60 - PLL1 */
+ 0x0031, /* R61 - PLL2 */
+ 0x0026, /* R62 - PLL3 */
+};
+
+/*
+ * read wm8990 register cache
+ */
+static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1);
+ return cache[reg];
+}
+
+/*
+ * write wm8990 register cache
+ */
+static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1);
+
+ /* Reset register is uncached */
+ if (reg == 0)
+ return;
+
+ cache[reg] = value;
+}
+
+/*
+ * write to the wm8990 register space
+ */
+static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[3];
+
+ data[0] = reg & 0xFF;
+ data[1] = (value >> 8) & 0xFF;
+ data[2] = value & 0xFF;
+
+ wm8990_write_reg_cache(codec, reg, value);
+
+ if (codec->hw_write(codec->control_data, data, 3) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0)
+
+static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600);
+
+static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000);
+
+static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, 0, -2100);
+
+static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600);
+
+static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0);
+
+static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0);
+
+static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763);
+
+static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0);
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8990_read_reg_cache(codec, reg);
+ return wm8990_write(codec, reg, val | 0x0100);
+}
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+ tlv_array) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8990_digital_sidetone[] =
+ {"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8990_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACL_SHIFT,
+ WM8990_ADC_TO_DACL_MASK,
+ wm8990_digital_sidetone);
+
+static const struct soc_enum wm8990_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADC_TO_DACR_SHIFT,
+ WM8990_ADC_TO_DACR_MASK,
+ wm8990_digital_sidetone);
+
+static const char *wm8990_adcmode[] =
+ {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8990_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_CUT_SHIFT,
+ WM8990_ADC_HPF_CUT_MASK,
+ wm8990_adcmode);
+
+static const struct snd_kcontrol_new wm8990_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+ WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+ WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+ WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+ WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv),
+
+/* LOUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME,
+ WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0),
+
+/* ROUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME,
+ WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0),
+
+/* LOPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME,
+ WM8990_LOPGAZC_BIT, 1, 0),
+
+/* ROPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME,
+ WM8990_ROPGAZC_BIT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LONMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOPMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_LOATTN_BIT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_RONMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROPMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+ WM8990_ROATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT3ATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+ WM8990_OUT4ATTN_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1,
+ WM8990_CDMODE_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME,
+ WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3,
+ WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3,
+ WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4,
+ WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4,
+ WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8990_LEFT_DAC_DIGITAL_VOLUME,
+ WM8990_DACL_VOL_SHIFT,
+ WM8990_DACL_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8990_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8990_DACR_VOL_SHIFT,
+ WM8990_DACR_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+ WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL,
+ WM8990_ADC_HPF_ENA_BIT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8990_LEFT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCL_VOL_SHIFT,
+ WM8990_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8990_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8990_ADCR_VOL_SHIFT,
+ WM8990_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LIN12VOL_SHIFT,
+ WM8990_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8990_LI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LIN34VOL_SHIFT,
+ WM8990_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8990_LI34MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RIN12VOL_SHIFT,
+ WM8990_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8990_RI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RIN34VOL_SHIFT,
+ WM8990_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8990_RI34MUTE_BIT, 1, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8990_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8990_snd_controls[i], codec,
+ NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2);
+ fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) |
+ (1 << WM8990_AINLMUX_PWR_BIT))) {
+ reg |= WM8990_AINL_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+
+ if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) |
+ (1 << WM8990_AINRMUX_PWR_BIT))) {
+ reg |= WM8990_AINR_ENA;
+ } else {
+ reg &= ~WM8990_AINL_ENA;
+ }
+ wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u32 reg_shift = kcontrol->private_value & 0xfff;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) :
+ reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1);
+ if (reg & WM8990_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2);
+ if (reg & WM8990_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8):
+ reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
+ if (reg & WM8990_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3,
+ WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4,
+ WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+ 1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8990_ainlmux[] =
+ {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8990_ainlmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8990_ainrmux[] =
+ {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8990_ainrmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT,
+ WM8990_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT,
+ WM8990_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LRI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LLI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LR12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LL12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1,
+ WM8990_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RLI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RRI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RL12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RR12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2,
+ WM8990_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LROPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1,
+ WM8990_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LR12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1,
+ WM8990_LL12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+ WM8990_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RLOPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2,
+ WM8990_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2,
+ WM8990_RR12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+ WM8990_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LI4O3_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RPGAO4_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+ WM8990_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LI2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LB2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LOPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_LDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+ WM8990_ROPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+ WM8990_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCL_ENA_BIT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2,
+ WM8990_ADCR_ENA_BIT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT,
+ 0, &wm8990_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT,
+ 0, &wm8990_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT,
+ 0, &wm8990_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT,
+ 0, &wm8990_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0,
+ &wm8990_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0,
+ &wm8990_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8990_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0,
+ &wm8990_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACL_ENA_BIT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3,
+ WM8990_DACR_ENA_BIT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT,
+ 0, &wm8990_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0,
+ &wm8990_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0,
+ &wm8990_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0,
+ &wm8990_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0,
+ &wm8990_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0,
+ &wm8990_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0,
+ &wm8990_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0,
+ &wm8990_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT,
+ 0, &wm8990_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8990_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0,
+ NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0,
+ NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1,
+ WM8990_MICBIAS_ENA_BIT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AILNMUX */
+ {"AILNMUX", "INMIXL Mix", "INMIXL"},
+ {"AILNMUX", "DIFFINL Mix", "LIN12PGA"},
+ {"AILNMUX", "DIFFINL Mix", "LIN34PGA"},
+ {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AILNMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AIRNMUX */
+ {"AIRNMUX", "INMIXR Mix", "INMIXR"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AIRNMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+static int wm8990_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets,
+ ARRAY_SIZE(wm8990_dapm_widgets));
+
+ /* set up the WM8990 audio map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2;
+ u32 n;
+ u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 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
+ "WM8990 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 wm8990_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct _pll_div pll_div;
+
+ if (freq_in && freq_out) {
+ pll_factors(&pll_div, freq_out * 4, freq_in);
+
+ /* Turn on PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ reg |= WM8990_PLL_ENA;
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+ /* sysclk comes from PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2);
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
+
+ /* set up N , fractional mode and pre-divisor if neccessary */
+ wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
+ (pll_div.div2?WM8990_PRESCALE:0));
+ wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
+ wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
+ } else {
+ /* Turn on PLL */
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ reg &= ~WM8990_PLL_ENA;
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+ }
+ return 0;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8990_priv *wm8990 = codec->private_data;
+
+ wm8990->sysclk = freq;
+ return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
+ audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8990_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8990_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8990_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8990_AIF_TMF_I2S;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8990_AIF_TMF_RIGHTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8990_AIF_TMF_LEFTJ;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8990_AIF_TMF_DSP;
+ audio1 &= ~WM8990_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8990_MCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_MCLK_DIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_DACCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_DAC_CLKDIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_ADCCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
+ ~WM8990_ADC_CLKDIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
+ break;
+ case WM8990_BCLK_DIV:
+ reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) &
+ ~WM8990_BCLK_DIV_MASK;
+ wm8990_write(codec, WM8990_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8990_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8990_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8990_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8990_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8990_AIF_WL_32BITS;
+ break;
+ }
+
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8990_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val;
+
+ val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
+
+ if (mute)
+ wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+ else
+ wm8990_write(codec, WM8990_DAC_CTRL, val);
+
+ return 0;
+}
+
+static int wm8990_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* Enable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_VMIDTOG);
+
+ /* Delay to allow output caps to discharge */
+ msleep(msecs_to_jiffies(300));
+
+ /* Disable VMIDTOG */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL);
+
+ /* disable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, 0);
+
+ /* Enable outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
+
+ msleep(msecs_to_jiffies(50));
+
+ /* Enable VMID at 2x50k */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
+
+ msleep(msecs_to_jiffies(100));
+
+ /* Enable VREF */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ msleep(msecs_to_jiffies(600));
+
+ /* Enable BUFIOEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* Disable outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
+ } else {
+ /* ON -> standby */
+
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_POBCTRL | WM8990_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+ WM8990_BUFDCOPEN | WM8990_POBCTRL |
+ WM8990_BUFIOEN);
+
+ /* mute DAC */
+ val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL);
+ wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+ /* Disable VMID */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
+
+ msleep(msecs_to_jiffies(300));
+
+ /* Enable all output discharge bits */
+ wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+ WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+ WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+ WM8990_DIS_ROUT);
+
+ /* Disable VREF */
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8990_write(codec, WM8990_ANTIPOP2, 0x0);
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/*
+ * The WM8990 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+struct snd_soc_dai wm8990_dai = {
+/* ADC/DAC on primary */
+ .name = "WM8990 ADC/DAC Primary",
+ .id = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8990_RATES,
+ .formats = WM8990_FORMATS,},
+ .ops = {
+ .hw_params = wm8990_hw_params,},
+ .dai_ops = {
+ .digital_mute = wm8990_mute,
+ .set_fmt = wm8990_set_dai_fmt,
+ .set_clkdiv = wm8990_set_dai_clkdiv,
+ .set_pll = wm8990_set_dai_pll,
+ .set_sysclk = wm8990_set_dai_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(wm8990_dai);
+
+static int wm8990_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;
+
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8990_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(wm8990_reg); i++) {
+ if (i + 1 == WM8990_RESET)
+ continue;
+ data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+ data[1] = cache[i] & 0x00ff;
+ codec->hw_write(codec->control_data, data, 2);
+ }
+
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+/*
+ * initialise the WM8990 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8990_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+ int ret = 0;
+
+ codec->name = "WM8990";
+ codec->owner = THIS_MODULE;
+ codec->read = wm8990_read_reg_cache;
+ codec->write = wm8990_write;
+ codec->set_bias_level = wm8990_set_bias_level;
+ codec->dai = &wm8990_dai;
+ codec->num_dai = 2;
+ codec->reg_cache_size = ARRAY_SIZE(wm8990_reg);
+ codec->reg_cache = kmemdup(wm8990_reg, sizeof(wm8990_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ wm8990_reset(codec);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8990: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* charge output caps */
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4);
+ wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) &
+ ~WM8990_GPIO1_SEL_MASK;
+ wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
+
+ reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
+ wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
+
+ wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ wm8990_add_controls(codec);
+ wm8990_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm8990: 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 *wm8990_socdev;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+/*
+ * WM891 2 wire address is determined by GPIO5
+ * state during powerup.
+ * low = 0x34
+ * high = 0x36
+ */
+
+static int wm8990_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_device *socdev = wm8990_socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret;
+
+ i2c_set_clientdata(i2c, codec);
+ codec->control_data = i2c;
+
+ ret = wm8990_init(socdev);
+ if (ret < 0)
+ pr_err("failed to initialise WM8990\n");
+
+ return ret;
+}
+
+static int wm8990_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ kfree(codec->reg_cache);
+ return 0;
+}
+
+static const struct i2c_device_id wm8990_i2c_id[] = {
+ { "wm8990", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id);
+
+static struct i2c_driver wm8990_i2c_driver = {
+ .driver = {
+ .name = "WM8990 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8990_i2c_probe,
+ .remove = wm8990_i2c_remove,
+ .id_table = wm8990_i2c_id,
+};
+
+static int wm8990_add_i2c_device(struct platform_device *pdev,
+ const struct wm8990_setup_data *setup)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ int ret;
+
+ ret = i2c_add_driver(&wm8990_i2c_driver);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "can't add i2c driver\n");
+ return ret;
+ }
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = setup->i2c_address;
+ strlcpy(info.type, "wm8990", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(setup->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "can't get i2c adapter %d\n",
+ setup->i2c_bus);
+ goto err_driver;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ goto err_driver;
+ }
+
+ return 0;
+
+err_driver:
+ i2c_del_driver(&wm8990_i2c_driver);
+ return -ENODEV;
+}
+#endif
+
+static int wm8990_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct wm8990_setup_data *setup;
+ struct snd_soc_codec *codec;
+ struct wm8990_priv *wm8990;
+ int ret;
+
+ pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION);
+
+ setup = socdev->codec_data;
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL);
+ if (wm8990 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = wm8990;
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+ wm8990_socdev = socdev;
+
+ ret = -ENODEV;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ if (setup->i2c_address) {
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ ret = wm8990_add_i2c_device(pdev, setup);
+ }
+#endif
+
+ if (ret != 0) {
+ kfree(codec->private_data);
+ kfree(codec);
+ }
+ return ret;
+}
+
+/* power down chip */
+static int wm8990_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)
+ wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_unregister_device(codec->control_data);
+ i2c_del_driver(&wm8990_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8990 = {
+ .probe = wm8990_probe,
+ .remove = wm8990_remove,
+ .suspend = wm8990_suspend,
+ .resume = wm8990_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990);
+
+MODULE_DESCRIPTION("ASoC WM8990 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h
new file mode 100644
index 0000000..0e192f3
--- /dev/null
+++ b/sound/soc/codecs/wm8990.h
@@ -0,0 +1,843 @@
+/*
+ * wm8990.h -- audio driver for WM8990
+ *
+ * Copyright 2007 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.
+ *
+ */
+
+#ifndef __WM8990REGISTERDEFS_H__
+#define __WM8990REGISTERDEFS_H__
+
+/*
+ * Register values.
+ */
+#define WM8990_RESET 0x00
+#define WM8990_POWER_MANAGEMENT_1 0x01
+#define WM8990_POWER_MANAGEMENT_2 0x02
+#define WM8990_POWER_MANAGEMENT_3 0x03
+#define WM8990_AUDIO_INTERFACE_1 0x04
+#define WM8990_AUDIO_INTERFACE_2 0x05
+#define WM8990_CLOCKING_1 0x06
+#define WM8990_CLOCKING_2 0x07
+#define WM8990_AUDIO_INTERFACE_3 0x08
+#define WM8990_AUDIO_INTERFACE_4 0x09
+#define WM8990_DAC_CTRL 0x0A
+#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8990_DIGITAL_SIDE_TONE 0x0D
+#define WM8990_ADC_CTRL 0x0E
+#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8990_GPIO_CTRL_1 0x12
+#define WM8990_GPIO1_GPIO2 0x13
+#define WM8990_GPIO3_GPIO4 0x14
+#define WM8990_GPIO5_GPIO6 0x15
+#define WM8990_GPIOCTRL_2 0x16
+#define WM8990_GPIO_POL 0x17
+#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8990_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8990_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8990_OUT3_4_VOLUME 0x1F
+#define WM8990_LEFT_OPGA_VOLUME 0x20
+#define WM8990_RIGHT_OPGA_VOLUME 0x21
+#define WM8990_SPEAKER_VOLUME 0x22
+#define WM8990_CLASSD1 0x23
+#define WM8990_CLASSD3 0x25
+#define WM8990_CLASSD4 0x26
+#define WM8990_INPUT_MIXER1 0x27
+#define WM8990_INPUT_MIXER2 0x28
+#define WM8990_INPUT_MIXER3 0x29
+#define WM8990_INPUT_MIXER4 0x2A
+#define WM8990_INPUT_MIXER5 0x2B
+#define WM8990_INPUT_MIXER6 0x2C
+#define WM8990_OUTPUT_MIXER1 0x2D
+#define WM8990_OUTPUT_MIXER2 0x2E
+#define WM8990_OUTPUT_MIXER3 0x2F
+#define WM8990_OUTPUT_MIXER4 0x30
+#define WM8990_OUTPUT_MIXER5 0x31
+#define WM8990_OUTPUT_MIXER6 0x32
+#define WM8990_OUT3_4_MIXER 0x33
+#define WM8990_LINE_MIXER1 0x34
+#define WM8990_LINE_MIXER2 0x35
+#define WM8990_SPEAKER_MIXER 0x36
+#define WM8990_ADDITIONAL_CONTROL 0x37
+#define WM8990_ANTIPOP1 0x38
+#define WM8990_ANTIPOP2 0x39
+#define WM8990_MICBIAS 0x3A
+#define WM8990_PLL1 0x3C
+#define WM8990_PLL2 0x3D
+#define WM8990_PLL3 0x3E
+#define WM8990_INTDRIVBITS 0x3F
+
+#define WM8990_REGISTER_COUNT 60
+#define WM8990_MAX_REGISTER 0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */
+#define WM8990_SPK_ENA_BIT 12
+#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */
+#define WM8990_OUT3_ENA_BIT 11
+#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */
+#define WM8990_OUT4_ENA_BIT 10
+#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */
+#define WM8990_LOUT_ENA_BIT 9
+#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */
+#define WM8990_ROUT_ENA_BIT 8
+#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */
+#define WM8990_MICBIAS_ENA_BIT 4
+#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */
+#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */
+#define WM8990_VREF_ENA_BIT 0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */
+#define WM8990_PLL_ENA_BIT 15
+#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8990_TSHUT_ENA_BIT 14
+#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8990_TSHUT_OPDIS_BIT 13
+#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8990_OPCLK_ENA_BIT 11
+#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */
+#define WM8990_AINL_ENA_BIT 9
+#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */
+#define WM8990_AINR_ENA_BIT 8
+#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */
+#define WM8990_LIN34_ENA_BIT 7
+#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */
+#define WM8990_LIN12_ENA_BIT 6
+#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */
+#define WM8990_RIN34_ENA_BIT 5
+#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */
+#define WM8990_RIN12_ENA_BIT 4
+#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8990_ADCL_ENA_BIT 1
+#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8990_ADCR_ENA_BIT 0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8990_LON_ENA 0x2000 /* LON_ENA */
+#define WM8990_LON_ENA_BIT 13
+#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */
+#define WM8990_LOP_ENA_BIT 12
+#define WM8990_RON_ENA 0x0800 /* RON_ENA */
+#define WM8990_RON_ENA_BIT 11
+#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */
+#define WM8990_ROP_ENA_BIT 10
+#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */
+#define WM8990_LOPGA_ENA_BIT 7
+#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */
+#define WM8990_ROPGA_ENA_BIT 6
+#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */
+#define WM8990_LOMIX_ENA_BIT 5
+#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */
+#define WM8990_ROMIX_ENA_BIT 4
+#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8990_DACL_ENA_BIT 1
+#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8990_DACR_ENA_BIT 0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8990_AIF_WL_16BITS (0 << 5)
+#define WM8990_AIF_WL_20BITS (1 << 5)
+#define WM8990_AIF_WL_24BITS (2 << 5)
+#define WM8990_AIF_WL_32BITS (3 << 5)
+#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8990_AIF_TMF_RIGHTJ (0 << 3)
+#define WM8990_AIF_TMF_LEFTJ (1 << 3)
+#define WM8990_AIF_TMF_I2S (2 << 3)
+#define WM8990_AIF_TMF_DSP (3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */
+#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */
+#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */
+#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */
+#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8990_BCLK_DIV_1 (0x0 << 1)
+#define WM8990_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8990_BCLK_DIV_2 (0x2 << 1)
+#define WM8990_BCLK_DIV_3 (0x3 << 1)
+#define WM8990_BCLK_DIV_4 (0x4 << 1)
+#define WM8990_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8990_BCLK_DIV_6 (0x6 << 1)
+#define WM8990_BCLK_DIV_8 (0x7 << 1)
+#define WM8990_BCLK_DIV_11 (0x8 << 1)
+#define WM8990_BCLK_DIV_12 (0x9 << 1)
+#define WM8990_BCLK_DIV_16 (0xA << 1)
+#define WM8990_BCLK_DIV_22 (0xB << 1)
+#define WM8990_BCLK_DIV_24 (0xC << 1)
+#define WM8990_BCLK_DIV_32 (0xD << 1)
+#define WM8990_BCLK_DIV_44 (0xE << 1)
+#define WM8990_BCLK_DIV_48 (0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */
+#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */
+#define WM8990_MCLK_DIV_1 (0 << 11)
+#define WM8990_MCLK_DIV_2 (2 << 11)
+#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */
+#define WM8990_ADC_CLKDIV_1 (0 << 5)
+#define WM8990_ADC_CLKDIV_1_5 (1 << 5)
+#define WM8990_ADC_CLKDIV_2 (2 << 5)
+#define WM8990_ADC_CLKDIV_3 (3 << 5)
+#define WM8990_ADC_CLKDIV_4 (4 << 5)
+#define WM8990_ADC_CLKDIV_5_5 (5 << 5)
+#define WM8990_ADC_CLKDIV_6 (6 << 5)
+#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */
+#define WM8990_DAC_CLKDIV_1 (0 << 2)
+#define WM8990_DAC_CLKDIV_1_5 (1 << 2)
+#define WM8990_DAC_CLKDIV_2 (2 << 2)
+#define WM8990_DAC_CLKDIV_3 (3 << 2)
+#define WM8990_DAC_CLKDIV_4 (4 << 2)
+#define WM8990_DAC_CLKDIV_5_5 (5 << 2)
+#define WM8990_DAC_CLKDIV_6 (6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */
+#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */
+#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */
+#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */
+#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */
+#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */
+#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */
+#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */
+#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */
+#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8990_DACL_VOL_SHIFT 0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8990_DAC_VU 0x0100 /* DAC_VU */
+#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8990_DACR_VOL_SHIFT 0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */
+#define WM8990_ADCL_DAC_SVOL_SHIFT 9
+#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */
+#define WM8990_ADCR_DAC_SVOL_SHIFT 5
+#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */
+#define WM8990_ADC_TO_DACL_SHIFT 2
+#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */
+#define WM8990_ADC_TO_DACR_SHIFT 0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */
+#define WM8990_ADC_HPF_ENA_BIT 8
+#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */
+#define WM8990_ADC_HPF_CUT_SHIFT 5
+#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8990_ADCL_DATINV_BIT 1
+#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8990_ADCR_DATINV_BIT 0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8990_ADCL_VOL_SHIFT 0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8990_ADC_VU 0x0100 /* ADC_VU */
+#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8990_ADCR_VOL_SHIFT 0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8990_IRQ 0x1000 /* IRQ */
+#define WM8990_TEMPOK 0x0800 /* TEMPOK */
+#define WM8990_MICSHRT 0x0400 /* MICSHRT */
+#define WM8990_MICDET 0x0200 /* MICDET */
+#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */
+#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */
+#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */
+#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */
+#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */
+#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */
+#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */
+#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */
+#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */
+#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */
+#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */
+#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */
+#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */
+#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */
+#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */
+#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */
+#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */
+#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */
+#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */
+#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */
+#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */
+#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */
+#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */
+#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */
+#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */
+#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */
+#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */
+#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */
+#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */
+#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */
+#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */
+#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */
+#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */
+#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */
+#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */
+#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */
+#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */
+#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */
+#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */
+#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */
+#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */
+#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */
+#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */
+#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */
+#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */
+#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */
+#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */
+#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */
+#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */
+#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */
+#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */
+#define WM8990_LI12MUTE_BIT 7
+#define WM8990_LI12ZC 0x0040 /* LI12ZC */
+#define WM8990_LI12ZC_BIT 6
+#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */
+#define WM8990_LIN12VOL_SHIFT 0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */
+#define WM8990_LI34MUTE_BIT 7
+#define WM8990_LI34ZC 0x0040 /* LI34ZC */
+#define WM8990_LI34ZC_BIT 6
+#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */
+#define WM8990_LIN34VOL_SHIFT 0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */
+#define WM8990_RI12MUTE_BIT 7
+#define WM8990_RI12ZC 0x0040 /* RI12ZC */
+#define WM8990_RI12ZC_BIT 6
+#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */
+#define WM8990_RIN12VOL_SHIFT 0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8990_IPVU 0x0100 /* IPVU */
+#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */
+#define WM8990_RI34MUTE_BIT 7
+#define WM8990_RI34ZC 0x0040 /* RI34ZC */
+#define WM8990_RI34ZC_BIT 6
+#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */
+#define WM8990_RIN34VOL_SHIFT 0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOZC 0x0080 /* LOZC */
+#define WM8990_LOZC_BIT 7
+#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8990_LOUTVOL_SHIFT 0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROZC 0x0080 /* ROZC */
+#define WM8990_ROZC_BIT 7
+#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8990_ROUTVOL_SHIFT 0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8990_LONMUTE 0x0040 /* LONMUTE */
+#define WM8990_LONMUTE_BIT 6
+#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */
+#define WM8990_LOPMUTE_BIT 5
+#define WM8990_LOATTN 0x0010 /* LOATTN */
+#define WM8990_LOATTN_BIT 4
+#define WM8990_RONMUTE 0x0004 /* RONMUTE */
+#define WM8990_RONMUTE_BIT 2
+#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */
+#define WM8990_ROPMUTE_BIT 1
+#define WM8990_ROATTN 0x0001 /* ROATTN */
+#define WM8990_ROATTN_BIT 0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */
+#define WM8990_OUT3MUTE_BIT 5
+#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */
+#define WM8990_OUT3ATTN_BIT 4
+#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */
+#define WM8990_OUT4MUTE_BIT 1
+#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */
+#define WM8990_OUT4ATTN_BIT 0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */
+#define WM8990_LOPGAZC_BIT 7
+#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */
+#define WM8990_LOPGAVOL_SHIFT 0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8990_OPVU 0x0100 /* OPVU */
+#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */
+#define WM8990_ROPGAZC_BIT 7
+#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */
+#define WM8990_ROPGAVOL_SHIFT 0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */
+#define WM8990_SPKATTN_SHIFT 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8990_CDMODE 0x0100 /* CDMODE */
+#define WM8990_CDMODE_BIT 8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */
+#define WM8990_DCGAIN_SHIFT 3
+#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */
+#define WM8990_ACGAIN_SHIFT 0
+
+/*
+ * R38 (0x26) - ClassD4
+ */
+#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */
+#define WM8990_SPKZC_SHIFT 7 /* SPKZC */
+#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */
+#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */
+
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */
+#define WM8990_AINLMODE_SHIFT 2
+#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */
+#define WM8990_AINRMODE_SHIFT 0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8990_LMP4 0x0080 /* LMP4 */
+#define WM8990_LMP4_BIT 7 /* LMP4 */
+#define WM8990_LMN3 0x0040 /* LMN3 */
+#define WM8990_LMN3_BIT 6 /* LMN3 */
+#define WM8990_LMP2 0x0020 /* LMP2 */
+#define WM8990_LMP2_BIT 5 /* LMP2 */
+#define WM8990_LMN1 0x0010 /* LMN1 */
+#define WM8990_LMN1_BIT 4 /* LMN1 */
+#define WM8990_RMP4 0x0008 /* RMP4 */
+#define WM8990_RMP4_BIT 3 /* RMP4 */
+#define WM8990_RMN3 0x0004 /* RMN3 */
+#define WM8990_RMN3_BIT 2 /* RMN3 */
+#define WM8990_RMP2 0x0002 /* RMP2 */
+#define WM8990_RMP2_BIT 1 /* RMP2 */
+#define WM8990_RMN1 0x0001 /* RMN1 */
+#define WM8990_RMN1_BIT 0 /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8990_L34MNB 0x0100 /* L34MNB */
+#define WM8990_L34MNB_BIT 8
+#define WM8990_L34MNBST 0x0080 /* L34MNBST */
+#define WM8990_L34MNBST_BIT 7
+#define WM8990_L12MNB 0x0020 /* L12MNB */
+#define WM8990_L12MNB_BIT 5
+#define WM8990_L12MNBST 0x0010 /* L12MNBST */
+#define WM8990_L12MNBST_BIT 4
+#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */
+#define WM8990_LDBVOL_SHIFT 0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8990_R34MNB 0x0100 /* R34MNB */
+#define WM8990_R34MNB_BIT 8
+#define WM8990_R34MNBST 0x0080 /* R34MNBST */
+#define WM8990_R34MNBST_BIT 7
+#define WM8990_R12MNB 0x0020 /* R12MNB */
+#define WM8990_R12MNB_BIT 5
+#define WM8990_R12MNBST 0x0010 /* R12MNBST */
+#define WM8990_R12MNBST_BIT 4
+#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */
+#define WM8990_RDBVOL_SHIFT 0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */
+#define WM8990_LI2BVOL_SHIFT 6
+#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */
+#define WM8990_LR4BVOL_SHIFT 3
+#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */
+#define WM8990_LL4BVOL_SHIFT 0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */
+#define WM8990_RI2BVOL_SHIFT 6
+#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */
+#define WM8990_RL4BVOL_SHIFT 3
+#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */
+#define WM8990_RR4BVOL_SHIFT 0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8990_LRBLO 0x0080 /* LRBLO */
+#define WM8990_LRBLO_BIT 7
+#define WM8990_LLBLO 0x0040 /* LLBLO */
+#define WM8990_LLBLO_BIT 6
+#define WM8990_LRI3LO 0x0020 /* LRI3LO */
+#define WM8990_LRI3LO_BIT 5
+#define WM8990_LLI3LO 0x0010 /* LLI3LO */
+#define WM8990_LLI3LO_BIT 4
+#define WM8990_LR12LO 0x0008 /* LR12LO */
+#define WM8990_LR12LO_BIT 3
+#define WM8990_LL12LO 0x0004 /* LL12LO */
+#define WM8990_LL12LO_BIT 2
+#define WM8990_LDLO 0x0001 /* LDLO */
+#define WM8990_LDLO_BIT 0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8990_RLBRO 0x0080 /* RLBRO */
+#define WM8990_RLBRO_BIT 7
+#define WM8990_RRBRO 0x0040 /* RRBRO */
+#define WM8990_RRBRO_BIT 6
+#define WM8990_RLI3RO 0x0020 /* RLI3RO */
+#define WM8990_RLI3RO_BIT 5
+#define WM8990_RRI3RO 0x0010 /* RRI3RO */
+#define WM8990_RRI3RO_BIT 4
+#define WM8990_RL12RO 0x0008 /* RL12RO */
+#define WM8990_RL12RO_BIT 3
+#define WM8990_RR12RO 0x0004 /* RR12RO */
+#define WM8990_RR12RO_BIT 2
+#define WM8990_RDRO 0x0001 /* RDRO */
+#define WM8990_RDRO_BIT 0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */
+#define WM8990_LLI3LOVOL_SHIFT 6
+#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */
+#define WM8990_LR12LOVOL_SHIFT 3
+#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */
+#define WM8990_LL12LOVOL_SHIFT 0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */
+#define WM8990_RRI3ROVOL_SHIFT 6
+#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */
+#define WM8990_RL12ROVOL_SHIFT 3
+#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */
+#define WM8990_RR12ROVOL_SHIFT 0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */
+#define WM8990_LRI3LOVOL_SHIFT 6
+#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */
+#define WM8990_LRBLOVOL_SHIFT 3
+#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */
+#define WM8990_LLBLOVOL_SHIFT 0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */
+#define WM8990_RLI3ROVOL_SHIFT 6
+#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */
+#define WM8990_RLBROVOL_SHIFT 3
+#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */
+#define WM8990_RRBROVOL_SHIFT 0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */
+#define WM8990_LI4O3 0x0020 /* LI4O3 */
+#define WM8990_LI4O3_BIT 5
+#define WM8990_LPGAO3 0x0010 /* LPGAO3 */
+#define WM8990_LPGAO3_BIT 4
+#define WM8990_RI4O4 0x0002 /* RI4O4 */
+#define WM8990_RI4O4_BIT 1
+#define WM8990_RPGAO4 0x0001 /* RPGAO4 */
+#define WM8990_RPGAO4_BIT 0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */
+#define WM8990_LLOPGALON_BIT 6
+#define WM8990_LROPGALON 0x0020 /* LROPGALON */
+#define WM8990_LROPGALON_BIT 5
+#define WM8990_LOPLON 0x0010 /* LOPLON */
+#define WM8990_LOPLON_BIT 4
+#define WM8990_LR12LOP 0x0004 /* LR12LOP */
+#define WM8990_LR12LOP_BIT 2
+#define WM8990_LL12LOP 0x0002 /* LL12LOP */
+#define WM8990_LL12LOP_BIT 1
+#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */
+#define WM8990_LLOPGALOP_BIT 0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8990_RROPGARON 0x0040 /* RROPGARON */
+#define WM8990_RROPGARON_BIT 6
+#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */
+#define WM8990_RLOPGARON_BIT 5
+#define WM8990_ROPRON 0x0010 /* ROPRON */
+#define WM8990_ROPRON_BIT 4
+#define WM8990_RL12ROP 0x0004 /* RL12ROP */
+#define WM8990_RL12ROP_BIT 2
+#define WM8990_RR12ROP 0x0002 /* RR12ROP */
+#define WM8990_RR12ROP_BIT 1
+#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */
+#define WM8990_RROPGAROP_BIT 0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8990_LB2SPK 0x0080 /* LB2SPK */
+#define WM8990_LB2SPK_BIT 7
+#define WM8990_RB2SPK 0x0040 /* RB2SPK */
+#define WM8990_RB2SPK_BIT 6
+#define WM8990_LI2SPK 0x0020 /* LI2SPK */
+#define WM8990_LI2SPK_BIT 5
+#define WM8990_RI2SPK 0x0010 /* RI2SPK */
+#define WM8990_RI2SPK_BIT 4
+#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */
+#define WM8990_LOPGASPK_BIT 3
+#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */
+#define WM8990_ROPGASPK_BIT 2
+#define WM8990_LDSPK 0x0002 /* LDSPK */
+#define WM8990_LDSPK_BIT 1
+#define WM8990_RDSPK 0x0001 /* RDSPK */
+#define WM8990_RDSPK_BIT 0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8990_VROI 0x0001 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */
+#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */
+#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */
+#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */
+#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */
+#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8990_SOFTST 0x0040 /* SOFTST */
+#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */
+#define WM8990_POBCTRL 0x0002 /* POBCTRL */
+#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */
+#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */
+#define WM8990_MCD 0x0004 /* MCD */
+#define WM8990_MBSEL 0x0001 /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8990_SDM 0x0080 /* SDM */
+#define WM8990_PRESCALE 0x0040 /* PRESCALE */
+#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8990_INMIXL_PWR_BIT 0
+#define WM8990_AINLMUX_PWR_BIT 1
+#define WM8990_INMIXR_PWR_BIT 2
+#define WM8990_AINRMUX_PWR_BIT 3
+
+struct wm8990_setup_data {
+ unsigned i2c_bus;
+ unsigned short i2c_address;
+};
+
+#define WM8990_MCLK_DIV 0
+#define WM8990_DACCLK_DIV 1
+#define WM8990_ADCCLK_DIV 2
+#define WM8990_BCLK_DIV 3
+
+extern struct snd_soc_dai wm8990_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8990;
+
+#endif /* __WM8990REGISTERDEFS_H__ */
+/*------------------------------ END OF FILE ---------------------------------*/
diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
new file mode 100644
index 0000000..ffb471e
--- /dev/null
+++ b/sound/soc/codecs/wm9712.c
@@ -0,0 +1,750 @@
+/*
+ * wm9712.c -- ALSA Soc WM9712 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include "wm9712.h"
+
+#define WM9712_VERSION "0.4"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9712 register cache
+ */
+static const u16 wm9712_reg[] = {
+ 0x6174, 0x8000, 0x8000, 0x8000, /* 6 */
+ 0x0f0f, 0xaaa0, 0xc008, 0x6808, /* e */
+ 0xe808, 0xaaa0, 0xad00, 0x8000, /* 16 */
+ 0xe808, 0x3000, 0x8000, 0x0000, /* 1e */
+ 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+ 0x0405, 0x0410, 0xbb80, 0xbb80, /* 2e */
+ 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+ 0x0000, 0x2000, 0x0000, 0x0000, /* 3e */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 46 */
+ 0x0000, 0x0000, 0xf83e, 0xffff, /* 4e */
+ 0x0000, 0x0000, 0x0000, 0xf83e, /* 56 */
+ 0x0008, 0x0000, 0x0000, 0x0000, /* 5e */
+ 0xb032, 0x3e00, 0x0000, 0x0000, /* 66 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+ 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+ 0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
+ 0x0000, 0x0000 /* virtual hp mixers */
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+
+static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
+static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right",
+ "Mono"};
+static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"};
+static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2",
+ "Stereo"};
+static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer",
+ "Line", "Headphone Mixer", "Phone Mixer", "Phone"};
+static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9712_diff_sel[] = {"Mic", "Line"};
+
+static const struct soc_enum wm9712_enum[] = {
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select),
+SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux),
+SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src),
+SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc),
+SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base),
+SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain),
+SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type),
+SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel),
+};
+
+static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+
+SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0),
+SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9712_enum[0]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9712_enum[10]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
+
+SOC_SINGLE("Mic Headphone Volume", AC97_VIDEO, 12, 7, 1),
+SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
+
+SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
+SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1),
+SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
+
+SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
+SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1),
+SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1),
+SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1),
+SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
+
+SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1),
+SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
+
+SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
+SOC_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
+SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0),
+
+SOC_ENUM("Bass Control", wm9712_enum[5]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1),
+SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
+
+SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
+SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
+SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
+
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
+};
+
+/* add non dapm controls */
+static int wm9712_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm9712_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm9712_snd_ac97_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ u16 l, r, beep, line, phone, mic, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ mic = ac97_read(w->codec, AC97_VIDEO);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ line = ac97_read(w->codec, AC97_LINE);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_CD);
+
+ if (l & 0x1 || r & 0x1)
+ ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
+
+ if (l & 0x2 || r & 0x2)
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if (l & 0x4 || r & 0x4)
+ ac97_write(w->codec, AC97_LINE, line & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_LINE, line | 0x8000);
+
+ if (l & 0x8 || r & 0x8)
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if (l & 0x10 || r & 0x10)
+ ac97_write(w->codec, AC97_CD, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_CD, aux | 0x8000);
+
+ if (l & 0x20 || r & 0x20)
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
+ SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1),
+ SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1),
+};
+
+/* Phone Mixer */
+static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = {
+ SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1),
+ SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1),
+ SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1),
+ SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1),
+ SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1),
+ SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1),
+};
+
+/* ALC headphone mux */
+static const struct snd_kcontrol_new wm9712_alc_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[1]);
+
+/* out 3 mux */
+static const struct snd_kcontrol_new wm9712_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[2]);
+
+/* spk mux */
+static const struct snd_kcontrol_new wm9712_spk_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[3]);
+
+/* Capture to Phone mux */
+static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[4]);
+
+/* Capture left select */
+static const struct snd_kcontrol_new wm9712_capture_selectl_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[8]);
+
+/* Capture right select */
+static const struct snd_kcontrol_new wm9712_capture_selectr_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[9]);
+
+/* Mic select */
+static const struct snd_kcontrol_new wm9712_mic_src_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[7]);
+
+/* diff select */
+static const struct snd_kcontrol_new wm9712_diff_sel_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[11]);
+
+static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_alc_mux_controls),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_out3_mux_controls),
+SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_spk_mux_controls),
+SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_phone_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectl_controls),
+SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0,
+ &wm9712_capture_selectr_controls),
+SND_SOC_DAPM_MUX("Mic Select Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_mic_src_controls),
+SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
+ &wm9712_diff_sel_controls),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
+ &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
+ &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
+ &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
+ &wm9712_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9712_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1),
+SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONE"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+
+ /* Left HP mixer */
+ {"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Left HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Left HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* Right HP mixer */
+ {"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Right HP Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "Mic Sidetone Switch", "Mic PGA"},
+ {"Right HP Mixer", NULL, "ALC Sidetone Mux"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Speaker Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "Phone Bypass Switch", "Phone PGA"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+
+ /* Phone mixer */
+ {"Phone Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+ {"Phone Mixer", "Line Bypass Switch", "Line PGA"},
+ {"Phone Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Phone Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"},
+ {"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"},
+
+ /* inputs */
+ {"Line PGA", NULL, "LINEINL"},
+ {"Line PGA", NULL, "LINEINR"},
+ {"Phone PGA", NULL, "PHONE"},
+ {"Mic PGA", NULL, "MIC1"},
+ {"Mic PGA", NULL, "MIC2"},
+
+ /* left capture selector */
+ {"Left Capture Select", "Mic", "MIC1"},
+ {"Left Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Left Capture Select", "Line", "LINEINL"},
+ {"Left Capture Select", "Headphone Mixer", "Left HP Mixer"},
+ {"Left Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Left Capture Select", "Phone", "PHONE"},
+
+ /* right capture selector */
+ {"Right Capture Select", "Mic", "MIC2"},
+ {"Right Capture Select", "Speaker Mixer", "Speaker Mixer"},
+ {"Right Capture Select", "Line", "LINEINR"},
+ {"Right Capture Select", "Headphone Mixer", "Right HP Mixer"},
+ {"Right Capture Select", "Phone Mixer", "Phone Mixer"},
+ {"Right Capture Select", "Phone", "PHONE"},
+
+ /* ALC Sidetone */
+ {"ALC Sidetone Mux", "Stereo", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Stereo", "Right Capture Select"},
+ {"ALC Sidetone Mux", "Left", "Left Capture Select"},
+ {"ALC Sidetone Mux", "Right", "Right Capture Select"},
+
+ /* ADC's */
+ {"Left ADC", NULL, "Left Capture Select"},
+ {"Right ADC", NULL, "Right Capture Select"},
+
+ /* outputs */
+ {"MONOOUT", NULL, "Phone Mixer"},
+ {"HPOUTL", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Left HP Mixer"},
+ {"HPOUTR", NULL, "Headphone PGA"},
+ {"Headphone PGA", NULL, "Right HP Mixer"},
+
+ /* mono mixer */
+ {"Mono Mixer", NULL, "Left HP Mixer"},
+ {"Mono Mixer", NULL, "Right HP Mixer"},
+
+ /* Out3 Mux */
+ {"Out3 Mux", "Left", "Left HP Mixer"},
+ {"Out3 Mux", "Mono", "Phone Mixer"},
+ {"Out3 Mux", "Left + Right", "Mono Mixer"},
+ {"Out 3 PGA", NULL, "Out3 Mux"},
+ {"OUT3", NULL, "Out 3 PGA"},
+
+ /* speaker Mux */
+ {"Speaker Mux", "Speaker Mix", "Speaker Mixer"},
+ {"Speaker Mux", "Headphone Mix", "Mono Mixer"},
+ {"Speaker PGA", NULL, "Speaker Mux"},
+ {"LOUT2", NULL, "Speaker PGA"},
+ {"ROUT2", NULL, "Speaker PGA"},
+};
+
+static int wm9712_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets,
+ ARRAY_SIZE(wm9712_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_REC_GAIN)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg > (ARRAY_SIZE(wm9712_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg <= (ARRAY_SIZE(wm9712_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai wm9712_dai[] = {
+{
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97_BUS,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_prepare,},
+},
+{
+ .name = "AC97 Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9712_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_aux_prepare,},
+}
+};
+EXPORT_SYMBOL_GPL(wm9712_dai);
+
+static int wm9712_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm9712_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9712_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9712_reg[0])
+ goto err;
+ return 0;
+
+err:
+ printk(KERN_ERR "WM9712 AC97 reset failed\n");
+ return -EIO;
+}
+
+static int wm9712_soc_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm9712_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9712_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (ret == 0) {
+ /* Sync reg_cache with the hardware after cold reset */
+ for (i = 2; i < ARRAY_SIZE(wm9712_reg) << 1; i += 2) {
+ if (i == AC97_INT_PAGING || i == AC97_POWERDOWN ||
+ (i > 0x58 && i != 0x5c))
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+ return ret;
+}
+
+static int wm9712_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache = kmemdup(wm9712_reg, sizeof(wm9712_reg), GFP_KERNEL);
+
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ codec->reg_cache_size = sizeof(wm9712_reg);
+ codec->reg_cache_step = 2;
+
+ codec->name = "WM9712";
+ codec->owner = THIS_MODULE;
+ codec->dai = wm9712_dai;
+ codec->num_dai = ARRAY_SIZE(wm9712_dai);
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ codec->set_bias_level = wm9712_set_bias_level;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9712: failed to register AC97 codec\n");
+ goto codec_err;
+ }
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+ ret = wm9712_reset(codec, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ /* set alc mux to none */
+ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000);
+
+ wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ wm9712_add_controls(codec);
+ wm9712_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "wm9712: failed to register card\n");
+ goto reset_err;
+ }
+
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int wm9712_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->reg_cache);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm9712 = {
+ .probe = wm9712_soc_probe,
+ .remove = wm9712_soc_remove,
+ .suspend = wm9712_soc_suspend,
+ .resume = wm9712_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm9712);
+
+MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h
new file mode 100644
index 0000000..d29e8a1
--- /dev/null
+++ b/sound/soc/codecs/wm9712.h
@@ -0,0 +1,14 @@
+/*
+ * wm9712.h -- WM9712 Soc Audio driver
+ */
+
+#ifndef _WM9712_H
+#define _WM9712_H
+
+#define WM9712_DAI_AC97_HIFI 0
+#define WM9712_DAI_AC97_AUX 1
+
+extern struct snd_soc_dai wm9712_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_wm9712;
+
+#endif
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
new file mode 100644
index 0000000..945b32e
--- /dev/null
+++ b/sound/soc/codecs/wm9713.c
@@ -0,0 +1,1306 @@
+/*
+ * wm9713.c -- ALSA Soc WM9713 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ *
+ * Features:-
+ *
+ * o Support for AC97 Codec, Voice DAC and Aux DAC
+ * o Support for DAPM
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "wm9713.h"
+
+#define WM9713_VERSION "0.15"
+
+struct wm9713_priv {
+ u32 pll_in; /* PLL input frequency */
+ u32 pll_out; /* PLL output frequency */
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int val);
+
+/*
+ * WM9713 register cache
+ * Reg 0x3c bit 15 is used by touch driver.
+ */
+static const u16 wm9713_reg[] = {
+ 0x6174, 0x8080, 0x8080, 0x8080,
+ 0xc880, 0xe808, 0xe808, 0x0808,
+ 0x00da, 0x8000, 0xd600, 0xaaa0,
+ 0xaaa0, 0xaaa0, 0x0000, 0x0000,
+ 0x0f0f, 0x0040, 0x0000, 0x7f00,
+ 0x0405, 0x0410, 0xbb80, 0xbb80,
+ 0x0000, 0xbb80, 0x0000, 0x4523,
+ 0x0000, 0x2000, 0x7eff, 0xffff,
+ 0x0000, 0x0000, 0x0080, 0x0000,
+ 0x0000, 0x0000, 0xfffe, 0xffff,
+ 0x0000, 0x0000, 0x0000, 0xfffe,
+ 0x4000, 0x0000, 0x0000, 0x0000,
+ 0xb032, 0x3e00, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0006,
+ 0x0001, 0x0000, 0x574d, 0x4c13,
+ 0x0000, 0x0000, 0x0000
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER 0x80
+#define HPR_MIXER 0x82
+#define MICB_MUX 0x82
+
+static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
+static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9713_rec_src[] =
+ {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker",
+ "Mono Out", "Zh"};
+static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv",
+ "Mono Vmid", "Inv Vmid"};
+static const char *wm9713_spk_pga[] =
+ {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
+ "Speaker Vmid", "Inv Vmid"};
+static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone",
+ "Headphone Vmid"};
+static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"};
+static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"};
+static const char *wm9713_dac_inv[] =
+ {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone",
+ "Headphone Mono", "NC", "Vmid"};
+static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"};
+static const char *wm9713_micb_select[] = {"MPB", "MPA"};
+
+static const struct soc_enum wm9713_enum[] = {
+SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/
+SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
+SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
+SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
+SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
+};
+
+static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
+SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+
+SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
+SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
+
+SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
+SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
+SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
+
+SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1),
+SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
+SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9713_enum[6]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9713_enum[17]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
+
+SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0),
+SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
+
+SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
+SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1),
+
+SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
+SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
+SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1),
+
+SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1),
+
+SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1),
+SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1),
+SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1),
+
+SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1),
+SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
+SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
+SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
+SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1),
+
+SOC_ENUM("Bass Control", wm9713_enum[16]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
+SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
+SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
+};
+
+/* add non dapm controls */
+static int wm9713_add_controls(struct snd_soc_codec *codec)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm9713_snd_ac97_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path using the current
+ * register map, thus we add a new (virtual) register to help determine the
+ * audio route within the device.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 l, r, beep, tone, phone, rec, pcm, aux;
+
+ l = ac97_read(w->codec, HPL_MIXER);
+ r = ac97_read(w->codec, HPR_MIXER);
+ beep = ac97_read(w->codec, AC97_PC_BEEP);
+ tone = ac97_read(w->codec, AC97_MASTER_TONE);
+ phone = ac97_read(w->codec, AC97_PHONE);
+ rec = ac97_read(w->codec, AC97_REC_SEL);
+ pcm = ac97_read(w->codec, AC97_PCM);
+ aux = ac97_read(w->codec, AC97_AUX);
+
+ if (event & SND_SOC_DAPM_PRE_REG)
+ return 0;
+ if ((l & 0x1) || (r & 0x1))
+ ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+ if ((l & 0x2) || (r & 0x2))
+ ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
+
+ if ((l & 0x4) || (r & 0x4))
+ ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+ if ((l & 0x8) || (r & 0x8))
+ ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
+
+ if ((l & 0x10) || (r & 0x10))
+ ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+ if ((l & 0x20) || (r & 0x20))
+ ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
+ else
+ ac97_write(w->codec, AC97_AUX, aux | 0x8000);
+
+ return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* headphone capture mux */
+static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[1]);
+
+/* headphone mic mux */
+static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[0]);
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1),
+SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1),
+SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1),
+};
+
+/* mono mic mux */
+static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[2]);
+
+/* mono output mux */
+static const struct snd_kcontrol_new wm9713_mono_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[7]);
+
+/* speaker left output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[8]);
+
+/* speaker right output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[9]);
+
+/* headphone left output mux */
+static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[10]);
+
+/* headphone right output mux */
+static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[11]);
+
+/* Out3 mux */
+static const struct snd_kcontrol_new wm9713_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[12]);
+
+/* Out4 mux */
+static const struct snd_kcontrol_new wm9713_out4_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[13]);
+
+/* DAC inv mux 1 */
+static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[14]);
+
+/* DAC inv mux 2 */
+static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[15]);
+
+/* Capture source left */
+static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[3]);
+
+/* Capture source right */
+static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[4]);
+
+/* mic source */
+static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[18]);
+
+/* mic source B virtual control */
+static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[19]);
+
+static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_rec_mux_controls),
+SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_mic_mux_controls),
+SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mic_mux_controls),
+SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_mono_mux_controls),
+SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkl_mux_controls),
+SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hp_spkr_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out3_mux_controls),
+SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0,
+ &wm9713_out4_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv1_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0,
+ &wm9713_dac_inv2_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcl_mux_controls),
+SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_rec_srcr_mux_controls),
+SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_mic_sel_mux_controls),
+SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
+ &wm9713_micb_sel_mux_controls),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
+ &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
+ &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
+ mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
+ &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
+ &wm9713_speaker_mixer_controls[0],
+ ARRAY_SIZE(wm9713_speaker_mixer_controls)),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
+SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
+SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1),
+SND_SOC_DAPM_OUTPUT("MONO"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKL"),
+SND_SOC_DAPM_OUTPUT("SPKR"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_INPUT("LINEL"),
+SND_SOC_DAPM_INPUT("LINER"),
+SND_SOC_DAPM_INPUT("MONOIN"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2A"),
+SND_SOC_DAPM_INPUT("MIC2B"),
+SND_SOC_DAPM_VMID("VMID"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* left HP mixer */
+ {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"},
+ {"Left HP Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Left HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* right HP mixer */
+ {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"},
+ {"Right HP Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"},
+ {"Right HP Mixer", NULL, "Capture Headphone Mux"},
+
+ /* virtual mixer - mixes left & right channels for spk and mono */
+ {"AC97 Mixer", NULL, "Left DAC"},
+ {"AC97 Mixer", NULL, "Right DAC"},
+ {"Line Mixer", NULL, "Right Line In"},
+ {"Line Mixer", NULL, "Left Line In"},
+ {"HP Mixer", NULL, "Left HP Mixer"},
+ {"HP Mixer", NULL, "Right HP Mixer"},
+ {"Capture Mixer", NULL, "Left Capture Source"},
+ {"Capture Mixer", NULL, "Right Capture Source"},
+
+ /* speaker mixer */
+ {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"},
+
+ /* mono mixer */
+ {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"},
+ {"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+ {"Mono Mixer", "Aux Playback Switch", "Aux DAC"},
+ {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"},
+ {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"},
+ {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"},
+ {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"},
+ {"Mono Mixer", NULL, "Capture Mono Mux"},
+
+ /* DAC inv mux 1 */
+ {"DAC Inv Mux 1", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"},
+
+ /* DAC inv mux 2 */
+ {"DAC Inv Mux 2", "Mono", "Mono Mixer"},
+ {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"},
+ {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"},
+ {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"},
+ {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"},
+
+ /* headphone left mux */
+ {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"},
+
+ /* headphone right mux */
+ {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"},
+
+ /* speaker left mux */
+ {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"},
+ {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* speaker right mux */
+ {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"},
+ {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
+ {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"},
+
+ /* mono mux */
+ {"Mono Out Mux", "Mono", "Mono Mixer"},
+ {"Mono Out Mux", "Inv", "DAC Inv Mux 1"},
+
+ /* out 3 mux */
+ {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"},
+
+ /* out 4 mux */
+ {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Out Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Out Mux"},
+ {"OUT3", NULL, "Out 3"},
+ {"Out 3", NULL, "Out 3 Mux"},
+ {"OUT4", NULL, "Out 4"},
+ {"Out 4", NULL, "Out 4 Mux"},
+ {"SPKL", NULL, "Left Speaker"},
+ {"Left Speaker", NULL, "Left Speaker Out Mux"},
+ {"SPKR", NULL, "Right Speaker"},
+ {"Right Speaker", NULL, "Right Speaker Out Mux"},
+ {"MONO", NULL, "Mono Out"},
+ {"Mono Out", NULL, "Mono Out Mux"},
+
+ /* input pga */
+ {"Left Line In", NULL, "LINEL"},
+ {"Right Line In", NULL, "LINER"},
+ {"Mono In", NULL, "MONOIN"},
+ {"Mic A PGA", NULL, "Mic A Pre Amp"},
+ {"Mic B PGA", NULL, "Mic B Pre Amp"},
+
+ /* left capture select */
+ {"Left Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Left Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Left Capture Source", "Line", "LINEL"},
+ {"Left Capture Source", "Mono In", "MONOIN"},
+ {"Left Capture Source", "Headphone", "Left HP Mixer"},
+ {"Left Capture Source", "Speaker", "Speaker Mixer"},
+ {"Left Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* right capture select */
+ {"Right Capture Source", "Mic 1", "Mic A Pre Amp"},
+ {"Right Capture Source", "Mic 2", "Mic B Pre Amp"},
+ {"Right Capture Source", "Line", "LINER"},
+ {"Right Capture Source", "Mono In", "MONOIN"},
+ {"Right Capture Source", "Headphone", "Right HP Mixer"},
+ {"Right Capture Source", "Speaker", "Speaker Mixer"},
+ {"Right Capture Source", "Mono Out", "Mono Mixer"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Source"},
+ {"Left Voice ADC", NULL, "Left ADC"},
+ {"Left HiFi ADC", NULL, "Left ADC"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Source"},
+ {"Right Voice ADC", NULL, "Right ADC"},
+ {"Right HiFi ADC", NULL, "Right ADC"},
+
+ /* mic */
+ {"Mic A Pre Amp", NULL, "Mic A Source"},
+ {"Mic A Source", "Mic 1", "MIC1"},
+ {"Mic A Source", "Mic 2 A", "MIC2A"},
+ {"Mic A Source", "Mic 2 B", "Mic B Source"},
+ {"Mic B Pre Amp", "MPB", "Mic B Source"},
+ {"Mic B Source", NULL, "MIC2B"},
+
+ /* headphone capture */
+ {"Capture Headphone Mux", "Stereo", "Capture Mixer"},
+ {"Capture Headphone Mux", "Left", "Left Capture Source"},
+ {"Capture Headphone Mux", "Right", "Right Capture Source"},
+
+ /* mono capture */
+ {"Capture Mono Mux", "Stereo", "Capture Mixer"},
+ {"Capture Mono Mux", "Left", "Left Capture Source"},
+ {"Capture Mono Mux", "Right", "Right Capture Source"},
+};
+
+static int wm9713_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets,
+ ARRAY_SIZE(wm9713_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+ reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+ reg == AC97_CD)
+ return soc_ac97_ops.read(codec->ac97, reg);
+ else {
+ reg = reg >> 1;
+
+ if (reg > (ARRAY_SIZE(wm9713_reg)))
+ return -EIO;
+
+ return cache[reg];
+ }
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < 0x7c)
+ soc_ac97_ops.write(codec->ac97, reg, val);
+ reg = reg >> 1;
+ if (reg <= (ARRAY_SIZE(wm9713_reg)))
+ cache[reg] = val;
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 divsel:1;
+ u32 divctl:1;
+ u32 lf:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+/* The size in bits of the PLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+
+ /* The the PLL output is always 98.304MHz. */
+ target = 98304000;
+
+ /* If the input frequency is over 14.4MHz then scale it down. */
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divsel = 1;
+
+ if (source > 14400000) {
+ source >>= 1;
+ pll_div->divctl = 1;
+ } else
+ pll_div->divctl = 0;
+
+ } else {
+ pll_div->divsel = 0;
+ pll_div->divctl = 0;
+ }
+
+ /* Low frequency sources require an additional divide in the
+ * loop.
+ */
+ if (source < 8192000) {
+ pll_div->lf = 1;
+ target >>= 2;
+ } else
+ pll_div->lf = 0;
+
+ Ndiv = target / source;
+ if ((Ndiv < 5) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM9713 PLL N value %d out of recommended range!\n",
+ Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+/**
+ * Please note that changing the PLL input frequency may require
+ * resynchronisation with the AC97 controller.
+ */
+static int wm9713_set_pll(struct snd_soc_codec *codec,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct wm9713_priv *wm9713 = codec->private_data;
+ u16 reg, reg2;
+ struct _pll_div pll_div;
+
+ /* turn PLL off ? */
+ if (freq_in == 0 || freq_out == 0) {
+ /* disable PLL power and select ext source */
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080);
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200);
+ wm9713->pll_out = 0;
+ return 0;
+ }
+
+ pll_factors(&pll_div, freq_in);
+
+ if (pll_div.k == 0) {
+ reg = (pll_div.n << 12) | (pll_div.lf << 11) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ } else {
+ /* write the fractional k to the reg 0x46 pages */
+ reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) |
+ (pll_div.divsel << 9) | (pll_div.divctl << 8);
+
+ /* K [21:20] */
+ reg = reg2 | (0x5 << 4) | (pll_div.k >> 20);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [19:16] */
+ reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [15:12] */
+ reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [11:8] */
+ reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ /* K [7:4] */
+ reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf);
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+ reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */
+ ac97_write(codec, AC97_LINE1_LEVEL, reg);
+ }
+
+ /* turn PLL on and select as source */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff);
+ reg = ac97_read(codec, AC97_HANDSET_RATE);
+ ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f);
+ wm9713->pll_out = freq_out;
+ wm9713->pll_in = freq_in;
+
+ /* wait 10ms AC97 link frames for the link to stabilise */
+ schedule_timeout_interruptible(msecs_to_jiffies(10));
+ return 0;
+}
+
+static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ return wm9713_set_pll(codec, pll_id, freq_in, freq_out);
+}
+
+/*
+ * Tristate the PCM DAI lines, tristate can be disabled by calling
+ * wm9713_set_dai_fmt()
+ */
+static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai,
+ int tristate)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff;
+
+ if (tristate)
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+
+ return 0;
+}
+
+/*
+ * Configure WM9713 clock dividers.
+ * Voice DAC needs 256 FS
+ */
+static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM9713_PCMCLK_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKA_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_CLKB_MULT:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_HIFI_DIV:
+ reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff;
+ ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+ break;
+ case WM9713_PCMBCLK_DIV:
+ reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff;
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div);
+ break;
+ case WM9713_PCMCLK_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div);
+ break;
+ case WM9713_HIFI_PLL_DIV:
+ reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+ ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5;
+ u16 reg = 0x8000;
+
+ /* clock masters */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ reg |= 0x4000;
+ gpio |= 0x0010;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ reg |= 0x6000;
+ gpio |= 0x0018;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ reg |= 0x2000;
+ gpio |= 0x001a;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ gpio |= 0x0012;
+ break;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ reg |= 0x00c0;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg |= 0x0040;
+ break;
+ }
+
+ /* DAI format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ reg |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ reg |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ reg |= 0x0043;
+ break;
+ }
+
+ ac97_write(codec, AC97_GPIO_CFG, gpio);
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ reg |= 0x0004;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg |= 0x0008;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ reg |= 0x000c;
+ break;
+ }
+
+ /* enable PCM interface in master mode */
+ ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+ return 0;
+}
+
+static void wm9713_voiceshutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 status;
+
+ /* Gracefully shut down the voice interface. */
+ status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000;
+ ac97_write(codec, AC97_HANDSET_RATE, 0x0280);
+ schedule_timeout_interruptible(msecs_to_jiffies(1));
+ ac97_write(codec, AC97_HANDSET_RATE, 0x0F80);
+ ac97_write(codec, AC97_EXTENDED_MID, status);
+}
+
+static int ac97_hifi_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int reg;
+ u16 vra;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = AC97_PCM_FRONT_DAC_RATE;
+ else
+ reg = AC97_PCM_LR_ADC_RATE;
+
+ return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 vra, xsle;
+
+ vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+ ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+ xsle = ac97_read(codec, AC97_PCI_SID);
+ ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+
+ return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_FORMATS \
+ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+ SNDRV_PCM_FORMAT_S24_LE)
+
+struct snd_soc_dai wm9713_dai[] = {
+{
+ .name = "AC97 HiFi",
+ .type = SND_SOC_DAI_AC97_BUS,
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "HiFi Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_hifi_prepare,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,},
+ },
+ {
+ .name = "AC97 Aux",
+ .playback = {
+ .stream_name = "Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .prepare = ac97_aux_prepare,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,},
+ },
+ {
+ .name = "WM9713 Voice",
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .capture = {
+ .stream_name = "Voice Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM9713_PCM_RATES,
+ .formats = WM9713_PCM_FORMATS,},
+ .ops = {
+ .hw_params = wm9713_pcm_hw_params,
+ .shutdown = wm9713_voiceshutdown,},
+ .dai_ops = {
+ .set_clkdiv = wm9713_set_dai_clkdiv,
+ .set_pll = wm9713_set_dai_pll,
+ .set_fmt = wm9713_set_dai_fmt,
+ .set_tristate = wm9713_set_dai_tristate,
+ },
+ },
+};
+EXPORT_SYMBOL_GPL(wm9713_dai);
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
+{
+ if (try_warm && soc_ac97_ops.warm_reset) {
+ soc_ac97_ops.warm_reset(codec->ac97);
+ if (ac97_read(codec, 0) == wm9713_reg[0])
+ return 1;
+ }
+
+ soc_ac97_ops.reset(codec->ac97);
+ if (ac97_read(codec, 0) != wm9713_reg[0])
+ return -EIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm9713_reset);
+
+static int wm9713_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ /* enable thermal shutdown */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* enable master bias and vmid */
+ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff;
+ ac97_write(codec, AC97_EXTENDED_MID, reg);
+ ac97_write(codec, AC97_POWERDOWN, 0x0000);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* disable everything including AC link */
+ ac97_write(codec, AC97_EXTENDED_MID, 0xffff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+static int wm9713_soc_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ u16 reg;
+
+ /* Disable everything except touchpanel - that will be handled
+ * by the touch driver and left disabled if touch is not in
+ * use. */
+ reg = ac97_read(codec, AC97_EXTENDED_MID);
+ ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff);
+ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+ ac97_write(codec, AC97_POWERDOWN, 0x6f00);
+ ac97_write(codec, AC97_POWERDOWN, 0xffff);
+
+ return 0;
+}
+
+static int wm9713_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct wm9713_priv *wm9713 = codec->private_data;
+ int i, ret;
+ u16 *cache = codec->reg_cache;
+
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "could not reset AC97 codec\n");
+ return ret;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* do we need to re-start the PLL ? */
+ if (wm9713->pll_out)
+ wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out);
+
+ /* only synchronise the codec if warm reset failed */
+ if (ret == 0) {
+ for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) {
+ if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID ||
+ i == AC97_EXTENDED_MSTATUS || i > 0x66)
+ continue;
+ soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+ }
+ }
+
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+ return ret;
+}
+
+static int wm9713_soc_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0, reg;
+
+ printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION);
+
+ socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->codec == NULL)
+ return -ENOMEM;
+ codec = socdev->codec;
+ mutex_init(&codec->mutex);
+
+ codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL);
+ if (codec->reg_cache == NULL) {
+ ret = -ENOMEM;
+ goto cache_err;
+ }
+ codec->reg_cache_size = sizeof(wm9713_reg);
+ codec->reg_cache_step = 2;
+
+ codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
+ if (codec->private_data == NULL) {
+ ret = -ENOMEM;
+ goto priv_err;
+ }
+
+ codec->name = "WM9713";
+ codec->owner = THIS_MODULE;
+ codec->dai = wm9713_dai;
+ codec->num_dai = ARRAY_SIZE(wm9713_dai);
+ codec->write = ac97_write;
+ codec->read = ac97_read;
+ codec->set_bias_level = wm9713_set_bias_level;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+ if (ret < 0)
+ goto codec_err;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+ /* do a cold reset for the controller and then try
+ * a warm reset followed by an optional cold reset for codec */
+ wm9713_reset(codec, 0);
+ ret = wm9713_reset(codec, 1);
+ if (ret < 0) {
+ printk(KERN_ERR "AC97 link error\n");
+ goto reset_err;
+ }
+
+ wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* unmute the adc - move to kcontrol */
+ reg = ac97_read(codec, AC97_CD) & 0x7fff;
+ ac97_write(codec, AC97_CD, reg);
+
+ wm9713_add_controls(codec);
+ wm9713_add_widgets(codec);
+ ret = snd_soc_register_card(socdev);
+ if (ret < 0)
+ goto reset_err;
+ return 0;
+
+reset_err:
+ snd_soc_free_pcms(socdev);
+
+pcm_err:
+ snd_soc_free_ac97_codec(codec);
+
+codec_err:
+ kfree(codec->private_data);
+
+priv_err:
+ kfree(codec->reg_cache);
+
+cache_err:
+ kfree(socdev->codec);
+ socdev->codec = NULL;
+ return ret;
+}
+
+static int wm9713_soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->codec;
+
+ if (codec == NULL)
+ return 0;
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ snd_soc_free_ac97_codec(codec);
+ kfree(codec->private_data);
+ kfree(codec->reg_cache);
+ kfree(codec->dai);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm9713 = {
+ .probe = wm9713_soc_probe,
+ .remove = wm9713_soc_remove,
+ .suspend = wm9713_soc_suspend,
+ .resume = wm9713_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713);
+
+MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h
new file mode 100644
index 0000000..63b8d81
--- /dev/null
+++ b/sound/soc/codecs/wm9713.h
@@ -0,0 +1,53 @@
+/*
+ * wm9713.h -- WM9713 Soc Audio driver
+ */
+
+#ifndef _WM9713_H
+#define _WM9713_H
+
+/* clock inputs */
+#define WM9713_CLKA_PIN 0
+#define WM9713_CLKB_PIN 1
+
+/* clock divider ID's */
+#define WM9713_PCMCLK_DIV 0
+#define WM9713_CLKA_MULT 1
+#define WM9713_CLKB_MULT 2
+#define WM9713_HIFI_DIV 3
+#define WM9713_PCMBCLK_DIV 4
+#define WM9713_PCMCLK_PLL_DIV 5
+#define WM9713_HIFI_PLL_DIV 6
+
+/* Calculate the appropriate bit mask for the external PCM clock divider */
+#define WM9713_PCMDIV(x) ((x - 1) << 8)
+
+/* Calculate the appropriate bit mask for the external HiFi clock divider */
+#define WM9713_HIFIDIV(x) ((x - 1) << 12)
+
+/* MCLK clock mulitipliers */
+#define WM9713_CLKA_X1 (0 << 1)
+#define WM9713_CLKA_X2 (1 << 1)
+#define WM9713_CLKB_X1 (0 << 2)
+#define WM9713_CLKB_X2 (1 << 2)
+
+/* MCLK clock MUX */
+#define WM9713_CLK_MUX_A (0 << 0)
+#define WM9713_CLK_MUX_B (1 << 0)
+
+/* Voice DAI BCLK divider */
+#define WM9713_PCMBCLK_DIV_1 (0 << 9)
+#define WM9713_PCMBCLK_DIV_2 (1 << 9)
+#define WM9713_PCMBCLK_DIV_4 (2 << 9)
+#define WM9713_PCMBCLK_DIV_8 (3 << 9)
+#define WM9713_PCMBCLK_DIV_16 (4 << 9)
+
+#define WM9713_DAI_AC97_HIFI 0
+#define WM9713_DAI_AC97_AUX 1
+#define WM9713_DAI_PCM_VOICE 2
+
+extern struct snd_soc_codec_device soc_codec_dev_wm9713;
+extern struct snd_soc_dai wm9713_dai[3];
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm);
+
+#endif
diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig
new file mode 100644
index 0000000..8f7e338
--- /dev/null
+++ b/sound/soc/davinci/Kconfig
@@ -0,0 +1,19 @@
+config SND_DAVINCI_SOC
+ tristate "SoC Audio for the TI DAVINCI chip"
+ depends on ARCH_DAVINCI
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the DAVINCI AC97 or I2S interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_DAVINCI_SOC_I2S
+ tristate
+
+config SND_DAVINCI_SOC_EVM
+ tristate "SoC Audio support for DaVinci EVM"
+ depends on SND_DAVINCI_SOC && MACH_DAVINCI_EVM
+ select SND_DAVINCI_SOC_I2S
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on TI
+ DaVinci EVM platform.
diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile
new file mode 100644
index 0000000..ca772e5
--- /dev/null
+++ b/sound/soc/davinci/Makefile
@@ -0,0 +1,11 @@
+# DAVINCI Platform Support
+snd-soc-davinci-objs := davinci-pcm.o
+snd-soc-davinci-i2s-objs := davinci-i2s.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o
+obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o
+
+# DAVINCI Machine Support
+snd-soc-evm-objs := davinci-evm.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o
diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c
new file mode 100644
index 0000000..9e6062c
--- /dev/null
+++ b/sound/soc/davinci/davinci-evm.c
@@ -0,0 +1,202 @@
+/*
+ * ASoC driver for TI DAVINCI EVM platform
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include "../codecs/tlv320aic3x.h"
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+#define EVM_CODEC_CLOCK 22579200
+
+static int evm_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int ret = 0;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_IB_NF);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, EVM_CODEC_CLOCK,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops evm_ops = {
+ .hw_params = evm_hw_params,
+};
+
+/* davinci-evm machine dapm widgets */
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+/* davinci-evm machine audio_mapnections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Headphone connected to HPLOUT, HPROUT */
+ {"Headphone Jack", NULL, "HPLOUT"},
+ {"Headphone Jack", NULL, "HPROUT"},
+
+ /* Line Out connected to LLOUT, RLOUT */
+ {"Line Out", NULL, "LLOUT"},
+ {"Line Out", NULL, "RLOUT"},
+
+ /* Mic connected to (MIC3L | MIC3R) */
+ {"MIC3L", NULL, "Mic Bias 2V"},
+ {"MIC3R", NULL, "Mic Bias 2V"},
+ {"Mic Bias 2V", NULL, "Mic Jack"},
+
+ /* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */
+ {"LINE1L", NULL, "Line In"},
+ {"LINE2L", NULL, "Line In"},
+ {"LINE1R", NULL, "Line In"},
+ {"LINE2R", NULL, "Line In"},
+};
+
+/* Logic for a aic3x as connected on a davinci-evm */
+static int evm_aic3x_init(struct snd_soc_codec *codec)
+{
+ /* Add davinci-evm specific widgets */
+ snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+ ARRAY_SIZE(aic3x_dapm_widgets));
+
+ /* Set up davinci-evm specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ /* not connected */
+ snd_soc_dapm_disable_pin(codec, "MONO_LOUT");
+ snd_soc_dapm_disable_pin(codec, "HPLCOM");
+ snd_soc_dapm_disable_pin(codec, "HPRCOM");
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Line Out");
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_enable_pin(codec, "Line In");
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* davinci-evm digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link evm_dai = {
+ .name = "TLV320AIC3X",
+ .stream_name = "AIC3X",
+ .cpu_dai = &davinci_i2s_dai,
+ .codec_dai = &aic3x_dai,
+ .init = evm_aic3x_init,
+ .ops = &evm_ops,
+};
+
+/* davinci-evm audio machine driver */
+static struct snd_soc_machine snd_soc_machine_evm = {
+ .name = "DaVinci EVM",
+ .dai_link = &evm_dai,
+ .num_links = 1,
+};
+
+/* evm audio private data */
+static struct aic3x_setup_data evm_aic3x_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1b,
+};
+
+/* evm audio subsystem */
+static struct snd_soc_device evm_snd_devdata = {
+ .machine = &snd_soc_machine_evm,
+ .platform = &davinci_soc_platform,
+ .codec_dev = &soc_codec_dev_aic3x,
+ .codec_data = &evm_aic3x_setup,
+};
+
+static struct resource evm_snd_resources[] = {
+ {
+ .start = DAVINCI_MCBSP_BASE,
+ .end = DAVINCI_MCBSP_BASE + SZ_8K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct evm_snd_platform_data evm_snd_data = {
+ .tx_dma_ch = DM644X_DMACH_MCBSP_TX,
+ .rx_dma_ch = DM644X_DMACH_MCBSP_RX,
+};
+
+static struct platform_device *evm_snd_device;
+
+static int __init evm_init(void)
+{
+ int ret;
+
+ evm_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!evm_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(evm_snd_device, &evm_snd_devdata);
+ evm_snd_devdata.dev = &evm_snd_device->dev;
+ evm_snd_device->dev.platform_data = &evm_snd_data;
+
+ ret = platform_device_add_resources(evm_snd_device, evm_snd_resources,
+ ARRAY_SIZE(evm_snd_resources));
+ if (ret) {
+ platform_device_put(evm_snd_device);
+ return ret;
+ }
+
+ ret = platform_device_add(evm_snd_device);
+ if (ret)
+ platform_device_put(evm_snd_device);
+
+ return ret;
+}
+
+static void __exit evm_exit(void)
+{
+ platform_device_unregister(evm_snd_device);
+}
+
+module_init(evm_init);
+module_exit(evm_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
new file mode 100644
index 0000000..abb5fed
--- /dev/null
+++ b/sound/soc/davinci/davinci-i2s.c
@@ -0,0 +1,409 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "davinci-pcm.h"
+
+#define DAVINCI_MCBSP_DRR_REG 0x00
+#define DAVINCI_MCBSP_DXR_REG 0x04
+#define DAVINCI_MCBSP_SPCR_REG 0x08
+#define DAVINCI_MCBSP_RCR_REG 0x0c
+#define DAVINCI_MCBSP_XCR_REG 0x10
+#define DAVINCI_MCBSP_SRGR_REG 0x14
+#define DAVINCI_MCBSP_PCR_REG 0x24
+
+#define DAVINCI_MCBSP_SPCR_RRST (1 << 0)
+#define DAVINCI_MCBSP_SPCR_RINTM(v) ((v) << 4)
+#define DAVINCI_MCBSP_SPCR_XRST (1 << 16)
+#define DAVINCI_MCBSP_SPCR_XINTM(v) ((v) << 20)
+#define DAVINCI_MCBSP_SPCR_GRST (1 << 22)
+#define DAVINCI_MCBSP_SPCR_FRST (1 << 23)
+#define DAVINCI_MCBSP_SPCR_FREE (1 << 25)
+
+#define DAVINCI_MCBSP_RCR_RWDLEN1(v) ((v) << 5)
+#define DAVINCI_MCBSP_RCR_RFRLEN1(v) ((v) << 8)
+#define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16)
+#define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21)
+
+#define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5)
+#define DAVINCI_MCBSP_XCR_XFRLEN1(v) ((v) << 8)
+#define DAVINCI_MCBSP_XCR_XDATDLY(v) ((v) << 16)
+#define DAVINCI_MCBSP_XCR_XFIG (1 << 18)
+#define DAVINCI_MCBSP_XCR_XWDLEN2(v) ((v) << 21)
+
+#define DAVINCI_MCBSP_SRGR_FWID(v) ((v) << 8)
+#define DAVINCI_MCBSP_SRGR_FPER(v) ((v) << 16)
+#define DAVINCI_MCBSP_SRGR_FSGM (1 << 28)
+
+#define DAVINCI_MCBSP_PCR_CLKRP (1 << 0)
+#define DAVINCI_MCBSP_PCR_CLKXP (1 << 1)
+#define DAVINCI_MCBSP_PCR_FSRP (1 << 2)
+#define DAVINCI_MCBSP_PCR_FSXP (1 << 3)
+#define DAVINCI_MCBSP_PCR_CLKRM (1 << 8)
+#define DAVINCI_MCBSP_PCR_CLKXM (1 << 9)
+#define DAVINCI_MCBSP_PCR_FSRM (1 << 10)
+#define DAVINCI_MCBSP_PCR_FSXM (1 << 11)
+
+#define MOD_REG_BIT(val, mask, set) do { \
+ if (set) { \
+ val |= mask; \
+ } else { \
+ val &= ~mask; \
+ } \
+} while (0)
+
+enum {
+ DAVINCI_MCBSP_WORD_8 = 0,
+ DAVINCI_MCBSP_WORD_12,
+ DAVINCI_MCBSP_WORD_16,
+ DAVINCI_MCBSP_WORD_20,
+ DAVINCI_MCBSP_WORD_24,
+ DAVINCI_MCBSP_WORD_32,
+};
+
+static struct davinci_pcm_dma_params davinci_i2s_pcm_out = {
+ .name = "I2S PCM Stereo out",
+};
+
+static struct davinci_pcm_dma_params davinci_i2s_pcm_in = {
+ .name = "I2S PCM Stereo in",
+};
+
+struct davinci_mcbsp_dev {
+ void __iomem *base;
+ struct clk *clk;
+ struct davinci_pcm_dma_params *dma_params[2];
+};
+
+static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev,
+ int reg, u32 val)
+{
+ __raw_writel(val, dev->base + reg);
+}
+
+static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg)
+{
+ return __raw_readl(dev->base + reg);
+}
+
+static void davinci_mcbsp_start(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
+ u32 w;
+
+ /* Start the sample generator and enable transmitter/receiver */
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST, 1);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 1);
+ else
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
+
+ /* Start frame sync */
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_FRST, 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
+}
+
+static void davinci_mcbsp_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
+ u32 w;
+
+ /* Reset transmitter/receiver and sample rate/frame sync generators */
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST |
+ DAVINCI_MCBSP_SPCR_FRST, 0);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 0);
+ else
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 0);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
+}
+
+static int davinci_i2s_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
+
+ cpu_dai->dma_data = dev->dma_params[substream->stream];
+
+ return 0;
+}
+
+static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
+ u32 w;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG,
+ DAVINCI_MCBSP_PCR_FSXM |
+ DAVINCI_MCBSP_PCR_FSRM |
+ DAVINCI_MCBSP_PCR_CLKXM |
+ DAVINCI_MCBSP_PCR_CLKRM);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG,
+ DAVINCI_MCBSP_SRGR_FSGM);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_PCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_PCR_CLKXP |
+ DAVINCI_MCBSP_PCR_CLKRP, 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, w);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_PCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_PCR_FSXP |
+ DAVINCI_MCBSP_PCR_FSRP, 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, w);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_PCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_PCR_CLKXP |
+ DAVINCI_MCBSP_PCR_CLKRP |
+ DAVINCI_MCBSP_PCR_FSXP |
+ DAVINCI_MCBSP_PCR_FSRP, 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, w);
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
+ struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
+ struct snd_interval *i = NULL;
+ int mcbsp_word_length;
+ u32 w;
+
+ /* general line settings */
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG,
+ DAVINCI_MCBSP_SPCR_RINTM(3) |
+ DAVINCI_MCBSP_SPCR_XINTM(3) |
+ DAVINCI_MCBSP_SPCR_FREE);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG,
+ DAVINCI_MCBSP_RCR_RFRLEN1(1) |
+ DAVINCI_MCBSP_RCR_RDATDLY(1));
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG,
+ DAVINCI_MCBSP_XCR_XFRLEN1(1) |
+ DAVINCI_MCBSP_XCR_XDATDLY(1) |
+ DAVINCI_MCBSP_XCR_XFIG);
+
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SRGR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1), 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, w);
+
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SRGR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1), 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, w);
+
+ /* Determine xfer data type */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ dma_params->data_type = 1;
+ mcbsp_word_length = DAVINCI_MCBSP_WORD_8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dma_params->data_type = 2;
+ mcbsp_word_length = DAVINCI_MCBSP_WORD_16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ dma_params->data_type = 4;
+ mcbsp_word_length = DAVINCI_MCBSP_WORD_32;
+ break;
+ default:
+ printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
+ return -EINVAL;
+ }
+
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_RCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
+ DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length), 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, w);
+
+ w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_XCR_REG);
+ MOD_REG_BIT(w, DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
+ DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length), 1);
+ davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, w);
+
+ return 0;
+}
+
+static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ davinci_mcbsp_start(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ davinci_mcbsp_stop(substream);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int davinci_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai;
+ struct davinci_mcbsp_dev *dev;
+ struct resource *mem, *ioarea;
+ struct evm_snd_platform_data *pdata;
+ int ret;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "no mem resource?\n");
+ return -ENODEV;
+ }
+
+ ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1,
+ pdev->name);
+ if (!ioarea) {
+ dev_err(&pdev->dev, "McBSP region already claimed\n");
+ return -EBUSY;
+ }
+
+ dev = kzalloc(sizeof(struct davinci_mcbsp_dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto err_release_region;
+ }
+
+ cpu_dai->private_data = dev;
+
+ dev->clk = clk_get(&pdev->dev, "McBSPCLK");
+ if (IS_ERR(dev->clk)) {
+ ret = -ENODEV;
+ goto err_free_mem;
+ }
+ clk_enable(dev->clk);
+
+ dev->base = (void __iomem *)IO_ADDRESS(mem->start);
+ pdata = pdev->dev.platform_data;
+
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK] = &davinci_i2s_pcm_out;
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = pdata->tx_dma_ch;
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->dma_addr =
+ (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DXR_REG);
+
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE] = &davinci_i2s_pcm_in;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = pdata->rx_dma_ch;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->dma_addr =
+ (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DRR_REG);
+
+ return 0;
+
+err_free_mem:
+ kfree(dev);
+err_release_region:
+ release_mem_region(mem->start, (mem->end - mem->start) + 1);
+
+ return ret;
+}
+
+static void davinci_i2s_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai;
+ struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
+ struct resource *mem;
+
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
+ dev->clk = NULL;
+
+ kfree(dev);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(mem->start, (mem->end - mem->start) + 1);
+}
+
+#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000
+
+struct snd_soc_dai davinci_i2s_dai = {
+ .name = "davinci-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .probe = davinci_i2s_probe,
+ .remove = davinci_i2s_remove,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = DAVINCI_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .startup = davinci_i2s_startup,
+ .trigger = davinci_i2s_trigger,
+ .hw_params = davinci_i2s_hw_params,},
+ .dai_ops = {
+ .set_fmt = davinci_i2s_set_dai_fmt,
+ },
+};
+EXPORT_SYMBOL_GPL(davinci_i2s_dai);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI I2S (McBSP) SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h
new file mode 100644
index 0000000..241648c
--- /dev/null
+++ b/sound/soc/davinci/davinci-i2s.h
@@ -0,0 +1,17 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_I2S_H
+#define _DAVINCI_I2S_H
+
+extern struct snd_soc_dai davinci_i2s_dai;
+
+#endif
diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c
new file mode 100644
index 0000000..76feaa6
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm.c
@@ -0,0 +1,389 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "davinci-pcm.h"
+
+#define DAVINCI_PCM_DEBUG 0
+#if DAVINCI_PCM_DEBUG
+#define DPRINTK(x...) printk(KERN_DEBUG x)
+#else
+#define DPRINTK(x...)
+#endif
+
+static struct snd_pcm_hardware davinci_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE),
+ .rates = (SNDRV_PCM_RATE_8000 | 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 |
+ SNDRV_PCM_RATE_KNOT),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8 * 1024,
+ .periods_min = 16,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+struct davinci_runtime_data {
+ spinlock_t lock;
+ int period; /* current DMA period */
+ int master_lch; /* Master DMA channel */
+ int slave_lch; /* Slave DMA channel */
+ struct davinci_pcm_dma_params *params; /* DMA params */
+};
+
+static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int lch = prtd->slave_lch;
+ unsigned int period_size;
+ unsigned int dma_offset;
+ dma_addr_t dma_pos;
+ dma_addr_t src, dst;
+ unsigned short src_bidx, dst_bidx;
+ unsigned int data_type;
+ unsigned int count;
+
+ period_size = snd_pcm_lib_period_bytes(substream);
+ dma_offset = prtd->period * period_size;
+ dma_pos = runtime->dma_addr + dma_offset;
+
+ DPRINTK("audio_set_dma_params_play channel = %d dma_ptr = %x "
+ "period_size=%x\n", lch, dma_pos, period_size);
+
+ data_type = prtd->params->data_type;
+ count = period_size / data_type;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ src = dma_pos;
+ dst = prtd->params->dma_addr;
+ src_bidx = data_type;
+ dst_bidx = 0;
+ } else {
+ src = prtd->params->dma_addr;
+ dst = dma_pos;
+ src_bidx = 0;
+ dst_bidx = data_type;
+ }
+
+ davinci_set_dma_src_params(lch, src, INCR, W8BIT);
+ davinci_set_dma_dest_params(lch, dst, INCR, W8BIT);
+ davinci_set_dma_src_index(lch, src_bidx, 0);
+ davinci_set_dma_dest_index(lch, dst_bidx, 0);
+ davinci_set_dma_transfer_params(lch, data_type, count, 1, 0, ASYNC);
+
+ prtd->period++;
+ if (unlikely(prtd->period >= runtime->periods))
+ prtd->period = 0;
+}
+
+static void davinci_pcm_dma_irq(int lch, u16 ch_status, void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+
+ DPRINTK("lch=%d, status=0x%x\n", lch, ch_status);
+
+ if (unlikely(ch_status != DMA_COMPLETE))
+ return;
+
+ if (snd_pcm_running(substream)) {
+ snd_pcm_period_elapsed(substream);
+
+ spin_lock(&prtd->lock);
+ davinci_pcm_enqueue_dma(substream);
+ spin_unlock(&prtd->lock);
+ }
+}
+
+static int davinci_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct davinci_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
+ int tcc = TCC_ANY;
+ int ret;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ prtd->params = dma_data;
+
+ /* Request master DMA channel */
+ ret = davinci_request_dma(prtd->params->channel, prtd->params->name,
+ davinci_pcm_dma_irq, substream,
+ &prtd->master_lch, &tcc, EVENTQ_0);
+ if (ret)
+ return ret;
+
+ /* Request slave DMA channel */
+ ret = davinci_request_dma(PARAM_ANY, "Link",
+ NULL, NULL, &prtd->slave_lch, &tcc, EVENTQ_0);
+ if (ret) {
+ davinci_free_dma(prtd->master_lch);
+ return ret;
+ }
+
+ /* Link slave DMA channel in loopback */
+ davinci_dma_link_lch(prtd->slave_lch, prtd->slave_lch);
+
+ return 0;
+}
+
+static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ davinci_start_dma(prtd->master_lch);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ davinci_stop_dma(prtd->master_lch);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct davinci_runtime_data *prtd = substream->runtime->private_data;
+ struct paramentry_descriptor temp;
+
+ prtd->period = 0;
+ davinci_pcm_enqueue_dma(substream);
+
+ /* Get slave channel dma params for master channel startup */
+ davinci_get_dma_params(prtd->slave_lch, &temp);
+ davinci_set_dma_params(prtd->master_lch, &temp);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+davinci_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd = runtime->private_data;
+ unsigned int offset;
+ dma_addr_t count;
+ dma_addr_t src, dst;
+
+ spin_lock(&prtd->lock);
+
+ davinci_dma_getposition(prtd->master_lch, &src, &dst);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ count = src - runtime->dma_addr;
+ else
+ count = dst - runtime->dma_addr;;
+
+ spin_unlock(&prtd->lock);
+
+ offset = bytes_to_frames(runtime, count);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int davinci_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd;
+ int ret = 0;
+
+ snd_soc_set_runtime_hwparams(substream, &davinci_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct davinci_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+
+ ret = davinci_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "davinci_pcm: Failed to get dma channels\n");
+ kfree(prtd);
+ }
+
+ return ret;
+}
+
+static int davinci_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct davinci_runtime_data *prtd = runtime->private_data;
+
+ davinci_dma_unlink_lch(prtd->slave_lch, prtd->slave_lch);
+
+ davinci_free_dma(prtd->slave_lch);
+ davinci_free_dma(prtd->master_lch);
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int davinci_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int davinci_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int davinci_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+struct snd_pcm_ops davinci_pcm_ops = {
+ .open = davinci_pcm_open,
+ .close = davinci_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = davinci_pcm_hw_params,
+ .hw_free = davinci_pcm_hw_free,
+ .prepare = davinci_pcm_prepare,
+ .trigger = davinci_pcm_trigger,
+ .pointer = davinci_pcm_pointer,
+ .mmap = davinci_pcm_mmap,
+};
+
+static int davinci_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 = davinci_pcm_hardware.buffer_bytes_max;
+
+ 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);
+
+ DPRINTK("preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
+ (void *) buf->area, (void *) buf->addr, size);
+
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void davinci_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ 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 davinci_pcm_dmamask = 0xffffffff;
+
+static int davinci_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &davinci_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = davinci_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = davinci_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+struct snd_soc_platform davinci_soc_platform = {
+ .name = "davinci-audio",
+ .pcm_ops = &davinci_pcm_ops,
+ .pcm_new = davinci_pcm_new,
+ .pcm_free = davinci_pcm_free,
+};
+EXPORT_SYMBOL_GPL(davinci_soc_platform);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h
new file mode 100644
index 0000000..62cb4eb
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm.h
@@ -0,0 +1,29 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author: Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_PCM_H
+#define _DAVINCI_PCM_H
+
+struct davinci_pcm_dma_params {
+ char *name; /* stream identifier */
+ int channel; /* sync dma channel ID */
+ dma_addr_t dma_addr; /* device physical address for DMA */
+ unsigned int data_type; /* xfer data type */
+};
+
+struct evm_snd_platform_data {
+ int tx_dma_ch;
+ int rx_dma_ch;
+};
+
+extern struct snd_soc_platform davinci_soc_platform;
+
+#endif
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
new file mode 100644
index 0000000..8d73edc
--- /dev/null
+++ b/sound/soc/fsl/Kconfig
@@ -0,0 +1,27 @@
+config SND_SOC_OF_SIMPLE
+ tristate
+
+config SND_SOC_MPC8610
+ bool "ALSA SoC support for the MPC8610 SOC"
+ depends on MPC8610_HPCD
+ default y if MPC8610
+ help
+ Say Y if you want to add support for codecs attached to the SSI
+ device on an MPC8610.
+
+config SND_SOC_MPC8610_HPCD
+ bool "ALSA SoC support for the Freescale MPC8610 HPCD board"
+ depends on SND_SOC_MPC8610
+ select SND_SOC_CS4270
+ select SND_SOC_CS4270_VD33_ERRATA
+ default y if MPC8610_HPCD
+ help
+ Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+
+config SND_SOC_MPC5200_I2S
+ tristate "Freescale MPC5200 PSC in I2S mode driver"
+ depends on SND_SOC && PPC_MPC52xx && PPC_BESTCOMM
+ select SND_SOC_OF_SIMPLE
+ select PPC_BESTCOMM_GEN_BD
+ help
+ Say Y here to support the MPC5200 PSCs in I2S mode.
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
new file mode 100644
index 0000000..035da4a
--- /dev/null
+++ b/sound/soc/fsl/Makefile
@@ -0,0 +1,11 @@
+# Simple machine driver that extracts configuration from the OF device tree
+obj-$(CONFIG_SND_SOC_OF_SIMPLE) += soc-of-simple.o
+
+# MPC8610 HPCD Machine Support
+obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o
+
+# MPC8610 Platform Support
+obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
+
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
+
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 0000000..d2d3da9
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,858 @@
+/*
+ * Freescale DMA ALSA SoC PCM driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ *
+ * This driver implements ASoC support for the Elo DMA controller, which is
+ * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
+ * the PCM driver is what handles the DMA buffer.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/io.h>
+
+#include "fsl_dma.h"
+
+/*
+ * The formats that the DMA controller supports, which is anything
+ * that is 8, 16, or 32 bits.
+ */
+#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_BE | \
+ SNDRV_PCM_FMTBIT_U24_LE | \
+ SNDRV_PCM_FMTBIT_U24_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | \
+ SNDRV_PCM_FMTBIT_S32_BE | \
+ SNDRV_PCM_FMTBIT_U32_LE | \
+ SNDRV_PCM_FMTBIT_U32_BE)
+
+#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+/* DMA global data. This structure is used by fsl_dma_open() to determine
+ * which DMA channels to assign to a substream. Unfortunately, ASoC V1 does
+ * not allow the machine driver to provide this information to the PCM
+ * driver in advance, and there's no way to differentiate between the two
+ * DMA controllers. So for now, this driver only supports one SSI device
+ * using two DMA channels. We cannot support multiple DMA devices.
+ *
+ * ssi_stx_phys: bus address of SSI STX register
+ * ssi_srx_phys: bus address of SSI SRX register
+ * dma_channel: pointer to the DMA channel's registers
+ * irq: IRQ for this DMA channel
+ * assigned: set to 1 if that DMA channel is assigned to a substream
+ */
+static struct {
+ dma_addr_t ssi_stx_phys;
+ dma_addr_t ssi_srx_phys;
+ struct ccsr_dma_channel __iomem *dma_channel[2];
+ unsigned int irq[2];
+ unsigned int assigned[2];
+} dma_global_data;
+
+/*
+ * The number of DMA links to use. Two is the bare minimum, but if you
+ * have really small links you might need more.
+ */
+#define NUM_DMA_LINKS 2
+
+/** fsl_dma_private: p-substream DMA data
+ *
+ * Each substream has a 1-to-1 association with a DMA channel.
+ *
+ * The link[] array is first because it needs to be aligned on a 32-byte
+ * boundary, so putting it first will ensure alignment without padding the
+ * structure.
+ *
+ * @link[]: array of link descriptors
+ * @controller_id: which DMA controller (0, 1, ...)
+ * @channel_id: which DMA channel on the controller (0, 1, 2, ...)
+ * @dma_channel: pointer to the DMA channel's registers
+ * @irq: IRQ for this DMA channel
+ * @substream: pointer to the substream object, needed by the ISR
+ * @ssi_sxx_phys: bus address of the STX or SRX register to use
+ * @ld_buf_phys: physical address of the LD buffer
+ * @current_link: index into link[] of the link currently being processed
+ * @dma_buf_phys: physical address of the DMA buffer
+ * @dma_buf_next: physical address of the next period to process
+ * @dma_buf_end: physical address of the byte after the end of the DMA
+ * @buffer period_size: the size of a single period
+ * @num_periods: the number of periods in the DMA buffer
+ */
+struct fsl_dma_private {
+ struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
+ unsigned int controller_id;
+ unsigned int channel_id;
+ struct ccsr_dma_channel __iomem *dma_channel;
+ unsigned int irq;
+ struct snd_pcm_substream *substream;
+ dma_addr_t ssi_sxx_phys;
+ dma_addr_t ld_buf_phys;
+ unsigned int current_link;
+ dma_addr_t dma_buf_phys;
+ dma_addr_t dma_buf_next;
+ dma_addr_t dma_buf_end;
+ size_t period_size;
+ unsigned int num_periods;
+};
+
+/**
+ * fsl_dma_hardare: define characteristics of the PCM hardware.
+ *
+ * The PCM hardware is the Freescale DMA controller. This structure defines
+ * the capabilities of that hardware.
+ *
+ * Since the sampling rate and data format are not controlled by the DMA
+ * controller, we specify no limits for those values. The only exception is
+ * period_bytes_min, which is set to a reasonably low value to prevent the
+ * DMA controller from generating too many interrupts per second.
+ *
+ * Since each link descriptor has a 32-bit byte count field, we set
+ * period_bytes_max to the largest 32-bit number. We also have no maximum
+ * number of periods.
+ *
+ * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a
+ * limitation in the SSI driver requires the sample rates for playback and
+ * capture to be the same.
+ */
+static const struct snd_pcm_hardware fsl_dma_hardware = {
+
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_JOINT_DUPLEX,
+ .formats = FSLDMA_PCM_FORMATS,
+ .rates = FSLDMA_PCM_RATES,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .period_bytes_min = 512, /* A reasonable limit */
+ .period_bytes_max = (u32) -1,
+ .periods_min = NUM_DMA_LINKS,
+ .periods_max = (unsigned int) -1,
+ .buffer_bytes_max = 128 * 1024, /* A reasonable limit */
+};
+
+/**
+ * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
+ *
+ * This function should be called by the ISR whenever the DMA controller
+ * halts data transfer.
+ */
+static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
+{
+ unsigned long flags;
+
+ snd_pcm_stream_lock_irqsave(substream, flags);
+
+ if (snd_pcm_running(substream))
+ snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+
+ snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * fsl_dma_update_pointers - update LD pointers to point to the next period
+ *
+ * As each period is completed, this function changes the the link
+ * descriptor pointers for that period to point to the next period.
+ */
+static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
+{
+ struct fsl_dma_link_descriptor *link =
+ &dma_private->link[dma_private->current_link];
+
+ /* Update our link descriptors to point to the next period */
+ if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ link->source_addr =
+ cpu_to_be32(dma_private->dma_buf_next);
+ else
+ link->dest_addr =
+ cpu_to_be32(dma_private->dma_buf_next);
+
+ /* Update our variables for next time */
+ dma_private->dma_buf_next += dma_private->period_size;
+
+ if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+ dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+ if (++dma_private->current_link >= NUM_DMA_LINKS)
+ dma_private->current_link = 0;
+}
+
+/**
+ * fsl_dma_isr: interrupt handler for the DMA controller
+ *
+ * @irq: IRQ of the DMA channel
+ * @dev_id: pointer to the dma_private structure for this DMA channel
+ */
+static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
+{
+ struct fsl_dma_private *dma_private = dev_id;
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ irqreturn_t ret = IRQ_NONE;
+ u32 sr, sr2 = 0;
+
+ /* We got an interrupt, so read the status register to see what we
+ were interrupted for.
+ */
+ sr = in_be32(&dma_channel->sr);
+
+ if (sr & CCSR_DMA_SR_TE) {
+ dev_err(dma_private->substream->pcm->card->dev,
+ "DMA transmit error (controller=%u channel=%u irq=%u\n",
+ dma_private->controller_id,
+ dma_private->channel_id, irq);
+ fsl_dma_abort_stream(dma_private->substream);
+ sr2 |= CCSR_DMA_SR_TE;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_CH)
+ ret = IRQ_HANDLED;
+
+ if (sr & CCSR_DMA_SR_PE) {
+ dev_err(dma_private->substream->pcm->card->dev,
+ "DMA%u programming error (channel=%u irq=%u)\n",
+ dma_private->controller_id,
+ dma_private->channel_id, irq);
+ fsl_dma_abort_stream(dma_private->substream);
+ sr2 |= CCSR_DMA_SR_PE;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_EOLNI) {
+ sr2 |= CCSR_DMA_SR_EOLNI;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_CB)
+ ret = IRQ_HANDLED;
+
+ if (sr & CCSR_DMA_SR_EOSI) {
+ struct snd_pcm_substream *substream = dma_private->substream;
+
+ /* Tell ALSA we completed a period. */
+ snd_pcm_period_elapsed(substream);
+
+ /*
+ * Update our link descriptors to point to the next period. We
+ * only need to do this if the number of periods is not equal to
+ * the number of links.
+ */
+ if (dma_private->num_periods != NUM_DMA_LINKS)
+ fsl_dma_update_pointers(dma_private);
+
+ sr2 |= CCSR_DMA_SR_EOSI;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sr & CCSR_DMA_SR_EOLSI) {
+ sr2 |= CCSR_DMA_SR_EOLSI;
+ ret = IRQ_HANDLED;
+ }
+
+ /* Clear the bits that we set */
+ if (sr2)
+ out_be32(&dma_channel->sr, sr2);
+
+ return ret;
+}
+
+/**
+ * fsl_dma_new: initialize this PCM driver.
+ *
+ * This function is called when the codec driver calls snd_soc_new_pcms(),
+ * once for each .dai_link in the machine driver's snd_soc_machine
+ * structure.
+ */
+static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ static u64 fsl_dma_dmamask = DMA_BIT_MASK(32);
+ int ret;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &fsl_dma_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = fsl_dma_dmamask;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[0].substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev,
+ "Can't allocate playback DMA buffer (size=%u)\n",
+ fsl_dma_hardware.buffer_bytes_max);
+ return -ENOMEM;
+ }
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev,
+ fsl_dma_hardware.buffer_bytes_max,
+ &pcm->streams[1].substream->dma_buffer);
+ if (ret) {
+ snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+ dev_err(card->dev,
+ "Can't allocate capture DMA buffer (size=%u)\n",
+ fsl_dma_hardware.buffer_bytes_max);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_open: open a new substream.
+ *
+ * Each substream has its own DMA buffer.
+ *
+ * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link
+ * descriptors that ping-pong from one period to the next. For example, if
+ * there are six periods and two link descriptors, this is how they look
+ * before playback starts:
+ *
+ * The last link descriptor
+ * ____________ points back to the first
+ * | |
+ * V |
+ * ___ ___ |
+ * | |->| |->|
+ * |___| |___|
+ * | |
+ * | |
+ * V V
+ * _________________________________________
+ * | | | | | | | The DMA buffer is
+ * | | | | | | | divided into 6 parts
+ * |______|______|______|______|______|______|
+ *
+ * and here's how they look after the first period is finished playing:
+ *
+ * ____________
+ * | |
+ * V |
+ * ___ ___ |
+ * | |->| |->|
+ * |___| |___|
+ * | |
+ * |______________
+ * | |
+ * V V
+ * _________________________________________
+ * | | | | | | |
+ * | | | | | | |
+ * |______|______|______|______|______|______|
+ *
+ * The first link descriptor now points to the third period. The DMA
+ * controller is currently playing the second period. When it finishes, it
+ * will jump back to the first descriptor and play the third period.
+ *
+ * There are four reasons we do this:
+ *
+ * 1. The only way to get the DMA controller to automatically restart the
+ * transfer when it gets to the end of the buffer is to use chaining
+ * mode. Basic direct mode doesn't offer that feature.
+ * 2. We need to receive an interrupt at the end of every period. The DMA
+ * controller can generate an interrupt at the end of every link transfer
+ * (aka segment). Making each period into a DMA segment will give us the
+ * interrupts we need.
+ * 3. By creating only two link descriptors, regardless of the number of
+ * periods, we do not need to reallocate the link descriptors if the
+ * number of periods changes.
+ * 4. All of the audio data is still stored in a single, contiguous DMA
+ * buffer, which is what ALSA expects. We're just dividing it into
+ * contiguous parts, and creating a link descriptor for each one.
+ */
+static int fsl_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private;
+ struct ccsr_dma_channel __iomem *dma_channel;
+ dma_addr_t ld_buf_phys;
+ u64 temp_link; /* Pointer to next link descriptor */
+ u32 mr;
+ unsigned int channel;
+ int ret = 0;
+ unsigned int i;
+
+ /*
+ * Reject any DMA buffer whose size is not a multiple of the period
+ * size. We need to make sure that the DMA buffer can be evenly divided
+ * into periods.
+ */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+ return ret;
+ }
+
+ channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ if (dma_global_data.assigned[channel]) {
+ dev_err(substream->pcm->card->dev,
+ "DMA channel already assigned\n");
+ return -EBUSY;
+ }
+
+ dma_private = dma_alloc_coherent(substream->pcm->dev,
+ sizeof(struct fsl_dma_private), &ld_buf_phys, GFP_KERNEL);
+ if (!dma_private) {
+ dev_err(substream->pcm->card->dev,
+ "can't allocate DMA private data\n");
+ return -ENOMEM;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_private->ssi_sxx_phys = dma_global_data.ssi_stx_phys;
+ else
+ dma_private->ssi_sxx_phys = dma_global_data.ssi_srx_phys;
+
+ dma_private->dma_channel = dma_global_data.dma_channel[channel];
+ dma_private->irq = dma_global_data.irq[channel];
+ dma_private->substream = substream;
+ dma_private->ld_buf_phys = ld_buf_phys;
+ dma_private->dma_buf_phys = substream->dma_buffer.addr;
+
+ /* We only support one DMA controller for now */
+ dma_private->controller_id = 0;
+ dma_private->channel_id = channel;
+
+ ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private);
+ if (ret) {
+ dev_err(substream->pcm->card->dev,
+ "can't register ISR for IRQ %u (ret=%i)\n",
+ dma_private->irq, ret);
+ dma_free_coherent(substream->pcm->dev,
+ sizeof(struct fsl_dma_private),
+ dma_private, dma_private->ld_buf_phys);
+ return ret;
+ }
+
+ dma_global_data.assigned[channel] = 1;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
+ runtime->private_data = dma_private;
+
+ /* Program the fixed DMA controller parameters */
+
+ dma_channel = dma_private->dma_channel;
+
+ temp_link = dma_private->ld_buf_phys +
+ sizeof(struct fsl_dma_link_descriptor);
+
+ for (i = 0; i < NUM_DMA_LINKS; i++) {
+ struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+ link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP);
+ link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP);
+ link->next = cpu_to_be64(temp_link);
+
+ temp_link += sizeof(struct fsl_dma_link_descriptor);
+ }
+ /* The last link descriptor points to the first */
+ dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
+
+ /* Tell the DMA controller where the first link descriptor is */
+ out_be32(&dma_channel->clndar,
+ CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
+ out_be32(&dma_channel->eclndar,
+ CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
+
+ /* The manual says the BCR must be clear before enabling EMP */
+ out_be32(&dma_channel->bcr, 0);
+
+ /*
+ * Program the mode register for interrupts, external master control,
+ * and source/destination hold. Also clear the Channel Abort bit.
+ */
+ mr = in_be32(&dma_channel->mr) &
+ ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
+
+ /*
+ * We want External Master Start and External Master Pause enabled,
+ * because the SSI is controlling the DMA controller. We want the DMA
+ * controller to be set up in advance, and then we signal only the SSI
+ * to start transferring.
+ *
+ * We want End-Of-Segment Interrupts enabled, because this will generate
+ * an interrupt at the end of each segment (each link descriptor
+ * represents one segment). Each DMA segment is the same thing as an
+ * ALSA period, so this is how we get an interrupt at the end of every
+ * period.
+ *
+ * We want Error Interrupt enabled, so that we can get an error if
+ * the DMA controller is mis-programmed somehow.
+ */
+ mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
+ CCSR_DMA_MR_EMS_EN;
+
+ /* For playback, we want the destination address to be held. For
+ capture, set the source address to be held. */
+ mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
+
+ out_be32(&dma_channel->mr, mr);
+
+ return 0;
+}
+
+/**
+ * fsl_dma_hw_params: continue initializing the DMA links
+ *
+ * This function obtains hardware parameters about the opened stream and
+ * programs the DMA controller accordingly.
+ *
+ * Note that due to a quirk of the SSI's STX register, the target address
+ * for the DMA operations depends on the sample size. So we don't program
+ * the dest_addr (for playback -- source_addr for capture) fields in the
+ * link descriptors here. We do that in fsl_dma_prepare()
+ */
+static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+
+ dma_addr_t temp_addr; /* Pointer to next period */
+
+ unsigned int i;
+
+ /* Get all the parameters we need */
+ size_t buffer_size = params_buffer_bytes(hw_params);
+ size_t period_size = params_period_bytes(hw_params);
+
+ /* Initialize our DMA tracking variables */
+ dma_private->period_size = period_size;
+ dma_private->num_periods = params_periods(hw_params);
+ dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
+ dma_private->dma_buf_next = dma_private->dma_buf_phys +
+ (NUM_DMA_LINKS * period_size);
+ if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+ dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+ /*
+ * The actual address in STX0 (destination for playback, source for
+ * capture) is based on the sample size, but we don't know the sample
+ * size in this function, so we'll have to adjust that later. See
+ * comments in fsl_dma_prepare().
+ *
+ * The DMA controller does not have a cache, so the CPU does not
+ * need to tell it to flush its cache. However, the DMA
+ * controller does need to tell the CPU to flush its cache.
+ * That's what the SNOOP bit does.
+ *
+ * Also, even though the DMA controller supports 36-bit addressing, for
+ * simplicity we currently support only 32-bit addresses for the audio
+ * buffer itself.
+ */
+ temp_addr = substream->dma_buffer.addr;
+
+ for (i = 0; i < NUM_DMA_LINKS; i++) {
+ struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+ link->count = cpu_to_be32(period_size);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ link->source_addr = cpu_to_be32(temp_addr);
+ else
+ link->dest_addr = cpu_to_be32(temp_addr);
+
+ temp_addr += period_size;
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_prepare - prepare the DMA registers for playback.
+ *
+ * This function is called after the specifics of the audio data are known,
+ * i.e. snd_pcm_runtime is initialized.
+ *
+ * In this function, we finish programming the registers of the DMA
+ * controller that are dependent on the sample size.
+ *
+ * One of the drawbacks with big-endian is that when copying integers of
+ * different sizes to a fixed-sized register, the address to which the
+ * integer must be copied is dependent on the size of the integer.
+ *
+ * For example, if P is the address of a 32-bit register, and X is a 32-bit
+ * integer, then X should be copied to address P. However, if X is a 16-bit
+ * integer, then it should be copied to P+2. If X is an 8-bit register,
+ * then it should be copied to P+3.
+ *
+ * So for playback of 8-bit samples, the DMA controller must transfer single
+ * bytes from the DMA buffer to the last byte of the STX0 register, i.e.
+ * offset by 3 bytes. For 16-bit samples, the offset is two bytes.
+ *
+ * For 24-bit samples, the offset is 1 byte. However, the DMA controller
+ * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
+ * and 8 bytes at a time). So we do not support packed 24-bit samples.
+ * 24-bit data must be padded to 32 bits.
+ */
+static int fsl_dma_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ u32 mr;
+ unsigned int i;
+ dma_addr_t ssi_sxx_phys; /* Bus address of SSI STX register */
+ unsigned int frame_size; /* Number of bytes per frame */
+
+ ssi_sxx_phys = dma_private->ssi_sxx_phys;
+
+ mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
+ CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
+
+ switch (runtime->sample_bits) {
+ case 8:
+ mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
+ ssi_sxx_phys += 3;
+ break;
+ case 16:
+ mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
+ ssi_sxx_phys += 2;
+ break;
+ case 32:
+ mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
+ break;
+ default:
+ dev_err(substream->pcm->card->dev,
+ "unsupported sample size %u\n", runtime->sample_bits);
+ return -EINVAL;
+ }
+
+ frame_size = runtime->frame_bits / 8;
+ /*
+ * BWC should always be a multiple of the frame size. BWC determines
+ * how many bytes are sent/received before the DMA controller checks the
+ * SSI to see if it needs to stop. For playback, the transmit FIFO can
+ * hold three frames, so we want to send two frames at a time. For
+ * capture, the receive FIFO is triggered when it contains one frame, so
+ * we want to receive one frame at a time.
+ */
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mr |= CCSR_DMA_MR_BWC(2 * frame_size);
+ else
+ mr |= CCSR_DMA_MR_BWC(frame_size);
+
+ out_be32(&dma_channel->mr, mr);
+
+ /*
+ * Program the address of the DMA transfer to/from the SSI.
+ */
+ for (i = 0; i < NUM_DMA_LINKS; i++) {
+ struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ link->dest_addr = cpu_to_be32(ssi_sxx_phys);
+ else
+ link->source_addr = cpu_to_be32(ssi_sxx_phys);
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_pointer: determine the current position of the DMA transfer
+ *
+ * This function is called by ALSA when ALSA wants to know where in the
+ * stream buffer the hardware currently is.
+ *
+ * For playback, the SAR register contains the physical address of the most
+ * recent DMA transfer. For capture, the value is in the DAR register.
+ *
+ * The base address of the buffer is stored in the source_addr field of the
+ * first link descriptor.
+ */
+static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+ dma_addr_t position;
+ snd_pcm_uframes_t frames;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ position = in_be32(&dma_channel->sar);
+ else
+ position = in_be32(&dma_channel->dar);
+
+ frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
+
+ /*
+ * If the current address is just past the end of the buffer, wrap it
+ * around.
+ */
+ if (frames == runtime->buffer_size)
+ frames = 0;
+
+ return frames;
+}
+
+/**
+ * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
+ *
+ * Release the resources allocated in fsl_dma_hw_params() and de-program the
+ * registers.
+ *
+ * This function can be called multiple times.
+ */
+static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+
+ if (dma_private) {
+ struct ccsr_dma_channel __iomem *dma_channel;
+
+ dma_channel = dma_private->dma_channel;
+
+ /* Stop the DMA */
+ out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
+ out_be32(&dma_channel->mr, 0);
+
+ /* Reset all the other registers */
+ out_be32(&dma_channel->sr, -1);
+ out_be32(&dma_channel->clndar, 0);
+ out_be32(&dma_channel->eclndar, 0);
+ out_be32(&dma_channel->satr, 0);
+ out_be32(&dma_channel->sar, 0);
+ out_be32(&dma_channel->datr, 0);
+ out_be32(&dma_channel->dar, 0);
+ out_be32(&dma_channel->bcr, 0);
+ out_be32(&dma_channel->nlndar, 0);
+ out_be32(&dma_channel->enlndar, 0);
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_dma_close: close the stream.
+ */
+static int fsl_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ int dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ if (dma_private) {
+ if (dma_private->irq)
+ free_irq(dma_private->irq, dma_private);
+
+ if (dma_private->ld_buf_phys) {
+ dma_unmap_single(substream->pcm->dev,
+ dma_private->ld_buf_phys,
+ sizeof(dma_private->link), DMA_TO_DEVICE);
+ }
+
+ /* Deallocate the fsl_dma_private structure */
+ dma_free_coherent(substream->pcm->dev,
+ sizeof(struct fsl_dma_private),
+ dma_private, dma_private->ld_buf_phys);
+ substream->runtime->private_data = NULL;
+ }
+
+ dma_global_data.assigned[dir] = 0;
+
+ return 0;
+}
+
+/*
+ * Remove this PCM driver.
+ */
+static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+ substream = pcm->streams[i].substream;
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+ }
+}
+
+static struct snd_pcm_ops fsl_dma_ops = {
+ .open = fsl_dma_open,
+ .close = fsl_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fsl_dma_hw_params,
+ .hw_free = fsl_dma_hw_free,
+ .prepare = fsl_dma_prepare,
+ .pointer = fsl_dma_pointer,
+};
+
+struct snd_soc_platform fsl_soc_platform = {
+ .name = "fsl-dma",
+ .pcm_ops = &fsl_dma_ops,
+ .pcm_new = fsl_dma_new,
+ .pcm_free = fsl_dma_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(fsl_soc_platform);
+
+/**
+ * fsl_dma_configure: store the DMA parameters from the fabric driver.
+ *
+ * This function is called by the ASoC fabric driver to give us the DMA and
+ * SSI channel information.
+ *
+ * Unfortunately, ASoC V1 does make it possible to determine the DMA/SSI
+ * data when a substream is created, so for now we need to store this data
+ * into a global variable. This means that we can only support one DMA
+ * controller, and hence only one SSI.
+ */
+int fsl_dma_configure(struct fsl_dma_info *dma_info)
+{
+ static int initialized;
+
+ /* We only support one DMA controller for now */
+ if (initialized)
+ return 0;
+
+ dma_global_data.ssi_stx_phys = dma_info->ssi_stx_phys;
+ dma_global_data.ssi_srx_phys = dma_info->ssi_srx_phys;
+ dma_global_data.dma_channel[0] = dma_info->dma_channel[0];
+ dma_global_data.dma_channel[1] = dma_info->dma_channel[1];
+ dma_global_data.irq[0] = dma_info->dma_irq[0];
+ dma_global_data.irq[1] = dma_info->dma_irq[1];
+ dma_global_data.assigned[0] = 0;
+ dma_global_data.assigned[1] = 0;
+
+ initialized = 1;
+ return 1;
+}
+EXPORT_SYMBOL_GPL(fsl_dma_configure);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
new file mode 100644
index 0000000..385d4a4
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,149 @@
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MPC8610_PCM_H
+#define _MPC8610_PCM_H
+
+struct ccsr_dma {
+ u8 res0[0x100];
+ struct ccsr_dma_channel {
+ __be32 mr; /* Mode register */
+ __be32 sr; /* Status register */
+ __be32 eclndar; /* Current link descriptor extended addr reg */
+ __be32 clndar; /* Current link descriptor address register */
+ __be32 satr; /* Source attributes register */
+ __be32 sar; /* Source address register */
+ __be32 datr; /* Destination attributes register */
+ __be32 dar; /* Destination address register */
+ __be32 bcr; /* Byte count register */
+ __be32 enlndar; /* Next link descriptor extended address reg */
+ __be32 nlndar; /* Next link descriptor address register */
+ u8 res1[4];
+ __be32 eclsdar; /* Current list descriptor extended addr reg */
+ __be32 clsdar; /* Current list descriptor address register */
+ __be32 enlsdar; /* Next list descriptor extended address reg */
+ __be32 nlsdar; /* Next list descriptor address register */
+ __be32 ssr; /* Source stride register */
+ __be32 dsr; /* Destination stride register */
+ u8 res2[0x38];
+ } channel[4];
+ __be32 dgsr;
+};
+
+#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000
+#define CCSR_DMA_MR_BWC_SHIFT 24
+#define CCSR_DMA_MR_BWC_MASK 0x0F000000
+#define CCSR_DMA_MR_BWC(x) \
+ ((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
+#define CCSR_DMA_MR_EMP_EN 0x00200000
+#define CCSR_DMA_MR_EMS_EN 0x00040000
+#define CCSR_DMA_MR_DAHTS_MASK 0x00030000
+#define CCSR_DMA_MR_DAHTS_1 0x00000000
+#define CCSR_DMA_MR_DAHTS_2 0x00010000
+#define CCSR_DMA_MR_DAHTS_4 0x00020000
+#define CCSR_DMA_MR_DAHTS_8 0x00030000
+#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000
+#define CCSR_DMA_MR_SAHTS_1 0x00000000
+#define CCSR_DMA_MR_SAHTS_2 0x00004000
+#define CCSR_DMA_MR_SAHTS_4 0x00008000
+#define CCSR_DMA_MR_SAHTS_8 0x0000C000
+#define CCSR_DMA_MR_DAHE 0x00002000
+#define CCSR_DMA_MR_SAHE 0x00001000
+#define CCSR_DMA_MR_SRW 0x00000400
+#define CCSR_DMA_MR_EOSIE 0x00000200
+#define CCSR_DMA_MR_EOLNIE 0x00000100
+#define CCSR_DMA_MR_EOLSIE 0x00000080
+#define CCSR_DMA_MR_EIE 0x00000040
+#define CCSR_DMA_MR_XFE 0x00000020
+#define CCSR_DMA_MR_CDSM_SWSM 0x00000010
+#define CCSR_DMA_MR_CA 0x00000008
+#define CCSR_DMA_MR_CTM 0x00000004
+#define CCSR_DMA_MR_CC 0x00000002
+#define CCSR_DMA_MR_CS 0x00000001
+
+#define CCSR_DMA_SR_TE 0x00000080
+#define CCSR_DMA_SR_CH 0x00000020
+#define CCSR_DMA_SR_PE 0x00000010
+#define CCSR_DMA_SR_EOLNI 0x00000008
+#define CCSR_DMA_SR_CB 0x00000004
+#define CCSR_DMA_SR_EOSI 0x00000002
+#define CCSR_DMA_SR_EOLSI 0x00000001
+
+/* ECLNDAR takes bits 32-36 of the CLNDAR register */
+static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
+{
+ return (x >> 32) & 0xf;
+}
+
+#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
+#define CCSR_DMA_CLNDAR_EOSIE 0x00000008
+
+/* SATR and DATR, combined */
+#define CCSR_DMA_ATR_PBATMU 0x20000000
+#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000
+#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000
+#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000
+#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000
+#define CCSR_DMA_ATR_PCIORDER 0x02000000
+#define CCSR_DMA_ATR_SME 0x01000000
+#define CCSR_DMA_ATR_NOSNOOP 0x00040000
+#define CCSR_DMA_ATR_SNOOP 0x00050000
+#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F
+
+/**
+ * List Descriptor for extended chaining mode DMA operations.
+ *
+ * The CLSDAR register points to the first (in a linked-list) List
+ * Descriptor. Each object must be aligned on a 32-byte boundary. Each
+ * list descriptor points to a linked-list of link Descriptors.
+ */
+struct fsl_dma_list_descriptor {
+ __be64 next; /* Address of next list descriptor */
+ __be64 first_link; /* Address of first link descriptor */
+ __be32 source; /* Source stride */
+ __be32 dest; /* Destination stride */
+ u8 res[8]; /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/**
+ * Link Descriptor for basic and extended chaining mode DMA operations.
+ *
+ * A Link Descriptor points to a single DMA buffer. Each link descriptor
+ * must be aligned on a 32-byte boundary.
+ */
+struct fsl_dma_link_descriptor {
+ __be32 source_attr; /* Programmed into SATR register */
+ __be32 source_addr; /* Programmed into SAR register */
+ __be32 dest_attr; /* Programmed into DATR register */
+ __be32 dest_addr; /* Programmed into DAR register */
+ __be64 next; /* Address of next link descriptor */
+ __be32 count; /* Byte count */
+ u8 res[4]; /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/* DMA information needed to create a snd_soc_dai object
+ *
+ * ssi_stx_phys: bus address of SSI STX register to use
+ * ssi_srx_phys: bus address of SSI SRX register to use
+ * dma[0]: points to the DMA channel to use for playback
+ * dma[1]: points to the DMA channel to use for capture
+ * dma_irq[0]: IRQ of the DMA channel to use for playback
+ * dma_irq[1]: IRQ of the DMA channel to use for capture
+ */
+struct fsl_dma_info {
+ dma_addr_t ssi_stx_phys;
+ dma_addr_t ssi_srx_phys;
+ struct ccsr_dma_channel __iomem *dma_channel[2];
+ unsigned int dma_irq[2];
+};
+
+extern struct snd_soc_platform fsl_soc_platform;
+
+int fsl_dma_configure(struct fsl_dma_info *dma_info);
+
+#endif
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 0000000..157a789
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,697 @@
+/*
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/immap_86xx.h>
+
+#include "fsl_ssi.h"
+
+/**
+ * FSLSSI_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the SSI running in I2S slave mode,
+ * which means the codec determines the sample rate. Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
+ *
+ * This driver currently only supports the SSI running in I2S slave mode.
+ *
+ * The SSI has a limitation in that the samples must be in the same byte
+ * order as the host CPU. This is because when multiple bytes are written
+ * to the STX register, the bytes and bits must be written in the same
+ * order. The STX is a shift register, so all the bits need to be aligned
+ * (bit-endianness must match byte-endianness). Processors typically write
+ * the bits within a byte in the same order that the bytes of a word are
+ * written in. So if the host CPU is big-endian, then only big-endian
+ * samples will be written to STX properly.
+ */
+#ifdef __BIG_ENDIAN
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE)
+#else
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
+#endif
+
+/**
+ * fsl_ssi_private: per-SSI private data
+ *
+ * @name: short name for this device ("SSI0", "SSI1", etc)
+ * @ssi: pointer to the SSI's registers
+ * @ssi_phys: physical address of the SSI registers
+ * @irq: IRQ of this SSI
+ * @first_stream: pointer to the stream that was opened first
+ * @second_stream: pointer to second stream
+ * @dev: struct device pointer
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @cpu_dai: the CPU DAI for this device
+ * @dev_attr: the sysfs device attribute structure
+ * @stats: SSI statistics
+ */
+struct fsl_ssi_private {
+ char name[8];
+ struct ccsr_ssi __iomem *ssi;
+ dma_addr_t ssi_phys;
+ unsigned int irq;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+ struct device *dev;
+ unsigned int playback;
+ unsigned int capture;
+ struct snd_soc_dai cpu_dai;
+ struct device_attribute dev_attr;
+
+ struct {
+ unsigned int rfrc;
+ unsigned int tfrc;
+ unsigned int cmdau;
+ unsigned int cmddu;
+ unsigned int rxt;
+ unsigned int rdr1;
+ unsigned int rdr0;
+ unsigned int tde1;
+ unsigned int tde0;
+ unsigned int roe1;
+ unsigned int roe0;
+ unsigned int tue1;
+ unsigned int tue0;
+ unsigned int tfs;
+ unsigned int rfs;
+ unsigned int tls;
+ unsigned int rls;
+ unsigned int rff1;
+ unsigned int rff0;
+ unsigned int tfe1;
+ unsigned int tfe0;
+ } stats;
+};
+
+/**
+ * fsl_ssi_isr: SSI interrupt handler
+ *
+ * Although it's possible to use the interrupt handler to send and receive
+ * data to/from the SSI, we use the DMA instead. Programming is more
+ * complicated, but the performance is much better.
+ *
+ * This interrupt handler is used only to gather statistics.
+ *
+ * @irq: IRQ of the SSI device
+ * @dev_id: pointer to the ssi_private structure for this SSI device
+ */
+static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
+{
+ struct fsl_ssi_private *ssi_private = dev_id;
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ irqreturn_t ret = IRQ_NONE;
+ __be32 sisr;
+ __be32 sisr2 = 0;
+
+ /* We got an interrupt, so read the status register to see what we
+ were interrupted for. We mask it with the Interrupt Enable register
+ so that we only check for events that we're interested in.
+ */
+ sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier);
+
+ if (sisr & CCSR_SSI_SISR_RFRC) {
+ ssi_private->stats.rfrc++;
+ sisr2 |= CCSR_SSI_SISR_RFRC;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFRC) {
+ ssi_private->stats.tfrc++;
+ sisr2 |= CCSR_SSI_SISR_TFRC;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_CMDAU) {
+ ssi_private->stats.cmdau++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_CMDDU) {
+ ssi_private->stats.cmddu++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RXT) {
+ ssi_private->stats.rxt++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RDR1) {
+ ssi_private->stats.rdr1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RDR0) {
+ ssi_private->stats.rdr0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TDE1) {
+ ssi_private->stats.tde1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TDE0) {
+ ssi_private->stats.tde0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_ROE1) {
+ ssi_private->stats.roe1++;
+ sisr2 |= CCSR_SSI_SISR_ROE1;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_ROE0) {
+ ssi_private->stats.roe0++;
+ sisr2 |= CCSR_SSI_SISR_ROE0;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TUE1) {
+ ssi_private->stats.tue1++;
+ sisr2 |= CCSR_SSI_SISR_TUE1;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TUE0) {
+ ssi_private->stats.tue0++;
+ sisr2 |= CCSR_SSI_SISR_TUE0;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFS) {
+ ssi_private->stats.tfs++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFS) {
+ ssi_private->stats.rfs++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TLS) {
+ ssi_private->stats.tls++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RLS) {
+ ssi_private->stats.rls++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFF1) {
+ ssi_private->stats.rff1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_RFF0) {
+ ssi_private->stats.rff0++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFE1) {
+ ssi_private->stats.tfe1++;
+ ret = IRQ_HANDLED;
+ }
+
+ if (sisr & CCSR_SSI_SISR_TFE0) {
+ ssi_private->stats.tfe0++;
+ ret = IRQ_HANDLED;
+ }
+
+ /* Clear the bits that we set */
+ if (sisr2)
+ out_be32(&ssi->sisr, sisr2);
+
+ return ret;
+}
+
+/**
+ * fsl_ssi_startup: create a new substream
+ *
+ * This is the first function called when a stream is opened.
+ *
+ * If this is the first stream open, then grab the IRQ and program most of
+ * the SSI registers.
+ */
+static int fsl_ssi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+ /*
+ * If this is the first stream opened, then request the IRQ
+ * and initialize the SSI registers.
+ */
+ if (!ssi_private->playback && !ssi_private->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ int ret;
+
+ ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
+ ssi_private->name, ssi_private);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not claim irq %u\n", ssi_private->irq);
+ return ret;
+ }
+
+ /*
+ * Section 16.5 of the MPC8610 reference manual says that the
+ * SSI needs to be disabled before updating the registers we set
+ * here.
+ */
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+ /*
+ * Program the SSI into I2S Slave Non-Network Synchronous mode.
+ * Also enable the transmit and receive FIFO.
+ *
+ * FIXME: Little-endian samples require a different shift dir
+ */
+ clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK,
+ CCSR_SSI_SCR_TFR_CLK_DIS |
+ CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN);
+
+ out_be32(&ssi->stcr,
+ CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
+ CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
+ CCSR_SSI_STCR_TSCKP);
+
+ out_be32(&ssi->srcr,
+ CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
+ CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
+ CCSR_SSI_SRCR_RSCKP);
+
+ /*
+ * The DC and PM bits are only used if the SSI is the clock
+ * master.
+ */
+
+ /* 4. Enable the interrupts and DMA requests */
+ out_be32(&ssi->sier,
+ CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE |
+ CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN |
+ CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN |
+ CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE |
+ CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN);
+
+ /*
+ * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
+ * don't use FIFO 1. Since the SSI only supports stereo, the
+ * watermark should never be an odd number.
+ */
+ out_be32(&ssi->sfcsr,
+ CCSR_SSI_SFCSR_TFWM0(6) | CCSR_SSI_SFCSR_RFWM0(2));
+
+ /*
+ * We keep the SSI disabled because if we enable it, then the
+ * DMA controller will start. It's not supposed to start until
+ * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The
+ * DMA controller will transfer one "BWC" of data (i.e. the
+ * amount of data that the MR.BWC bits are set to). The reason
+ * this is bad is because at this point, the PCM driver has not
+ * finished initializing the DMA controller.
+ */
+ }
+
+ if (!ssi_private->first_stream)
+ ssi_private->first_stream = substream;
+ else {
+ /* This is the second stream open, so we need to impose sample
+ * rate and maybe sample size constraints. Note that this can
+ * cause a race condition if the second stream is opened before
+ * the first stream is fully initialized.
+ *
+ * We provide some protection by checking to make sure the first
+ * stream is initialized, but it's not perfect. ALSA sometimes
+ * re-initializes the driver with a different sample rate or
+ * size. If the second stream is opened before the first stream
+ * has received its final parameters, then the second stream may
+ * be constrained to the wrong sample rate or size.
+ *
+ * FIXME: This code does not handle opening and closing streams
+ * repeatedly. If you open two streams and then close the first
+ * one, you may not be able to open another stream until you
+ * close the second one as well.
+ */
+ struct snd_pcm_runtime *first_runtime =
+ ssi_private->first_stream->runtime;
+
+ if (!first_runtime->rate || !first_runtime->sample_bits) {
+ dev_err(substream->pcm->card->dev,
+ "set sample rate and size in %s stream first\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ ? "capture" : "playback");
+ return -EAGAIN;
+ }
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ first_runtime->rate, first_runtime->rate);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ first_runtime->sample_bits,
+ first_runtime->sample_bits);
+
+ ssi_private->second_stream = substream;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ssi_private->playback++;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ssi_private->capture++;
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_prepare: prepare the SSI.
+ *
+ * Most of the SSI registers have been programmed in the startup function,
+ * but the word length must be programmed here. Unfortunately, programming
+ * the SxCCR.WL bits requires the SSI to be temporarily disabled. This can
+ * cause a problem with supporting simultaneous playback and capture. If
+ * the SSI is already playing a stream, then that stream may be temporarily
+ * stopped when you start capture.
+ *
+ * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
+ * clock master.
+ */
+static int fsl_ssi_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ if (substream == ssi_private->first_stream) {
+ u32 wl;
+
+ /* The SSI should always be disabled at this points (SSIEN=0) */
+ wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format));
+
+ /* In synchronous mode, the SSI uses STCCR for capture */
+ clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ *
+ * The DMA channel is in external master start and pause mode, which
+ * means the SSI completely controls the flow of data.
+ */
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+ setbits32(&ssi->scr,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+ } else {
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+ setbits32(&ssi->scr,
+ CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
+
+ /*
+ * I think we need this delay to allow time for the SSI
+ * to put data into its FIFO. Without it, ALSA starts
+ * to complain about overruns.
+ */
+ mdelay(1);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_TE);
+ else
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_RE);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_ssi_shutdown: shutdown the SSI
+ *
+ * Shutdown the SSI if there are no other substreams open.
+ */
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ssi_private->playback--;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ssi_private->capture--;
+
+ if (ssi_private->first_stream == substream)
+ ssi_private->first_stream = ssi_private->second_stream;
+
+ ssi_private->second_stream = NULL;
+
+ /*
+ * If this is the last active substream, disable the SSI and release
+ * the IRQ.
+ */
+ if (!ssi_private->playback && !ssi_private->capture) {
+ struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+ clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+ free_irq(ssi_private->irq, ssi_private);
+ }
+}
+
+/**
+ * fsl_ssi_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency. Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int fsl_ssi_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+
+ return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * fsl_ssi_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * Currently, we only support I2S mode. Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
+{
+ return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/**
+ * fsl_ssi_dai_template: template CPU DAI for the SSI
+ */
+static struct snd_soc_dai fsl_ssi_dai_template = {
+ .playback = {
+ /* The SSI does not support monaural audio. */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSLSSI_I2S_RATES,
+ .formats = FSLSSI_I2S_FORMATS,
+ },
+ .ops = {
+ .startup = fsl_ssi_startup,
+ .prepare = fsl_ssi_prepare,
+ .shutdown = fsl_ssi_shutdown,
+ .trigger = fsl_ssi_trigger,
+ },
+ .dai_ops = {
+ .set_sysclk = fsl_ssi_set_sysclk,
+ .set_fmt = fsl_ssi_set_fmt,
+ },
+};
+
+/**
+ * fsl_sysfs_ssi_show: display SSI statistics
+ *
+ * Display the statistics for the current SSI device.
+ */
+static ssize_t fsl_sysfs_ssi_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsl_ssi_private *ssi_private =
+ container_of(attr, struct fsl_ssi_private, dev_attr);
+ ssize_t length;
+
+ length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc);
+ length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc);
+ length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau);
+ length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu);
+ length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt);
+ length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1);
+ length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0);
+ length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1);
+ length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0);
+ length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1);
+ length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0);
+ length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1);
+ length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0);
+ length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs);
+ length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs);
+ length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls);
+ length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls);
+ length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1);
+ length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0);
+ length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1);
+ length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0);
+
+ return length;
+}
+
+/**
+ * fsl_ssi_create_dai: create a snd_soc_dai structure
+ *
+ * This function is called by the machine driver to create a snd_soc_dai
+ * structure. The function creates an ssi_private object, which contains
+ * the snd_soc_dai. It also creates the sysfs statistics device.
+ */
+struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
+{
+ struct snd_soc_dai *fsl_ssi_dai;
+ struct fsl_ssi_private *ssi_private;
+ int ret = 0;
+ struct device_attribute *dev_attr;
+
+ ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL);
+ if (!ssi_private) {
+ dev_err(ssi_info->dev, "could not allocate DAI object\n");
+ return NULL;
+ }
+ memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template,
+ sizeof(struct snd_soc_dai));
+
+ fsl_ssi_dai = &ssi_private->cpu_dai;
+ dev_attr = &ssi_private->dev_attr;
+
+ sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id);
+ ssi_private->ssi = ssi_info->ssi;
+ ssi_private->ssi_phys = ssi_info->ssi_phys;
+ ssi_private->irq = ssi_info->irq;
+ ssi_private->dev = ssi_info->dev;
+
+ ssi_private->dev->driver_data = fsl_ssi_dai;
+
+ /* Initialize the the device_attribute structure */
+ dev_attr->attr.name = "ssi-stats";
+ dev_attr->attr.mode = S_IRUGO;
+ dev_attr->show = fsl_sysfs_ssi_show;
+
+ ret = device_create_file(ssi_private->dev, dev_attr);
+ if (ret) {
+ dev_err(ssi_info->dev, "could not create sysfs %s file\n",
+ ssi_private->dev_attr.attr.name);
+ kfree(fsl_ssi_dai);
+ return NULL;
+ }
+
+ fsl_ssi_dai->private_data = ssi_private;
+ fsl_ssi_dai->name = ssi_private->name;
+ fsl_ssi_dai->id = ssi_info->id;
+
+ return fsl_ssi_dai;
+}
+EXPORT_SYMBOL_GPL(fsl_ssi_create_dai);
+
+/**
+ * fsl_ssi_destroy_dai: destroy the snd_soc_dai object
+ *
+ * This function undoes the operations of fsl_ssi_create_dai()
+ */
+void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai)
+{
+ struct fsl_ssi_private *ssi_private =
+ container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai);
+
+ device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
+
+ kfree(ssi_private);
+}
+EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
new file mode 100644
index 0000000..83b44d7
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,224 @@
+/*
+ * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#ifndef _MPC8610_I2S_H
+#define _MPC8610_I2S_H
+
+/* SSI Register Map */
+struct ccsr_ssi {
+ __be32 stx0; /* 0x.0000 - SSI Transmit Data Register 0 */
+ __be32 stx1; /* 0x.0004 - SSI Transmit Data Register 1 */
+ __be32 srx0; /* 0x.0008 - SSI Receive Data Register 0 */
+ __be32 srx1; /* 0x.000C - SSI Receive Data Register 1 */
+ __be32 scr; /* 0x.0010 - SSI Control Register */
+ __be32 sisr; /* 0x.0014 - SSI Interrupt Status Register Mixed */
+ __be32 sier; /* 0x.0018 - SSI Interrupt Enable Register */
+ __be32 stcr; /* 0x.001C - SSI Transmit Configuration Register */
+ __be32 srcr; /* 0x.0020 - SSI Receive Configuration Register */
+ __be32 stccr; /* 0x.0024 - SSI Transmit Clock Control Register */
+ __be32 srccr; /* 0x.0028 - SSI Receive Clock Control Register */
+ __be32 sfcsr; /* 0x.002C - SSI FIFO Control/Status Register */
+ __be32 str; /* 0x.0030 - SSI Test Register */
+ __be32 sor; /* 0x.0034 - SSI Option Register */
+ __be32 sacnt; /* 0x.0038 - SSI AC97 Control Register */
+ __be32 sacadd; /* 0x.003C - SSI AC97 Command Address Register */
+ __be32 sacdat; /* 0x.0040 - SSI AC97 Command Data Register */
+ __be32 satag; /* 0x.0044 - SSI AC97 Tag Register */
+ __be32 stmsk; /* 0x.0048 - SSI Transmit Time Slot Mask Register */
+ __be32 srmsk; /* 0x.004C - SSI Receive Time Slot Mask Register */
+ __be32 saccst; /* 0x.0050 - SSI AC97 Channel Status Register */
+ __be32 saccen; /* 0x.0054 - SSI AC97 Channel Enable Register */
+ __be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
+};
+
+#define CCSR_SSI_SCR_RFR_CLK_DIS 0x00000800
+#define CCSR_SSI_SCR_TFR_CLK_DIS 0x00000400
+#define CCSR_SSI_SCR_TCH_EN 0x00000100
+#define CCSR_SSI_SCR_SYS_CLK_EN 0x00000080
+#define CCSR_SSI_SCR_I2S_MODE_MASK 0x00000060
+#define CCSR_SSI_SCR_I2S_MODE_NORMAL 0x00000000
+#define CCSR_SSI_SCR_I2S_MODE_MASTER 0x00000020
+#define CCSR_SSI_SCR_I2S_MODE_SLAVE 0x00000040
+#define CCSR_SSI_SCR_SYN 0x00000010
+#define CCSR_SSI_SCR_NET 0x00000008
+#define CCSR_SSI_SCR_RE 0x00000004
+#define CCSR_SSI_SCR_TE 0x00000002
+#define CCSR_SSI_SCR_SSIEN 0x00000001
+
+#define CCSR_SSI_SISR_RFRC 0x01000000
+#define CCSR_SSI_SISR_TFRC 0x00800000
+#define CCSR_SSI_SISR_CMDAU 0x00040000
+#define CCSR_SSI_SISR_CMDDU 0x00020000
+#define CCSR_SSI_SISR_RXT 0x00010000
+#define CCSR_SSI_SISR_RDR1 0x00008000
+#define CCSR_SSI_SISR_RDR0 0x00004000
+#define CCSR_SSI_SISR_TDE1 0x00002000
+#define CCSR_SSI_SISR_TDE0 0x00001000
+#define CCSR_SSI_SISR_ROE1 0x00000800
+#define CCSR_SSI_SISR_ROE0 0x00000400
+#define CCSR_SSI_SISR_TUE1 0x00000200
+#define CCSR_SSI_SISR_TUE0 0x00000100
+#define CCSR_SSI_SISR_TFS 0x00000080
+#define CCSR_SSI_SISR_RFS 0x00000040
+#define CCSR_SSI_SISR_TLS 0x00000020
+#define CCSR_SSI_SISR_RLS 0x00000010
+#define CCSR_SSI_SISR_RFF1 0x00000008
+#define CCSR_SSI_SISR_RFF0 0x00000004
+#define CCSR_SSI_SISR_TFE1 0x00000002
+#define CCSR_SSI_SISR_TFE0 0x00000001
+
+#define CCSR_SSI_SIER_RFRC_EN 0x01000000
+#define CCSR_SSI_SIER_TFRC_EN 0x00800000
+#define CCSR_SSI_SIER_RDMAE 0x00400000
+#define CCSR_SSI_SIER_RIE 0x00200000
+#define CCSR_SSI_SIER_TDMAE 0x00100000
+#define CCSR_SSI_SIER_TIE 0x00080000
+#define CCSR_SSI_SIER_CMDAU_EN 0x00040000
+#define CCSR_SSI_SIER_CMDDU_EN 0x00020000
+#define CCSR_SSI_SIER_RXT_EN 0x00010000
+#define CCSR_SSI_SIER_RDR1_EN 0x00008000
+#define CCSR_SSI_SIER_RDR0_EN 0x00004000
+#define CCSR_SSI_SIER_TDE1_EN 0x00002000
+#define CCSR_SSI_SIER_TDE0_EN 0x00001000
+#define CCSR_SSI_SIER_ROE1_EN 0x00000800
+#define CCSR_SSI_SIER_ROE0_EN 0x00000400
+#define CCSR_SSI_SIER_TUE1_EN 0x00000200
+#define CCSR_SSI_SIER_TUE0_EN 0x00000100
+#define CCSR_SSI_SIER_TFS_EN 0x00000080
+#define CCSR_SSI_SIER_RFS_EN 0x00000040
+#define CCSR_SSI_SIER_TLS_EN 0x00000020
+#define CCSR_SSI_SIER_RLS_EN 0x00000010
+#define CCSR_SSI_SIER_RFF1_EN 0x00000008
+#define CCSR_SSI_SIER_RFF0_EN 0x00000004
+#define CCSR_SSI_SIER_TFE1_EN 0x00000002
+#define CCSR_SSI_SIER_TFE0_EN 0x00000001
+
+#define CCSR_SSI_STCR_TXBIT0 0x00000200
+#define CCSR_SSI_STCR_TFEN1 0x00000100
+#define CCSR_SSI_STCR_TFEN0 0x00000080
+#define CCSR_SSI_STCR_TFDIR 0x00000040
+#define CCSR_SSI_STCR_TXDIR 0x00000020
+#define CCSR_SSI_STCR_TSHFD 0x00000010
+#define CCSR_SSI_STCR_TSCKP 0x00000008
+#define CCSR_SSI_STCR_TFSI 0x00000004
+#define CCSR_SSI_STCR_TFSL 0x00000002
+#define CCSR_SSI_STCR_TEFS 0x00000001
+
+#define CCSR_SSI_SRCR_RXEXT 0x00000400
+#define CCSR_SSI_SRCR_RXBIT0 0x00000200
+#define CCSR_SSI_SRCR_RFEN1 0x00000100
+#define CCSR_SSI_SRCR_RFEN0 0x00000080
+#define CCSR_SSI_SRCR_RFDIR 0x00000040
+#define CCSR_SSI_SRCR_RXDIR 0x00000020
+#define CCSR_SSI_SRCR_RSHFD 0x00000010
+#define CCSR_SSI_SRCR_RSCKP 0x00000008
+#define CCSR_SSI_SRCR_RFSI 0x00000004
+#define CCSR_SSI_SRCR_RFSL 0x00000002
+#define CCSR_SSI_SRCR_REFS 0x00000001
+
+/* STCCR and SRCCR */
+#define CCSR_SSI_SxCCR_DIV2 0x00040000
+#define CCSR_SSI_SxCCR_PSR 0x00020000
+#define CCSR_SSI_SxCCR_WL_SHIFT 13
+#define CCSR_SSI_SxCCR_WL_MASK 0x0001E000
+#define CCSR_SSI_SxCCR_WL(x) \
+ (((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK)
+#define CCSR_SSI_SxCCR_DC_SHIFT 8
+#define CCSR_SSI_SxCCR_DC_MASK 0x00001F00
+#define CCSR_SSI_SxCCR_DC(x) \
+ ((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK)
+#define CCSR_SSI_SxCCR_PM_SHIFT 0
+#define CCSR_SSI_SxCCR_PM_MASK 0x000000FF
+#define CCSR_SSI_SxCCR_PM(x) \
+ ((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK)
+
+/*
+ * The xFCNT bits are read-only, and the xFWM bits are read/write. Use the
+ * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the
+ * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks.
+ */
+#define CCSR_SSI_SFCSR_RFCNT1_SHIFT 28
+#define CCSR_SSI_SFCSR_RFCNT1_MASK 0xF0000000
+#define CCSR_SSI_SFCSR_RFCNT1(x) \
+ (((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT1_SHIFT 24
+#define CCSR_SSI_SFCSR_TFCNT1_MASK 0x0F000000
+#define CCSR_SSI_SFCSR_TFCNT1(x) \
+ (((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM1_SHIFT 20
+#define CCSR_SSI_SFCSR_RFWM1_MASK 0x00F00000
+#define CCSR_SSI_SFCSR_RFWM1(x) \
+ (((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK)
+#define CCSR_SSI_SFCSR_TFWM1_SHIFT 16
+#define CCSR_SSI_SFCSR_TFWM1_MASK 0x000F0000
+#define CCSR_SSI_SFCSR_TFWM1(x) \
+ (((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK)
+#define CCSR_SSI_SFCSR_RFCNT0_SHIFT 12
+#define CCSR_SSI_SFCSR_RFCNT0_MASK 0x0000F000
+#define CCSR_SSI_SFCSR_RFCNT0(x) \
+ (((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT0_SHIFT 8
+#define CCSR_SSI_SFCSR_TFCNT0_MASK 0x00000F00
+#define CCSR_SSI_SFCSR_TFCNT0(x) \
+ (((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM0_SHIFT 4
+#define CCSR_SSI_SFCSR_RFWM0_MASK 0x000000F0
+#define CCSR_SSI_SFCSR_RFWM0(x) \
+ (((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK)
+#define CCSR_SSI_SFCSR_TFWM0_SHIFT 0
+#define CCSR_SSI_SFCSR_TFWM0_MASK 0x0000000F
+#define CCSR_SSI_SFCSR_TFWM0(x) \
+ (((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK)
+
+#define CCSR_SSI_STR_TEST 0x00008000
+#define CCSR_SSI_STR_RCK2TCK 0x00004000
+#define CCSR_SSI_STR_RFS2TFS 0x00002000
+#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F)
+#define CCSR_SSI_STR_TXD2RXD 0x00000080
+#define CCSR_SSI_STR_TCK2RCK 0x00000040
+#define CCSR_SSI_STR_TFS2RFS 0x00000020
+#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F)
+
+#define CCSR_SSI_SOR_CLKOFF 0x00000040
+#define CCSR_SSI_SOR_RX_CLR 0x00000020
+#define CCSR_SSI_SOR_TX_CLR 0x00000010
+#define CCSR_SSI_SOR_INIT 0x00000008
+#define CCSR_SSI_SOR_WAIT_SHIFT 1
+#define CCSR_SSI_SOR_WAIT_MASK 0x00000006
+#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
+#define CCSR_SSI_SOR_SYNRST 0x00000001
+
+/* Instantiation data for an SSI interface
+ *
+ * This structure contains all the information that the the SSI driver needs
+ * to instantiate an SSI interface with ALSA. The machine driver should
+ * create this structure, fill it in, call fsl_ssi_create_dai(), and then
+ * delete the structure.
+ *
+ * id: which SSI this is (0, 1, etc. )
+ * ssi: pointer to the SSI's registers
+ * ssi_phys: physical address of the SSI registers
+ * irq: IRQ of this SSI
+ * dev: struct device, used to create the sysfs statistics file
+*/
+struct fsl_ssi_info {
+ unsigned int id;
+ struct ccsr_ssi __iomem *ssi;
+ dma_addr_t ssi_phys;
+ unsigned int irq;
+ struct device *dev;
+};
+
+struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info);
+void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai);
+
+#endif
+
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
new file mode 100644
index 0000000..94a02ea
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,886 @@
+/*
+ * Freescale MPC5200 PSC in I2S mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-of-simple.h>
+
+#include <sysdev/bestcomm/bestcomm.h>
+#include <sysdev/bestcomm/gen_bd.h>
+#include <asm/mpc52xx_psc.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
+MODULE_LICENSE("GPL");
+
+/**
+ * PSC_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the PSC running in I2S slave mode,
+ * which means the codec determines the sample rate. Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+ SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
+ */
+#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \
+ SNDRV_PCM_FMTBIT_S32_BE)
+
+/**
+ * psc_i2s_stream - Data specific to a single stream (playback or capture)
+ * @active: flag indicating if the stream is active
+ * @psc_i2s: pointer back to parent psc_i2s data structure
+ * @bcom_task: bestcomm task structure
+ * @irq: irq number for bestcomm task
+ * @period_start: physical address of start of DMA region
+ * @period_end: physical address of end of DMA region
+ * @period_next_pt: physical address of next DMA buffer to enqueue
+ * @period_bytes: size of DMA period in bytes
+ */
+struct psc_i2s_stream {
+ int active;
+ struct psc_i2s *psc_i2s;
+ struct bcom_task *bcom_task;
+ int irq;
+ struct snd_pcm_substream *stream;
+ dma_addr_t period_start;
+ dma_addr_t period_end;
+ dma_addr_t period_next_pt;
+ dma_addr_t period_current_pt;
+ int period_bytes;
+};
+
+/**
+ * psc_i2s - Private driver data
+ * @name: short name for this device ("PSC0", "PSC1", etc)
+ * @psc_regs: pointer to the PSC's registers
+ * @fifo_regs: pointer to the PSC's FIFO registers
+ * @irq: IRQ of this PSC
+ * @dev: struct device pointer
+ * @dai: the CPU DAI for this device
+ * @sicr: Base value used in serial interface control register; mode is ORed
+ * with this value.
+ * @playback: Playback stream context data
+ * @capture: Capture stream context data
+ */
+struct psc_i2s {
+ char name[32];
+ struct mpc52xx_psc __iomem *psc_regs;
+ struct mpc52xx_psc_fifo __iomem *fifo_regs;
+ unsigned int irq;
+ struct device *dev;
+ struct snd_soc_dai dai;
+ spinlock_t lock;
+ u32 sicr;
+
+ /* per-stream data */
+ struct psc_i2s_stream playback;
+ struct psc_i2s_stream capture;
+
+ /* Statistics */
+ struct {
+ int overrun_count;
+ int underrun_count;
+ } stats;
+};
+
+/*
+ * Interrupt handlers
+ */
+static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s)
+{
+ struct psc_i2s *psc_i2s = _psc_i2s;
+ struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
+ u16 isr;
+
+ isr = in_be16(&regs->mpc52xx_psc_isr);
+
+ /* Playback underrun error */
+ if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
+ psc_i2s->stats.underrun_count++;
+
+ /* Capture overrun error */
+ if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
+ psc_i2s->stats.overrun_count++;
+
+ out_8(&regs->command, 4 << 4); /* reset the error status */
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer
+ * @s: pointer to stream private data structure
+ *
+ * Enqueues another audio period buffer into the bestcomm queue.
+ *
+ * Note: The routine must only be called when there is space available in
+ * the queue. Otherwise the enqueue will fail and the audio ring buffer
+ * will get out of sync
+ */
+static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s)
+{
+ struct bcom_bd *bd;
+
+ /* Prepare and enqueue the next buffer descriptor */
+ bd = bcom_prepare_next_buffer(s->bcom_task);
+ bd->status = s->period_bytes;
+ bd->data[0] = s->period_next_pt;
+ bcom_submit_next_buffer(s->bcom_task, NULL);
+
+ /* Update for next period */
+ s->period_next_pt += s->period_bytes;
+ if (s->period_next_pt >= s->period_end)
+ s->period_next_pt = s->period_start;
+}
+
+/* Bestcomm DMA irq handler */
+static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream)
+{
+ struct psc_i2s_stream *s = _psc_i2s_stream;
+
+ /* For each finished period, dequeue the completed period buffer
+ * and enqueue a new one in it's place. */
+ while (bcom_buffer_done(s->bcom_task)) {
+ bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+ s->period_current_pt += s->period_bytes;
+ if (s->period_current_pt >= s->period_end)
+ s->period_current_pt = s->period_start;
+ psc_i2s_bcom_enqueue_next_buffer(s);
+ bcom_enable(s->bcom_task);
+ }
+
+ /* If the stream is active, then also inform the PCM middle layer
+ * of the period finished event. */
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * psc_i2s_startup: create a new substream
+ *
+ * This is the first function called when a stream is opened.
+ *
+ * If this is the first stream open, then grab the IRQ and program most of
+ * the PSC registers.
+ */
+static int psc_i2s_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ int rc;
+
+ dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream);
+
+ if (!psc_i2s->playback.active &&
+ !psc_i2s->capture.active) {
+ /* Setup the IRQs */
+ rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED,
+ "psc-i2s-status", psc_i2s);
+ rc |= request_irq(psc_i2s->capture.irq,
+ &psc_i2s_bcom_irq, IRQF_SHARED,
+ "psc-i2s-capture", &psc_i2s->capture);
+ rc |= request_irq(psc_i2s->playback.irq,
+ &psc_i2s_bcom_irq, IRQF_SHARED,
+ "psc-i2s-playback", &psc_i2s->playback);
+ if (rc) {
+ free_irq(psc_i2s->irq, psc_i2s);
+ free_irq(psc_i2s->capture.irq,
+ &psc_i2s->capture);
+ free_irq(psc_i2s->playback.irq,
+ &psc_i2s->playback);
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ u32 mode;
+
+ dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+ " periods=%i buffer_size=%i buffer_bytes=%i\n",
+ __func__, substream, params_period_size(params),
+ params_period_bytes(params), params_periods(params),
+ params_buffer_size(params), params_buffer_bytes(params));
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_BE:
+ mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
+ break;
+ default:
+ dev_dbg(psc_i2s->dev, "invalid format\n");
+ return -EINVAL;
+ }
+ out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int psc_i2s_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+/**
+ * psc_i2s_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct psc_i2s_stream *s;
+ struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
+ u16 imr;
+ u8 psc_cmd;
+ unsigned long flags;
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_i2s->capture;
+ else
+ s = &psc_i2s->playback;
+
+ dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)"
+ " stream_id=%i\n",
+ substream, cmd, substream->pstr->stream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ s->period_bytes = frames_to_bytes(runtime,
+ runtime->period_size);
+ s->period_start = virt_to_phys(runtime->dma_area);
+ s->period_end = s->period_start +
+ (s->period_bytes * runtime->periods);
+ s->period_next_pt = s->period_start;
+ s->period_current_pt = s->period_start;
+ s->active = 1;
+
+ /* First; reset everything */
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ out_8(&regs->command, MPC52xx_PSC_RST_RX);
+ out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+ } else {
+ out_8(&regs->command, MPC52xx_PSC_RST_TX);
+ out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+ }
+
+ /* Next, fill up the bestcomm bd queue and enable DMA.
+ * This will begin filling the PSC's fifo. */
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ bcom_gen_bd_rx_reset(s->bcom_task);
+ else
+ bcom_gen_bd_tx_reset(s->bcom_task);
+ while (!bcom_queue_full(s->bcom_task))
+ psc_i2s_bcom_enqueue_next_buffer(s);
+ bcom_enable(s->bcom_task);
+
+ /* Due to errata in the i2s mode; need to line up enabling
+ * the transmitter with a transition on the frame sync
+ * line */
+
+ spin_lock_irqsave(&psc_i2s->lock, flags);
+ /* first make sure it is low */
+ while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) != 0)
+ ;
+ /* then wait for the transition to high */
+ while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) == 0)
+ ;
+ /* Finally, enable the PSC.
+ * Receiver must always be enabled; even when we only want
+ * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
+ psc_cmd = MPC52xx_PSC_RX_ENABLE;
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ psc_cmd |= MPC52xx_PSC_TX_ENABLE;
+ out_8(&regs->command, psc_cmd);
+ spin_unlock_irqrestore(&psc_i2s->lock, flags);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* Turn off the PSC */
+ s->active = 0;
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ if (!psc_i2s->playback.active) {
+ out_8(&regs->command, 2 << 4); /* reset rx */
+ out_8(&regs->command, 3 << 4); /* reset tx */
+ out_8(&regs->command, 4 << 4); /* reset err */
+ }
+ } else {
+ out_8(&regs->command, 3 << 4); /* reset tx */
+ out_8(&regs->command, 4 << 4); /* reset err */
+ if (!psc_i2s->capture.active)
+ out_8(&regs->command, 2 << 4); /* reset rx */
+ }
+
+ bcom_disable(s->bcom_task);
+ while (!bcom_queue_empty(s->bcom_task))
+ bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+
+ break;
+
+ default:
+ dev_dbg(psc_i2s->dev, "invalid command\n");
+ return -EINVAL;
+ }
+
+ /* Update interrupt enable settings */
+ imr = 0;
+ if (psc_i2s->playback.active)
+ imr |= MPC52xx_PSC_IMR_TXEMP;
+ if (psc_i2s->capture.active)
+ imr |= MPC52xx_PSC_IMR_ORERR;
+ out_be16(&regs->isr_imr.imr, imr);
+
+ return 0;
+}
+
+/**
+ * psc_i2s_shutdown: shutdown the data transfer on a stream
+ *
+ * Shutdown the PSC if there are no other substreams open.
+ */
+static void psc_i2s_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+
+ dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream);
+
+ /*
+ * If this is the last active substream, disable the PSC and release
+ * the IRQ.
+ */
+ if (!psc_i2s->playback.active &&
+ !psc_i2s->capture.active) {
+
+ /* Disable all interrupts and reset the PSC */
+ out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
+ out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */
+ out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */
+ out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
+ out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
+
+ /* Release irqs */
+ free_irq(psc_i2s->irq, psc_i2s);
+ free_irq(psc_i2s->capture.irq, &psc_i2s->capture);
+ free_irq(psc_i2s->playback.irq, &psc_i2s->playback);
+ }
+}
+
+/**
+ * psc_i2s_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency. Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct psc_i2s *psc_i2s = cpu_dai->private_data;
+ dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
+ cpu_dai, dir);
+ return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * psc_i2s_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * This driver only supports I2S mode. Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
+{
+ struct psc_i2s *psc_i2s = cpu_dai->private_data;
+ dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
+ cpu_dai, format);
+ return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_i2s_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_dai psc_i2s_dai_template = {
+ .type = SND_SOC_DAI_I2S,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PSC_I2S_RATES,
+ .formats = PSC_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PSC_I2S_RATES,
+ .formats = PSC_I2S_FORMATS,
+ },
+ .ops = {
+ .startup = psc_i2s_startup,
+ .hw_params = psc_i2s_hw_params,
+ .hw_free = psc_i2s_hw_free,
+ .shutdown = psc_i2s_shutdown,
+ .trigger = psc_i2s_trigger,
+ },
+ .dai_ops = {
+ .set_sysclk = psc_i2s_set_sysclk,
+ .set_fmt = psc_i2s_set_fmt,
+ },
+};
+
+/* ---------------------------------------------------------------------
+ * The PSC I2S 'ASoC platform' driver
+ *
+ * Can be referenced by an 'ASoC machine' driver
+ * This driver only deals with the audio bus; it doesn't have any
+ * interaction with the attached codec
+ */
+
+static const struct snd_pcm_hardware psc_i2s_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_max = 1024 * 1024,
+ .period_bytes_min = 32,
+ .periods_min = 2,
+ .periods_max = 256,
+ .buffer_bytes_max = 2 * 1024 * 1024,
+ .fifo_size = 0,
+};
+
+static int psc_i2s_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ struct psc_i2s_stream *s;
+
+ dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_i2s->capture;
+ else
+ s = &psc_i2s->playback;
+
+ snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware);
+
+ s->stream = substream;
+ return 0;
+}
+
+static int psc_i2s_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ struct psc_i2s_stream *s;
+
+ dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_i2s->capture;
+ else
+ s = &psc_i2s->playback;
+
+ s->stream = NULL;
+ return 0;
+}
+
+static snd_pcm_uframes_t
+psc_i2s_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
+ struct psc_i2s_stream *s;
+ dma_addr_t count;
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s = &psc_i2s->capture;
+ else
+ s = &psc_i2s->playback;
+
+ count = s->period_current_pt - s->period_start;
+
+ return bytes_to_frames(substream->runtime, count);
+}
+
+static struct snd_pcm_ops psc_i2s_pcm_ops = {
+ .open = psc_i2s_pcm_open,
+ .close = psc_i2s_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .pointer = psc_i2s_pcm_pointer,
+};
+
+static u64 psc_i2s_pcm_dmamask = 0xffffffff;
+static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ size_t size = psc_i2s_pcm_hardware.buffer_bytes_max;
+ int rc = 0;
+
+ dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n",
+ card, dai, pcm);
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &psc_i2s_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (pcm->streams[0].substream) {
+ rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
+ &pcm->streams[0].substream->dma_buffer);
+ if (rc)
+ goto playback_alloc_err;
+ }
+
+ if (pcm->streams[1].substream) {
+ rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
+ &pcm->streams[1].substream->dma_buffer);
+ if (rc)
+ goto capture_alloc_err;
+ }
+
+ return 0;
+
+ capture_alloc_err:
+ if (pcm->streams[0].substream)
+ snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+ playback_alloc_err:
+ dev_err(card->dev, "Cannot allocate buffer(s)\n");
+ return -ENOMEM;
+}
+
+static void psc_i2s_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_pcm_substream *substream;
+ int stream;
+
+ dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm);
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+ }
+}
+
+struct snd_soc_platform psc_i2s_pcm_soc_platform = {
+ .name = "mpc5200-psc-audio",
+ .pcm_ops = &psc_i2s_pcm_ops,
+ .pcm_new = &psc_i2s_pcm_new,
+ .pcm_free = &psc_i2s_pcm_free,
+};
+
+/* ---------------------------------------------------------------------
+ * Sysfs attributes for debugging
+ */
+
+static ssize_t psc_i2s_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+
+ return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x "
+ "tfnum=%i tfstat=0x%.4x\n",
+ in_be16(&psc_i2s->psc_regs->sr_csr.status),
+ in_be32(&psc_i2s->psc_regs->sicr),
+ in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff,
+ in_be16(&psc_i2s->fifo_regs->rfstat),
+ in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff,
+ in_be16(&psc_i2s->fifo_regs->tfstat));
+}
+
+static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name)
+{
+ if (strcmp(name, "playback_underrun") == 0)
+ return &psc_i2s->stats.underrun_count;
+ if (strcmp(name, "capture_overrun") == 0)
+ return &psc_i2s->stats.overrun_count;
+
+ return NULL;
+}
+
+static ssize_t psc_i2s_stat_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+ int *attrib;
+
+ attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
+ if (!attrib)
+ return 0;
+
+ return sprintf(buf, "%i\n", *attrib);
+}
+
+static ssize_t psc_i2s_stat_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
+ int *attrib;
+
+ attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
+ if (!attrib)
+ return 0;
+
+ *attrib = simple_strtoul(buf, NULL, 0);
+ return count;
+}
+
+static DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL);
+static DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show,
+ psc_i2s_stat_store);
+static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show,
+ psc_i2s_stat_store);
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_i2s_of_probe(struct of_device *op,
+ const struct of_device_id *match)
+{
+ phys_addr_t fifo;
+ struct psc_i2s *psc_i2s;
+ struct resource res;
+ int size, psc_id, irq, rc;
+ const __be32 *prop;
+ void __iomem *regs;
+
+ dev_dbg(&op->dev, "probing psc i2s device\n");
+
+ /* Get the PSC ID */
+ prop = of_get_property(op->node, "cell-index", &size);
+ if (!prop || size < sizeof *prop)
+ return -ENODEV;
+ psc_id = be32_to_cpu(*prop);
+
+ /* Fetch the registers and IRQ of the PSC */
+ irq = irq_of_parse_and_map(op->node, 0);
+ if (of_address_to_resource(op->node, 0, &res)) {
+ dev_err(&op->dev, "Missing reg property\n");
+ return -ENODEV;
+ }
+ regs = ioremap(res.start, 1 + res.end - res.start);
+ if (!regs) {
+ dev_err(&op->dev, "Could not map registers\n");
+ return -ENODEV;
+ }
+
+ /* Allocate and initialize the driver private data */
+ psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL);
+ if (!psc_i2s) {
+ iounmap(regs);
+ return -ENOMEM;
+ }
+ spin_lock_init(&psc_i2s->lock);
+ psc_i2s->irq = irq;
+ psc_i2s->psc_regs = regs;
+ psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs;
+ psc_i2s->dev = &op->dev;
+ psc_i2s->playback.psc_i2s = psc_i2s;
+ psc_i2s->capture.psc_i2s = psc_i2s;
+ snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1);
+
+ /* Fill out the CPU DAI structure */
+ memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai);
+ psc_i2s->dai.private_data = psc_i2s;
+ psc_i2s->dai.name = psc_i2s->name;
+ psc_i2s->dai.id = psc_id;
+
+ /* Find the address of the fifo data registers and setup the
+ * DMA tasks */
+ fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
+ psc_i2s->capture.bcom_task =
+ bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512);
+ psc_i2s->playback.bcom_task =
+ bcom_psc_gen_bd_tx_init(psc_id, 10, fifo);
+ if (!psc_i2s->capture.bcom_task ||
+ !psc_i2s->playback.bcom_task) {
+ dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
+ iounmap(regs);
+ kfree(psc_i2s);
+ return -ENODEV;
+ }
+
+ /* Disable all interrupts and reset the PSC */
+ out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
+ out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */
+ out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */
+ out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
+ out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
+
+ /* Configure the serial interface mode; defaulting to CODEC8 mode */
+ psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
+ MPC52xx_PSC_SICR_CLKPOL;
+ if (of_get_property(op->node, "fsl,cellslave", NULL))
+ psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE |
+ MPC52xx_PSC_SICR_GENCLK;
+ out_be32(&psc_i2s->psc_regs->sicr,
+ psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
+
+ /* Check for the codec handle. If it is not present then we
+ * are done */
+ if (!of_get_property(op->node, "codec-handle", NULL))
+ return 0;
+
+ /* Set up mode register;
+ * First write: RxRdy (FIFO Alarm) generates rx FIFO irq
+ * Second write: register Normal mode for non loopback
+ */
+ out_8(&psc_i2s->psc_regs->mode, 0);
+ out_8(&psc_i2s->psc_regs->mode, 0);
+
+ /* Set the TX and RX fifo alarm thresholds */
+ out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100);
+ out_8(&psc_i2s->fifo_regs->rfcntl, 0x4);
+ out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100);
+ out_8(&psc_i2s->fifo_regs->tfcntl, 0x7);
+
+ /* Lookup the IRQ numbers */
+ psc_i2s->playback.irq =
+ bcom_get_task_irq(psc_i2s->playback.bcom_task);
+ psc_i2s->capture.irq =
+ bcom_get_task_irq(psc_i2s->capture.bcom_task);
+
+ /* Save what we've done so it can be found again later */
+ dev_set_drvdata(&op->dev, psc_i2s);
+
+ /* Register the SYSFS files */
+ rc = device_create_file(psc_i2s->dev, &dev_attr_status);
+ rc |= device_create_file(psc_i2s->dev, &dev_attr_capture_overrun);
+ rc |= device_create_file(psc_i2s->dev, &dev_attr_playback_underrun);
+ if (rc)
+ dev_info(psc_i2s->dev, "error creating sysfs files\n");
+
+ /* Tell the ASoC OF helpers about it */
+ of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node,
+ &psc_i2s->dai);
+
+ return 0;
+}
+
+static int __devexit psc_i2s_of_remove(struct of_device *op)
+{
+ struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev);
+
+ dev_dbg(&op->dev, "psc_i2s_remove()\n");
+
+ bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task);
+ bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task);
+
+ iounmap(psc_i2s->psc_regs);
+ iounmap(psc_i2s->fifo_regs);
+ kfree(psc_i2s);
+ dev_set_drvdata(&op->dev, NULL);
+
+ return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_i2s_match[] __devinitdata = {
+ { .compatible = "fsl,mpc5200-psc-i2s", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, psc_i2s_match);
+
+static struct of_platform_driver psc_i2s_driver = {
+ .match_table = psc_i2s_match,
+ .probe = psc_i2s_of_probe,
+ .remove = __devexit_p(psc_i2s_of_remove),
+ .driver = {
+ .name = "mpc5200-psc-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in I2S mode.
+ */
+static int __init psc_i2s_init(void)
+{
+ return of_register_platform_driver(&psc_i2s_driver);
+}
+module_init(psc_i2s_init);
+
+static void __exit psc_i2s_exit(void)
+{
+ of_unregister_platform_driver(&psc_i2s_driver);
+}
+module_exit(psc_i2s_exit);
+
+
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
new file mode 100644
index 0000000..94f89de
--- /dev/null
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,625 @@
+/**
+ * Freescale MPC8610HPCD ALSA SoC Fabric driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <asm/immap_86xx.h>
+
+#include "../codecs/cs4270.h"
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/**
+ * mpc8610_hpcd_data: fabric-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD. Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+ struct snd_soc_device sound_devdata;
+ struct snd_soc_dai_link dai;
+ struct snd_soc_machine machine;
+ unsigned int dai_format;
+ unsigned int codec_clk_direction;
+ unsigned int cpu_clk_direction;
+ unsigned int clk_frequency;
+ struct ccsr_guts __iomem *guts;
+ struct ccsr_ssi __iomem *ssi;
+ unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
+ unsigned int ssi_irq;
+ unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */
+ unsigned int dma_irq[2];
+ struct ccsr_dma_channel __iomem *dma[2];
+ unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initalize the board
+ *
+ * This function is called when platform_device_add() is called. It is used
+ * to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
+{
+ struct mpc8610_hpcd_data *machine_data =
+ sound_device->dev.platform_data;
+
+ /* Program the signal routing between the SSI and the DMA */
+ guts_set_dmacr(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI);
+ guts_set_dmacr(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI);
+
+ guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[0], 0);
+ guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&machine_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+ break;
+ case 1:
+ clrsetbits_be32(&machine_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct mpc8610_hpcd_data *machine_data =
+ rtd->socdev->dev->platform_data;
+ int ret = 0;
+
+ /* Tell the CPU driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set CPU driver audio format\n");
+ return ret;
+ }
+
+ /* Tell the codec driver what the serial protocol is. */
+ ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set codec driver audio format\n");
+ return ret;
+ }
+
+ /*
+ * Tell the CPU driver what the clock frequency is, and whether it's a
+ * slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
+ machine_data->clk_frequency,
+ machine_data->cpu_clk_direction);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set CPU driver clock parameters\n");
+ return ret;
+ }
+
+ /*
+ * Tell the codec driver what the MCLK frequency is, and whether it's
+ * a slave or master.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ machine_data->clk_frequency,
+ machine_data->codec_clk_direction);
+ if (ret < 0) {
+ dev_err(substream->pcm->card->dev,
+ "could not set codec driver clock params\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI. We
+ * de-program the DMACR and PMUXCR register.
+ */
+int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
+{
+ struct mpc8610_hpcd_data *machine_data =
+ sound_device->dev.platform_data;
+
+ /* Restore the signal routing */
+
+ guts_set_dmacr(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[0], 0);
+ guts_set_dmacr(machine_data->guts, machine_data->dma_id,
+ machine_data->dma_channel_id[1], 0);
+
+ switch (machine_data->ssi_id) {
+ case 0:
+ clrsetbits_be32(&machine_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+ break;
+ case 1:
+ clrsetbits_be32(&machine_data->guts->pmuxcr,
+ CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC fabric driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+ .startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * mpc8610_hpcd_machine: ASoC machine data
+ */
+static struct snd_soc_machine mpc8610_hpcd_machine = {
+ .probe = mpc8610_hpcd_machine_probe,
+ .remove = mpc8610_hpcd_machine_remove,
+ .name = "MPC8610 HPCD",
+ .num_links = 1,
+};
+
+/**
+ * mpc8610_hpcd_probe: OF probe function for the fabric driver
+ *
+ * This function gets called when an SSI node is found in the device tree.
+ *
+ * Although this is a fabric driver, the SSI node is the "master" node with
+ * respect to audio hardware connections. Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ *
+ * FIXME: Currently, we only support one DMA controller, so if there are
+ * multiple SSI nodes with codecs, only the first will be supported.
+ *
+ * FIXME: Even if we did support multiple DMA controllers, we have no
+ * mechanism for assigning DMA controllers and channels to the individual
+ * SSI devices. We also probably aren't compatible with the generic Elo DMA
+ * device driver.
+ */
+static int mpc8610_hpcd_probe(struct of_device *ofdev,
+ const struct of_device_id *match)
+{
+ struct device_node *np = ofdev->node;
+ struct device_node *codec_np = NULL;
+ struct device_node *guts_np = NULL;
+ struct device_node *dma_np = NULL;
+ struct device_node *dma_channel_np = NULL;
+ const phandle *codec_ph;
+ const char *sprop;
+ const u32 *iprop;
+ struct resource res;
+ struct platform_device *sound_device = NULL;
+ struct mpc8610_hpcd_data *machine_data;
+ struct fsl_ssi_info ssi_info;
+ struct fsl_dma_info dma_info;
+ int ret = -ENODEV;
+ unsigned int playback_dma_channel;
+ unsigned int capture_dma_channel;
+
+ machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+ if (!machine_data)
+ return -ENOMEM;
+
+ memset(&ssi_info, 0, sizeof(ssi_info));
+ memset(&dma_info, 0, sizeof(dma_info));
+
+ ssi_info.dev = &ofdev->dev;
+
+ /*
+ * We are only interested in SSIs with a codec phandle in them, so let's
+ * make sure this SSI has one.
+ */
+ codec_ph = of_get_property(np, "codec-handle", NULL);
+ if (!codec_ph)
+ goto error;
+
+ codec_np = of_find_node_by_phandle(*codec_ph);
+ if (!codec_np)
+ goto error;
+
+ /* The MPC8610 HPCD only knows about the CS4270 codec, so reject
+ anything else. */
+ if (!of_device_is_compatible(codec_np, "cirrus,cs4270"))
+ goto error;
+
+ /* Get the device ID */
+ iprop = of_get_property(np, "cell-index", NULL);
+ if (!iprop) {
+ dev_err(&ofdev->dev, "cell-index property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->ssi_id = *iprop;
+ ssi_info.id = *iprop;
+
+ /* Get the serial format and clock direction. */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop) {
+ dev_err(&ofdev->dev, "fsl,mode property not found\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (strcasecmp(sprop, "i2s-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+ /*
+ * In i2s-slave mode, the codec has its own clock source, so we
+ * need to get the frequency from the device tree and pass it to
+ * the codec driver.
+ */
+ iprop = of_get_property(codec_np, "clock-frequency", NULL);
+ if (!iprop || !*iprop) {
+ dev_err(&ofdev->dev, "codec bus-frequency property "
+ "is missing or invalid\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->clk_frequency = *iprop;
+ } else if (strcasecmp(sprop, "i2s-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "lj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "lj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "rj-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "rj-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else if (strcasecmp(sprop, "ac97-slave") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+ } else if (strcasecmp(sprop, "ac97-master") == 0) {
+ machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+ machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+ machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+ } else {
+ dev_err(&ofdev->dev,
+ "unrecognized fsl,mode property \"%s\"\n", sprop);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (!machine_data->clk_frequency) {
+ dev_err(&ofdev->dev, "unknown clock frequency\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Read the SSI information from the device tree */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ dev_err(&ofdev->dev, "could not obtain SSI address\n");
+ goto error;
+ }
+ if (!res.start) {
+ dev_err(&ofdev->dev, "invalid SSI address\n");
+ goto error;
+ }
+ ssi_info.ssi_phys = res.start;
+
+ machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi));
+ if (!machine_data->ssi) {
+ dev_err(&ofdev->dev, "could not map SSI address %x\n",
+ ssi_info.ssi_phys);
+ ret = -EINVAL;
+ goto error;
+ }
+ ssi_info.ssi = machine_data->ssi;
+
+
+ /* Get the IRQ of the SSI */
+ machine_data->ssi_irq = irq_of_parse_and_map(np, 0);
+ if (!machine_data->ssi_irq) {
+ dev_err(&ofdev->dev, "could not get SSI IRQ\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ ssi_info.irq = machine_data->ssi_irq;
+
+
+ /* Map the global utilities registers. */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+ if (!guts_np) {
+ dev_err(&ofdev->dev, "could not obtain address of GUTS\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->guts = of_iomap(guts_np, 0);
+ of_node_put(guts_np);
+ if (!machine_data->guts) {
+ dev_err(&ofdev->dev, "could not map GUTS\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Find the DMA channels to use. Both SSIs need to use the same DMA
+ * controller, so let's use DMA#1.
+ */
+ for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") {
+ iprop = of_get_property(dma_np, "cell-index", NULL);
+ if (iprop && (*iprop == 0)) {
+ of_node_put(dma_np);
+ break;
+ }
+ }
+ if (!dma_np) {
+ dev_err(&ofdev->dev, "could not find DMA node\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ machine_data->dma_id = *iprop;
+
+ /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA
+ * channels 2 and 3. This is just how the MPC8610 is wired
+ * internally.
+ */
+ playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2;
+ capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3;
+
+ /*
+ * Find the DMA channels to use.
+ */
+ while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) {
+ iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+ if (iprop && (*iprop == playback_dma_channel)) {
+ /* dma_channel[0] and dma_irq[0] are for playback */
+ dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0);
+ dma_info.dma_irq[0] =
+ irq_of_parse_and_map(dma_channel_np, 0);
+ machine_data->dma_channel_id[0] = *iprop;
+ continue;
+ }
+ if (iprop && (*iprop == capture_dma_channel)) {
+ /* dma_channel[1] and dma_irq[1] are for capture */
+ dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0);
+ dma_info.dma_irq[1] =
+ irq_of_parse_and_map(dma_channel_np, 0);
+ machine_data->dma_channel_id[1] = *iprop;
+ continue;
+ }
+ }
+ if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] ||
+ !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) {
+ dev_err(&ofdev->dev, "could not find DMA channels\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ dma_info.ssi_stx_phys = ssi_info.ssi_phys +
+ offsetof(struct ccsr_ssi, stx0);
+ dma_info.ssi_srx_phys = ssi_info.ssi_phys +
+ offsetof(struct ccsr_ssi, srx0);
+
+ /* We have the DMA information, so tell the DMA driver what it is */
+ if (!fsl_dma_configure(&dma_info)) {
+ dev_err(&ofdev->dev, "could not instantiate DMA device\n");
+ ret = -EBUSY;
+ goto error;
+ }
+
+ /*
+ * Initialize our DAI data structure. We should probably get this
+ * information from the device tree.
+ */
+ machine_data->dai.name = "CS4270";
+ machine_data->dai.stream_name = "CS4270";
+
+ machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info);
+ machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */
+ machine_data->dai.ops = &mpc8610_hpcd_ops;
+
+ mpc8610_hpcd_machine.dai_link = &machine_data->dai;
+
+ /* Allocate a new audio platform device structure */
+ sound_device = platform_device_alloc("soc-audio", -1);
+ if (!sound_device) {
+ dev_err(&ofdev->dev, "platform device allocation failed\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ machine_data->sound_devdata.machine = &mpc8610_hpcd_machine;
+ machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270;
+ machine_data->sound_devdata.platform = &fsl_soc_platform;
+
+ sound_device->dev.platform_data = machine_data;
+
+
+ /* Set the platform device and ASoC device to point to each other */
+ platform_set_drvdata(sound_device, &machine_data->sound_devdata);
+
+ machine_data->sound_devdata.dev = &sound_device->dev;
+
+
+ /* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(),
+ if it exists. */
+ ret = platform_device_add(sound_device);
+
+ if (ret) {
+ dev_err(&ofdev->dev, "platform device add failed\n");
+ goto error;
+ }
+
+ dev_set_drvdata(&ofdev->dev, sound_device);
+
+ return 0;
+
+error:
+ of_node_put(codec_np);
+ of_node_put(guts_np);
+ of_node_put(dma_np);
+ of_node_put(dma_channel_np);
+
+ if (sound_device)
+ platform_device_unregister(sound_device);
+
+ if (machine_data->dai.cpu_dai)
+ fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
+
+ if (ssi_info.ssi)
+ iounmap(ssi_info.ssi);
+
+ if (ssi_info.irq)
+ irq_dispose_mapping(ssi_info.irq);
+
+ if (dma_info.dma_channel[0])
+ iounmap(dma_info.dma_channel[0]);
+
+ if (dma_info.dma_channel[1])
+ iounmap(dma_info.dma_channel[1]);
+
+ if (dma_info.dma_irq[0])
+ irq_dispose_mapping(dma_info.dma_irq[0]);
+
+ if (dma_info.dma_irq[1])
+ irq_dispose_mapping(dma_info.dma_irq[1]);
+
+ if (machine_data->guts)
+ iounmap(machine_data->guts);
+
+ kfree(machine_data);
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the OF device
+ *
+ * This function is called when the OF device is removed.
+ */
+static int mpc8610_hpcd_remove(struct of_device *ofdev)
+{
+ struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev);
+ struct mpc8610_hpcd_data *machine_data =
+ sound_device->dev.platform_data;
+
+ platform_device_unregister(sound_device);
+
+ if (machine_data->dai.cpu_dai)
+ fsl_ssi_destroy_dai(machine_data->dai.cpu_dai);
+
+ if (machine_data->ssi)
+ iounmap(machine_data->ssi);
+
+ if (machine_data->dma[0])
+ iounmap(machine_data->dma[0]);
+
+ if (machine_data->dma[1])
+ iounmap(machine_data->dma[1]);
+
+ if (machine_data->dma_irq[0])
+ irq_dispose_mapping(machine_data->dma_irq[0]);
+
+ if (machine_data->dma_irq[1])
+ irq_dispose_mapping(machine_data->dma_irq[1]);
+
+ if (machine_data->guts)
+ iounmap(machine_data->guts);
+
+ kfree(machine_data);
+ sound_device->dev.platform_data = NULL;
+
+ dev_set_drvdata(&ofdev->dev, NULL);
+
+ return 0;
+}
+
+static struct of_device_id mpc8610_hpcd_match[] = {
+ {
+ .compatible = "fsl,mpc8610-ssi",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match);
+
+static struct of_platform_driver mpc8610_hpcd_of_driver = {
+ .owner = THIS_MODULE,
+ .name = "mpc8610_hpcd",
+ .match_table = mpc8610_hpcd_match,
+ .probe = mpc8610_hpcd_probe,
+ .remove = mpc8610_hpcd_remove,
+};
+
+/**
+ * mpc8610_hpcd_init: fabric driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n");
+
+ ret = of_register_platform_driver(&mpc8610_hpcd_of_driver);
+
+ if (ret)
+ printk(KERN_ERR
+ "mpc8610-hpcd: failed to register platform driver\n");
+
+ return ret;
+}
+
+/**
+ * mpc8610_hpcd_exit: fabric driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+ of_unregister_platform_driver(&mpc8610_hpcd_of_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/soc-of-simple.c b/sound/soc/fsl/soc-of-simple.c
new file mode 100644
index 0000000..0382fda
--- /dev/null
+++ b/sound/soc/fsl/soc-of-simple.c
@@ -0,0 +1,171 @@
+/*
+ * OF helpers for ALSA SoC Layer
+ *
+ * Copyright (C) 2008, Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-of-simple.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings");
+
+static DEFINE_MUTEX(of_snd_soc_mutex);
+static LIST_HEAD(of_snd_soc_device_list);
+static int of_snd_soc_next_index;
+
+struct of_snd_soc_device {
+ int id;
+ struct list_head list;
+ struct snd_soc_device device;
+ struct snd_soc_machine machine;
+ struct snd_soc_dai_link dai_link;
+ struct platform_device *pdev;
+ struct device_node *platform_node;
+ struct device_node *codec_node;
+};
+
+static struct snd_soc_ops of_snd_soc_ops = {
+};
+
+static struct of_snd_soc_device *
+of_snd_soc_get_device(struct device_node *codec_node)
+{
+ struct of_snd_soc_device *of_soc;
+
+ list_for_each_entry(of_soc, &of_snd_soc_device_list, list) {
+ if (of_soc->codec_node == codec_node)
+ return of_soc;
+ }
+
+ of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL);
+ if (!of_soc)
+ return NULL;
+
+ /* Initialize the structure and add it to the global list */
+ of_soc->codec_node = codec_node;
+ of_soc->id = of_snd_soc_next_index++;
+ of_soc->machine.dai_link = &of_soc->dai_link;
+ of_soc->machine.num_links = 1;
+ of_soc->device.machine = &of_soc->machine;
+ of_soc->dai_link.ops = &of_snd_soc_ops;
+ list_add(&of_soc->list, &of_snd_soc_device_list);
+
+ return of_soc;
+}
+
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc)
+{
+ struct platform_device *pdev;
+ int rc;
+
+ /* Only register the device if both the codec and platform have
+ * been registered */
+ if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
+ return;
+
+ pr_info("platform<-->codec match achieved; registering machine\n");
+
+ pdev = platform_device_alloc("soc-audio", of_soc->id);
+ if (!pdev) {
+ pr_err("of_soc: platform_device_alloc() failed\n");
+ return;
+ }
+
+ pdev->dev.platform_data = of_soc;
+ platform_set_drvdata(pdev, &of_soc->device);
+ of_soc->device.dev = &pdev->dev;
+
+ /* The ASoC device is complete; register it */
+ rc = platform_device_add(pdev);
+ if (rc) {
+ pr_err("of_soc: platform_device_add() failed\n");
+ return;
+ }
+
+}
+
+int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev,
+ void *codec_data, struct snd_soc_dai *dai,
+ struct device_node *node)
+{
+ struct of_snd_soc_device *of_soc;
+ int rc = 0;
+
+ pr_info("registering ASoC codec driver: %s\n", node->full_name);
+
+ mutex_lock(&of_snd_soc_mutex);
+ of_soc = of_snd_soc_get_device(node);
+ if (!of_soc) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* Store the codec data */
+ of_soc->device.codec_data = codec_data;
+ of_soc->device.codec_dev = codec_dev;
+ of_soc->dai_link.name = (char *)node->name;
+ of_soc->dai_link.stream_name = (char *)node->name;
+ of_soc->dai_link.codec_dai = dai;
+
+ /* Now try to register the SoC device */
+ of_snd_soc_register_device(of_soc);
+
+ out:
+ mutex_unlock(&of_snd_soc_mutex);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(of_snd_soc_register_codec);
+
+int of_snd_soc_register_platform(struct snd_soc_platform *platform,
+ struct device_node *node,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct of_snd_soc_device *of_soc;
+ struct device_node *codec_node;
+ const phandle *handle;
+ int len, rc = 0;
+
+ pr_info("registering ASoC platform driver: %s\n", node->full_name);
+
+ handle = of_get_property(node, "codec-handle", &len);
+ if (!handle || len < sizeof(handle))
+ return -ENODEV;
+ codec_node = of_find_node_by_phandle(*handle);
+ if (!codec_node)
+ return -ENODEV;
+ pr_info("looking for codec: %s\n", codec_node->full_name);
+
+ mutex_lock(&of_snd_soc_mutex);
+ of_soc = of_snd_soc_get_device(codec_node);
+ if (!of_soc) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ of_soc->platform_node = node;
+ of_soc->dai_link.cpu_dai = cpu_dai;
+ of_soc->device.platform = platform;
+ of_soc->machine.name = of_soc->dai_link.cpu_dai->name;
+
+ /* Now try to register the SoC device */
+ of_snd_soc_register_device(of_soc);
+
+ out:
+ mutex_unlock(&of_snd_soc_mutex);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(of_snd_soc_register_platform);
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig
new file mode 100644
index 0000000..8b7766b
--- /dev/null
+++ b/sound/soc/omap/Kconfig
@@ -0,0 +1,23 @@
+config SND_OMAP_SOC
+ tristate "SoC Audio for the Texas Instruments OMAP chips"
+ depends on ARCH_OMAP && SND_SOC
+
+config SND_OMAP_SOC_MCBSP
+ tristate
+ select OMAP_MCBSP
+
+config SND_OMAP_SOC_N810
+ tristate "SoC Audio support for Nokia N810"
+ depends on SND_OMAP_SOC && MACH_NOKIA_N810
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for SoC audio on Nokia N810.
+
+config SND_OMAP_SOC_OSK5912
+ tristate "SoC Audio support for omap osk5912"
+ depends on SND_OMAP_SOC && MACH_OMAP_OSK
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TLV320AIC23
+ help
+ Say Y if you want to add support for SoC audio on osk5912.
diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile
new file mode 100644
index 0000000..e09d1f2
--- /dev/null
+++ b/sound/soc/omap/Makefile
@@ -0,0 +1,13 @@
+# OMAP Platform Support
+snd-soc-omap-objs := omap-pcm.o
+snd-soc-omap-mcbsp-objs := omap-mcbsp.o
+
+obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
+obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
+
+# OMAP Machine Support
+snd-soc-n810-objs := n810.o
+snd-soc-osk5912-objs := osk5912.o
+
+obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
+obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c
new file mode 100644
index 0000000..fae3ad3
--- /dev/null
+++ b/sound/soc/omap/n810.c
@@ -0,0 +1,391 @@
+/*
+ * n810.c -- SoC audio for Nokia N810
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <mach/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic3x.h"
+
+#define N810_HEADSET_AMP_GPIO 10
+#define N810_SPEAKER_AMP_GPIO 101
+
+static struct clk *sys_clkout2;
+static struct clk *sys_clkout2_src;
+static struct clk *func96m_clk;
+
+static int n810_spk_func;
+static int n810_jack_func;
+static int n810_dmic_func;
+
+static void n810_ext_control(struct snd_soc_codec *codec)
+{
+ if (n810_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ if (n810_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ if (n810_dmic_func)
+ snd_soc_dapm_enable_pin(codec, "DMic");
+ else
+ snd_soc_dapm_disable_pin(codec, "DMic");
+
+ snd_soc_dapm_sync(codec);
+}
+
+static int n810_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ n810_ext_control(codec);
+ return clk_enable(sys_clkout2);
+}
+
+static void n810_shutdown(struct snd_pcm_substream *substream)
+{
+ clk_disable(sys_clkout2);
+}
+
+static int n810_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0)
+ return err;
+
+ /* Set the codec system clock for DAC and ADC */
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000,
+ SND_SOC_CLOCK_IN);
+
+ return err;
+}
+
+static struct snd_soc_ops n810_ops = {
+ .startup = n810_startup,
+ .hw_params = n810_hw_params,
+ .shutdown = n810_shutdown,
+};
+
+static int n810_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_spk_func;
+
+ return 0;
+}
+
+static int n810_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_spk_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_jack_func;
+
+ return 0;
+}
+
+static int n810_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_jack_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_get_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = n810_dmic_func;
+
+ return 0;
+}
+
+static int n810_set_input(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (n810_dmic_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ n810_dmic_func = ucontrol->value.integer.value[0];
+ n810_ext_control(codec);
+
+ return 1;
+}
+
+static int n810_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(N810_SPEAKER_AMP_GPIO, 1);
+ else
+ gpio_set_value(N810_SPEAKER_AMP_GPIO, 0);
+
+ return 0;
+}
+
+static int n810_jack_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpio_set_value(N810_HEADSET_AMP_GPIO, 1);
+ else
+ gpio_set_value(N810_HEADSET_AMP_GPIO, 0);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event),
+ SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event),
+ SND_SOC_DAPM_MIC("DMic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "HPLOUT"},
+ {"Headphone Jack", NULL, "HPROUT"},
+
+ {"Ext Spk", NULL, "LLOUT"},
+ {"Ext Spk", NULL, "RLOUT"},
+
+ {"DMic Rate 64", NULL, "Mic Bias 2V"},
+ {"Mic Bias 2V", NULL, "DMic"},
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *jack_function[] = {"Off", "Headphone"};
+static const char *input_function[] = {"ADC", "Digital Mic"};
+static const struct soc_enum n810_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+};
+
+static const struct snd_kcontrol_new aic33_n810_controls[] = {
+ SOC_ENUM_EXT("Speaker Function", n810_enum[0],
+ n810_get_spk, n810_set_spk),
+ SOC_ENUM_EXT("Jack Function", n810_enum[1],
+ n810_get_jack, n810_set_jack),
+ SOC_ENUM_EXT("Input Select", n810_enum[2],
+ n810_get_input, n810_set_input),
+};
+
+static int n810_aic33_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ /* Not connected */
+ snd_soc_dapm_nc_pin(codec, "MONO_LOUT");
+ snd_soc_dapm_nc_pin(codec, "HPLCOM");
+ snd_soc_dapm_nc_pin(codec, "HPRCOM");
+
+ /* Add N810 specific controls */
+ for (i = 0; i < ARRAY_SIZE(aic33_n810_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&aic33_n810_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* Add N810 specific widgets */
+ snd_soc_dapm_new_controls(codec, aic33_dapm_widgets,
+ ARRAY_SIZE(aic33_dapm_widgets));
+
+ /* Set up N810 specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link n810_dai = {
+ .name = "TLV320AIC33",
+ .stream_name = "AIC33",
+ .cpu_dai = &omap_mcbsp_dai[0],
+ .codec_dai = &aic3x_dai,
+ .init = n810_aic33_init,
+ .ops = &n810_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_machine snd_soc_machine_n810 = {
+ .name = "N810",
+ .dai_link = &n810_dai,
+ .num_links = 1,
+};
+
+/* Audio private data */
+static struct aic3x_setup_data n810_aic33_setup = {
+ .i2c_bus = 2,
+ .i2c_address = 0x18,
+ .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED,
+ .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT,
+};
+
+/* Audio subsystem */
+static struct snd_soc_device n810_snd_devdata = {
+ .machine = &snd_soc_machine_n810,
+ .platform = &omap_soc_platform,
+ .codec_dev = &soc_codec_dev_aic3x,
+ .codec_data = &n810_aic33_setup,
+};
+
+static struct platform_device *n810_snd_device;
+
+static int __init n810_soc_init(void)
+{
+ int err;
+ struct device *dev;
+
+ if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax()))
+ return -ENODEV;
+
+ n810_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!n810_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(n810_snd_device, &n810_snd_devdata);
+ n810_snd_devdata.dev = &n810_snd_device->dev;
+ *(unsigned int *)n810_dai.cpu_dai->private_data = 1; /* McBSP2 */
+ err = platform_device_add(n810_snd_device);
+ if (err)
+ goto err1;
+
+ dev = &n810_snd_device->dev;
+
+ sys_clkout2_src = clk_get(dev, "sys_clkout2_src");
+ if (IS_ERR(sys_clkout2_src)) {
+ dev_err(dev, "Could not get sys_clkout2_src clock\n");
+ err = PTR_ERR(sys_clkout2_src);
+ goto err2;
+ }
+ sys_clkout2 = clk_get(dev, "sys_clkout2");
+ if (IS_ERR(sys_clkout2)) {
+ dev_err(dev, "Could not get sys_clkout2\n");
+ err = PTR_ERR(sys_clkout2);
+ goto err3;
+ }
+ /*
+ * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use
+ * 96 MHz as its parent in order to get 12 MHz
+ */
+ func96m_clk = clk_get(dev, "func_96m_ck");
+ if (IS_ERR(func96m_clk)) {
+ dev_err(dev, "Could not get func 96M clock\n");
+ err = PTR_ERR(func96m_clk);
+ goto err4;
+ }
+ clk_set_parent(sys_clkout2_src, func96m_clk);
+ clk_set_rate(sys_clkout2, 12000000);
+
+ if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0)
+ BUG();
+ if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0)
+ BUG();
+ gpio_direction_output(N810_HEADSET_AMP_GPIO, 0);
+ gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0);
+
+ return 0;
+err4:
+ clk_put(sys_clkout2);
+err3:
+ clk_put(sys_clkout2_src);
+err2:
+ platform_device_del(n810_snd_device);
+err1:
+ platform_device_put(n810_snd_device);
+
+ return err;
+}
+
+static void __exit n810_soc_exit(void)
+{
+ gpio_free(N810_SPEAKER_AMP_GPIO);
+ gpio_free(N810_HEADSET_AMP_GPIO);
+ clk_put(sys_clkout2_src);
+ clk_put(sys_clkout2);
+ clk_put(func96m_clk);
+
+ platform_device_unregister(n810_snd_device);
+}
+
+module_init(n810_soc_init);
+module_exit(n810_soc_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
+MODULE_DESCRIPTION("ALSA SoC Nokia N810");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c
new file mode 100644
index 0000000..8485a8a
--- /dev/null
+++ b/sound/soc/omap/omap-mcbsp.c
@@ -0,0 +1,500 @@
+/*
+ * omap-mcbsp.c -- OMAP ALSA SoC DAI driver using McBSP port
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/control.h>
+#include <mach/dma.h>
+#include <mach/mcbsp.h>
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_KNOT)
+
+struct omap_mcbsp_data {
+ unsigned int bus_id;
+ struct omap_mcbsp_reg_cfg regs;
+ unsigned int fmt;
+ /*
+ * Flags indicating is the bus already activated and configured by
+ * another substream
+ */
+ int active;
+ int configured;
+};
+
+#define to_mcbsp(priv) container_of((priv), struct omap_mcbsp_data, bus_id)
+
+static struct omap_mcbsp_data mcbsp_data[NUM_LINKS];
+
+/*
+ * Stream DMA parameters. DMA request line and port address are set runtime
+ * since they are different between OMAP1 and later OMAPs
+ */
+static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2];
+
+#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
+static const int omap1_dma_reqs[][2] = {
+ { OMAP_DMA_MCBSP1_TX, OMAP_DMA_MCBSP1_RX },
+ { OMAP_DMA_MCBSP2_TX, OMAP_DMA_MCBSP2_RX },
+ { OMAP_DMA_MCBSP3_TX, OMAP_DMA_MCBSP3_RX },
+};
+static const unsigned long omap1_mcbsp_port[][2] = {
+ { OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
+ OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
+ { OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
+ OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
+ { OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DXR1,
+ OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DRR1 },
+};
+#else
+static const int omap1_dma_reqs[][2] = {};
+static const unsigned long omap1_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP24XX) || defined(CONFIG_ARCH_OMAP34XX)
+static const int omap24xx_dma_reqs[][2] = {
+ { OMAP24XX_DMA_MCBSP1_TX, OMAP24XX_DMA_MCBSP1_RX },
+ { OMAP24XX_DMA_MCBSP2_TX, OMAP24XX_DMA_MCBSP2_RX },
+#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX)
+ { OMAP24XX_DMA_MCBSP3_TX, OMAP24XX_DMA_MCBSP3_RX },
+ { OMAP24XX_DMA_MCBSP4_TX, OMAP24XX_DMA_MCBSP4_RX },
+ { OMAP24XX_DMA_MCBSP5_TX, OMAP24XX_DMA_MCBSP5_RX },
+#endif
+};
+#else
+static const int omap24xx_dma_reqs[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP2420)
+static const unsigned long omap2420_mcbsp_port[][2] = {
+ { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
+ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
+ { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
+ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
+};
+#else
+static const unsigned long omap2420_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP2430)
+static const unsigned long omap2430_mcbsp_port[][2] = {
+ { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
+};
+#else
+static const unsigned long omap2430_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP34XX)
+static const unsigned long omap34xx_mcbsp_port[][2] = {
+ { OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
+ { OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
+ OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
+};
+#else
+static const unsigned long omap34xx_mcbsp_port[][2] = {};
+#endif
+
+static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ int err = 0;
+
+ if (!cpu_dai->active)
+ err = omap_mcbsp_request(mcbsp_data->bus_id);
+
+ return err;
+}
+
+static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+
+ if (!cpu_dai->active) {
+ omap_mcbsp_free(mcbsp_data->bus_id);
+ mcbsp_data->configured = 0;
+ }
+}
+
+static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ int err = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!mcbsp_data->active++)
+ omap_mcbsp_start(mcbsp_data->bus_id);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!--mcbsp_data->active)
+ omap_mcbsp_stop(mcbsp_data->bus_id);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int omap_mcbsp_dai_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_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+ int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id;
+ int wlen;
+ unsigned long port;
+
+ if (cpu_class_is_omap1()) {
+ dma = omap1_dma_reqs[bus_id][substream->stream];
+ port = omap1_mcbsp_port[bus_id][substream->stream];
+ } else if (cpu_is_omap2420()) {
+ dma = omap24xx_dma_reqs[bus_id][substream->stream];
+ port = omap2420_mcbsp_port[bus_id][substream->stream];
+ } else if (cpu_is_omap2430()) {
+ dma = omap24xx_dma_reqs[bus_id][substream->stream];
+ port = omap2430_mcbsp_port[bus_id][substream->stream];
+ } else if (cpu_is_omap343x()) {
+ dma = omap24xx_dma_reqs[bus_id][substream->stream];
+ port = omap34xx_mcbsp_port[bus_id][substream->stream];
+ } else {
+ return -ENODEV;
+ }
+ omap_mcbsp_dai_dma_params[id][substream->stream].name =
+ substream->stream ? "Audio Capture" : "Audio Playback";
+ omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma;
+ omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port;
+ cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream];
+
+ if (mcbsp_data->configured) {
+ /* McBSP already configured by another stream */
+ return 0;
+ }
+
+ switch (params_channels(params)) {
+ case 2:
+ /* Set 1 word per (McBPSP) frame and use dual-phase frames */
+ regs->rcr2 |= RFRLEN2(1 - 1) | RPHASE;
+ regs->rcr1 |= RFRLEN1(1 - 1);
+ regs->xcr2 |= XFRLEN2(1 - 1) | XPHASE;
+ regs->xcr1 |= XFRLEN1(1 - 1);
+ break;
+ default:
+ /* Unsupported number of channels */
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* Set word lengths */
+ wlen = 16;
+ regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16);
+ regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16);
+ regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16);
+ regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16);
+ break;
+ default:
+ /* Unsupported PCM format */
+ return -EINVAL;
+ }
+
+ /* Set FS period and length in terms of bit clock periods */
+ switch (mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ regs->srgr2 |= FPER(wlen * 2 - 1);
+ regs->srgr1 |= FWID(wlen - 1);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ regs->srgr2 |= FPER(wlen * 2 - 1);
+ regs->srgr1 |= FWID(wlen * 2 - 2);
+ break;
+ }
+
+ omap_mcbsp_config(bus_id, &mcbsp_data->regs);
+ mcbsp_data->configured = 1;
+
+ return 0;
+}
+
+/*
+ * This must be called before _set_clkdiv and _set_sysclk since McBSP register
+ * cache is initialized here
+ */
+static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+
+ if (mcbsp_data->configured)
+ return 0;
+
+ mcbsp_data->fmt = fmt;
+ memset(regs, 0, sizeof(*regs));
+ /* Generic McBSP register settings */
+ regs->spcr2 |= XINTM(3) | FREE;
+ regs->spcr1 |= RINTM(3);
+ regs->rcr2 |= RFIG;
+ regs->xcr2 |= XFIG;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* 1-bit data delay */
+ regs->rcr2 |= RDATDLY(1);
+ regs->xcr2 |= XDATDLY(1);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* 0-bit data delay */
+ regs->rcr2 |= RDATDLY(0);
+ regs->xcr2 |= XDATDLY(0);
+ break;
+ default:
+ /* Unsupported data format */
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* McBSP master. Set FS and bit clocks as outputs */
+ regs->pcr0 |= FSXM | FSRM |
+ CLKXM | CLKRM;
+ /* Sample rate generator drives the FS */
+ regs->srgr2 |= FSGM;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* McBSP slave */
+ break;
+ default:
+ /* Unsupported master/slave configuration */
+ return -EINVAL;
+ }
+
+ /* Set bit clock (CLKX/CLKR) and FS polarities */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /*
+ * Normal BCLK + FS.
+ * FS active low. TX data driven on falling edge of bit clock
+ * and RX data sampled on rising edge of bit clock.
+ */
+ regs->pcr0 |= FSXP | FSRP |
+ CLKXP | CLKRP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ regs->pcr0 |= CLKXP | CLKRP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ regs->pcr0 |= FSXP | FSRP;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+
+ if (div_id != OMAP_MCBSP_CLKGDV)
+ return -ENODEV;
+
+ regs->srgr1 |= CLKGDV(div - 1);
+
+ return 0;
+}
+
+static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data,
+ int clk_id)
+{
+ int sel_bit;
+ u16 reg, reg_devconf1 = OMAP243X_CONTROL_DEVCONF1;
+
+ if (cpu_class_is_omap1()) {
+ /* OMAP1's can use only external source clock */
+ if (unlikely(clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK))
+ return -EINVAL;
+ else
+ return 0;
+ }
+
+ if (cpu_is_omap2420() && mcbsp_data->bus_id > 1)
+ return -EINVAL;
+
+ if (cpu_is_omap343x())
+ reg_devconf1 = OMAP343X_CONTROL_DEVCONF1;
+
+ switch (mcbsp_data->bus_id) {
+ case 0:
+ reg = OMAP2_CONTROL_DEVCONF0;
+ sel_bit = 2;
+ break;
+ case 1:
+ reg = OMAP2_CONTROL_DEVCONF0;
+ sel_bit = 6;
+ break;
+ case 2:
+ reg = reg_devconf1;
+ sel_bit = 0;
+ break;
+ case 3:
+ reg = reg_devconf1;
+ sel_bit = 2;
+ break;
+ case 4:
+ reg = reg_devconf1;
+ sel_bit = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK)
+ omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg);
+ else
+ omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg);
+
+ return 0;
+}
+
+static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq,
+ int dir)
+{
+ struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+ struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+ int err = 0;
+
+ switch (clk_id) {
+ case OMAP_MCBSP_SYSCLK_CLK:
+ regs->srgr2 |= CLKSM;
+ break;
+ case OMAP_MCBSP_SYSCLK_CLKS_FCLK:
+ case OMAP_MCBSP_SYSCLK_CLKS_EXT:
+ err = omap_mcbsp_dai_set_clks_src(mcbsp_data, clk_id);
+ break;
+
+ case OMAP_MCBSP_SYSCLK_CLKX_EXT:
+ regs->srgr2 |= CLKSM;
+ case OMAP_MCBSP_SYSCLK_CLKR_EXT:
+ regs->pcr0 |= SCLKME;
+ break;
+ default:
+ err = -ENODEV;
+ }
+
+ return err;
+}
+
+#define OMAP_MCBSP_DAI_BUILDER(link_id) \
+{ \
+ .name = "omap-mcbsp-dai-(link_id)", \
+ .id = (link_id), \
+ .type = SND_SOC_DAI_I2S, \
+ .playback = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = OMAP_MCBSP_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .capture = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = OMAP_MCBSP_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .ops = { \
+ .startup = omap_mcbsp_dai_startup, \
+ .shutdown = omap_mcbsp_dai_shutdown, \
+ .trigger = omap_mcbsp_dai_trigger, \
+ .hw_params = omap_mcbsp_dai_hw_params, \
+ }, \
+ .dai_ops = { \
+ .set_fmt = omap_mcbsp_dai_set_dai_fmt, \
+ .set_clkdiv = omap_mcbsp_dai_set_clkdiv, \
+ .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, \
+ }, \
+ .private_data = &mcbsp_data[(link_id)].bus_id, \
+}
+
+struct snd_soc_dai omap_mcbsp_dai[] = {
+ OMAP_MCBSP_DAI_BUILDER(0),
+ OMAP_MCBSP_DAI_BUILDER(1),
+#if NUM_LINKS >= 3
+ OMAP_MCBSP_DAI_BUILDER(2),
+#endif
+#if NUM_LINKS == 5
+ OMAP_MCBSP_DAI_BUILDER(3),
+ OMAP_MCBSP_DAI_BUILDER(4),
+#endif
+};
+
+EXPORT_SYMBOL_GPL(omap_mcbsp_dai);
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
+MODULE_DESCRIPTION("OMAP I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h
new file mode 100644
index 0000000..df7ad13
--- /dev/null
+++ b/sound/soc/omap/omap-mcbsp.h
@@ -0,0 +1,55 @@
+/*
+ * omap-mcbsp.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_I2S_H__
+#define __OMAP_I2S_H__
+
+/* Source clocks for McBSP sample rate generator */
+enum omap_mcbsp_clksrg_clk {
+ OMAP_MCBSP_SYSCLK_CLKS_FCLK, /* Internal FCLK */
+ OMAP_MCBSP_SYSCLK_CLKS_EXT, /* External CLKS pin */
+ OMAP_MCBSP_SYSCLK_CLK, /* Internal ICLK */
+ OMAP_MCBSP_SYSCLK_CLKX_EXT, /* External CLKX pin */
+ OMAP_MCBSP_SYSCLK_CLKR_EXT, /* External CLKR pin */
+};
+
+/* McBSP dividers */
+enum omap_mcbsp_div {
+ OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */
+};
+
+#if defined(CONFIG_ARCH_OMAP2420)
+#define NUM_LINKS 2
+#endif
+#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
+#undef NUM_LINKS
+#define NUM_LINKS 3
+#endif
+#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX)
+#undef NUM_LINKS
+#define NUM_LINKS 5
+#endif
+
+extern struct snd_soc_dai omap_mcbsp_dai[NUM_LINKS];
+
+#endif
diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c
new file mode 100644
index 0000000..acd68ef
--- /dev/null
+++ b/sound/soc/omap/omap-pcm.c
@@ -0,0 +1,359 @@
+/*
+ * omap-pcm.c -- ALSA PCM interface for the OMAP SoC
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include "omap-pcm.h"
+
+static const struct snd_pcm_hardware omap_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 64 * 1024,
+ .periods_min = 2,
+ .periods_max = 255,
+ .buffer_bytes_max = 128 * 1024,
+};
+
+struct omap_runtime_data {
+ spinlock_t lock;
+ struct omap_pcm_dma_data *dma_data;
+ int dma_ch;
+ int period_index;
+};
+
+static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ unsigned long flags;
+
+ if (cpu_is_omap1510()) {
+ /*
+ * OMAP1510 doesn't support DMA chaining so have to restart
+ * the transfer after all periods are transferred
+ */
+ spin_lock_irqsave(&prtd->lock, flags);
+ if (prtd->period_index >= 0) {
+ if (++prtd->period_index == runtime->periods) {
+ prtd->period_index = 0;
+ omap_start_dma(prtd->dma_ch);
+ }
+ }
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ }
+
+ snd_pcm_period_elapsed(substream);
+}
+
+/* this may get called several times by oss emulation */
+static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data;
+ int err = 0;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ if (prtd->dma_data)
+ return 0;
+ prtd->dma_data = dma_data;
+ err = omap_request_dma(dma_data->dma_req, dma_data->name,
+ omap_pcm_dma_irq, substream, &prtd->dma_ch);
+ if (!err & !cpu_is_omap1510()) {
+ /*
+ * Link channel with itself so DMA doesn't need any
+ * reprogramming while looping the buffer
+ */
+ omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
+ }
+
+ return err;
+}
+
+static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+
+ if (prtd->dma_data == NULL)
+ return 0;
+
+ if (!cpu_is_omap1510())
+ omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
+ omap_free_dma(prtd->dma_ch);
+ prtd->dma_data = NULL;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int omap_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ struct omap_pcm_dma_data *dma_data = prtd->dma_data;
+ struct omap_dma_channel_params dma_params;
+
+ memset(&dma_params, 0, sizeof(dma_params));
+ /*
+ * Note: Regardless of interface data formats supported by OMAP McBSP
+ * or EAC blocks, internal representation is always fixed 16-bit/sample
+ */
+ dma_params.data_type = OMAP_DMA_DATA_TYPE_S16;
+ dma_params.trigger = dma_data->dma_req;
+ dma_params.sync_mode = OMAP_DMA_SYNC_ELEMENT;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_params.src_amode = OMAP_DMA_AMODE_POST_INC;
+ dma_params.dst_amode = OMAP_DMA_AMODE_CONSTANT;
+ dma_params.src_or_dst_synch = OMAP_DMA_DST_SYNC;
+ dma_params.src_start = runtime->dma_addr;
+ dma_params.dst_start = dma_data->port_addr;
+ dma_params.dst_port = OMAP_DMA_PORT_MPUI;
+ } else {
+ dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT;
+ dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC;
+ dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC;
+ dma_params.src_start = dma_data->port_addr;
+ dma_params.dst_start = runtime->dma_addr;
+ dma_params.src_port = OMAP_DMA_PORT_MPUI;
+ }
+ /*
+ * Set DMA transfer frame size equal to ALSA period size and frame
+ * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
+ * we can transfer the whole ALSA buffer with single DMA transfer but
+ * still can get an interrupt at each period bounary
+ */
+ dma_params.elem_count = snd_pcm_lib_period_bytes(substream) / 2;
+ dma_params.frame_count = runtime->periods;
+ omap_set_dma_params(prtd->dma_ch, &dma_params);
+
+ omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);
+
+ return 0;
+}
+
+static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ int ret = 0;
+
+ spin_lock_irq(&prtd->lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->period_index = 0;
+ omap_start_dma(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->period_index = -1;
+ omap_stop_dma(prtd->dma_ch);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock_irq(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd = runtime->private_data;
+ dma_addr_t ptr;
+ snd_pcm_uframes_t offset;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ptr = omap_get_dma_src_pos(prtd->dma_ch);
+ else
+ ptr = omap_get_dma_dst_pos(prtd->dma_ch);
+
+ offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int omap_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct omap_runtime_data *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ spin_lock_init(&prtd->lock);
+ runtime->private_data = prtd;
+
+out:
+ return ret;
+}
+
+static int omap_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ kfree(runtime->private_data);
+ return 0;
+}
+
+static int omap_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+struct snd_pcm_ops omap_pcm_ops = {
+ .open = omap_pcm_open,
+ .close = omap_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = omap_pcm_hw_params,
+ .hw_free = omap_pcm_hw_free,
+ .prepare = omap_pcm_prepare,
+ .trigger = omap_pcm_trigger,
+ .pointer = omap_pcm_pointer,
+ .mmap = omap_pcm_mmap,
+};
+
+static u64 omap_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int omap_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 = omap_pcm_hardware.buffer_bytes_max;
+
+ 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 omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ 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;
+ }
+}
+
+int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &omap_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_32BIT_MASK;
+
+ if (dai->playback.channels_min) {
+ ret = omap_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = omap_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+struct snd_soc_platform omap_soc_platform = {
+ .name = "omap-pcm-audio",
+ .pcm_ops = &omap_pcm_ops,
+ .pcm_new = omap_pcm_new,
+ .pcm_free = omap_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(omap_soc_platform);
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
+MODULE_DESCRIPTION("OMAP PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/omap-pcm.h b/sound/soc/omap/omap-pcm.h
new file mode 100644
index 0000000..e4369bd
--- /dev/null
+++ b/sound/soc/omap/omap-pcm.h
@@ -0,0 +1,35 @@
+/*
+ * omap-pcm.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_PCM_H__
+#define __OMAP_PCM_H__
+
+struct omap_pcm_dma_data {
+ char *name; /* stream identifier */
+ int dma_req; /* DMA request line */
+ unsigned long port_addr; /* transmit/receive register */
+};
+
+extern struct snd_soc_platform omap_soc_platform;
+
+#endif
diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c
new file mode 100644
index 0000000..0fe7337
--- /dev/null
+++ b/sound/soc/omap/osk5912.c
@@ -0,0 +1,232 @@
+/*
+ * osk5912.c -- SoC audio for OSK 5912
+ *
+ * Copyright (C) 2008 Mistral Solutions
+ *
+ * Contact: Arun KS <arunks@mistralsolutions.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <mach/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic23.h"
+
+#define CODEC_CLOCK 12000000
+
+static struct clk *tlv320aic23_mclk;
+
+static int osk_startup(struct snd_pcm_substream *substream)
+{
+ return clk_enable(tlv320aic23_mclk);
+}
+
+static void osk_shutdown(struct snd_pcm_substream *substream)
+{
+ clk_disable(tlv320aic23_mclk);
+}
+
+static int osk_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int err;
+
+ /* Set codec DAI configuration */
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec DAI configuration\n");
+ return err;
+ }
+
+ /* Set cpu DAI configuration */
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (err < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return err;
+ }
+
+ /* Set the codec system clock for DAC and ADC */
+ err =
+ snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+ if (err < 0) {
+ printk(KERN_ERR "can't set codec system clock\n");
+ return err;
+ }
+
+ return err;
+}
+
+static struct snd_soc_ops osk_ops = {
+ .startup = osk_startup,
+ .hw_params = osk_hw_params,
+ .shutdown = osk_shutdown,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+
+ {"MICIN", NULL, "Mic Jack"},
+};
+
+static int osk_tlv320aic23_init(struct snd_soc_codec *codec)
+{
+
+ /* Add osk5912 specific widgets */
+ snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+ ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+ /* Set up osk5912 specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Line In");
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link osk_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .cpu_dai = &omap_mcbsp_dai[0],
+ .codec_dai = &tlv320aic23_dai,
+ .init = osk_tlv320aic23_init,
+ .ops = &osk_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_machine snd_soc_machine_osk = {
+ .name = "OSK5912",
+ .dai_link = &osk_dai,
+ .num_links = 1,
+};
+
+/* Audio subsystem */
+static struct snd_soc_device osk_snd_devdata = {
+ .machine = &snd_soc_machine_osk,
+ .platform = &omap_soc_platform,
+ .codec_dev = &soc_codec_dev_tlv320aic23,
+};
+
+static struct platform_device *osk_snd_device;
+
+static int __init osk_soc_init(void)
+{
+ int err;
+ u32 curRate;
+ struct device *dev;
+
+ if (!(machine_is_omap_osk()))
+ return -ENODEV;
+
+ osk_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!osk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(osk_snd_device, &osk_snd_devdata);
+ osk_snd_devdata.dev = &osk_snd_device->dev;
+ *(unsigned int *)osk_dai.cpu_dai->private_data = 0; /* McBSP1 */
+ err = platform_device_add(osk_snd_device);
+ if (err)
+ goto err1;
+
+ dev = &osk_snd_device->dev;
+
+ tlv320aic23_mclk = clk_get(dev, "mclk");
+ if (IS_ERR(tlv320aic23_mclk)) {
+ printk(KERN_ERR "Could not get mclk clock\n");
+ return -ENODEV;
+ }
+
+ if (clk_get_usecount(tlv320aic23_mclk) > 0) {
+ /* MCLK is already in use */
+ printk(KERN_WARNING
+ "MCLK in use at %d Hz. We change it to %d Hz\n",
+ (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK);
+ }
+
+ /*
+ * Configure 12 MHz output on MCLK.
+ */
+ curRate = (uint) clk_get_rate(tlv320aic23_mclk);
+ if (curRate != CODEC_CLOCK) {
+ if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) {
+ printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n");
+ err = -ECANCELED;
+ goto err1;
+ }
+ }
+
+ printk(KERN_INFO "MCLK = %d [%d], usecount = %d\n",
+ (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK,
+ clk_get_usecount(tlv320aic23_mclk));
+
+ return 0;
+err1:
+ clk_put(tlv320aic23_mclk);
+ platform_device_del(osk_snd_device);
+ platform_device_put(osk_snd_device);
+
+ return err;
+
+}
+
+static void __exit osk_soc_exit(void)
+{
+ platform_device_unregister(osk_snd_device);
+}
+
+module_init(osk_soc_init);
+module_exit(osk_soc_exit);
+
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_DESCRIPTION("ALSA SoC OSK 5912");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
new file mode 100644
index 0000000..f8c1cdd
--- /dev/null
+++ b/sound/soc/pxa/Kconfig
@@ -0,0 +1,77 @@
+config SND_PXA2XX_SOC
+ tristate "SoC Audio for the Intel PXA2xx chip"
+ depends on ARCH_PXA
+ select SND_PXA2XX_LIB
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the PXA2xx AC97, I2S or SSP interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_PXA2XX_AC97
+ tristate
+ select SND_AC97_CODEC
+
+config SND_PXA2XX_SOC_AC97
+ tristate
+ select AC97_BUS
+ select SND_ARM
+ select SND_PXA2XX_LIB_AC97
+ select SND_SOC_AC97_BUS
+
+config SND_PXA2XX_SOC_I2S
+ tristate
+
+config SND_PXA2XX_SOC_CORGI
+ tristate "SoC Audio support for Sharp Zaurus SL-C7x0"
+ depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-C7x0 models (Corgi, Shepherd, Husky).
+
+config SND_PXA2XX_SOC_SPITZ
+ tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
+ depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8750
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita).
+
+config SND_PXA2XX_SOC_POODLE
+ tristate "SoC Audio support for Poodle"
+ depends on SND_PXA2XX_SOC && MACH_POODLE
+ select SND_PXA2XX_SOC_I2S
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-5600 model (Poodle).
+
+config SND_PXA2XX_SOC_TOSA
+ tristate "SoC AC97 Audio support for Tosa"
+ depends on SND_PXA2XX_SOC && MACH_TOSA
+ depends on MFD_TC6393XB
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for SoC audio on Sharp
+ Zaurus SL-C6000x models (Tosa).
+
+config SND_PXA2XX_SOC_E800
+ tristate "SoC AC97 Audio support for e800"
+ depends on SND_PXA2XX_SOC && MACH_E800
+ select SND_SOC_WM9712
+ select SND_PXA2XX_SOC_AC97
+ help
+ Say Y if you want to add support for SoC audio on the
+ Toshiba e800 PDA
+
+config SND_PXA2XX_SOC_EM_X270
+ tristate "SoC Audio support for CompuLab EM-x270"
+ depends on SND_PXA2XX_SOC && MACH_EM_X270
+ select SND_PXA2XX_SOC_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y if you want to add support for SoC audio on
+ CompuLab EM-x270.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile
new file mode 100644
index 0000000..5bc8edf
--- /dev/null
+++ b/sound/soc/pxa/Makefile
@@ -0,0 +1,23 @@
+# PXA Platform Support
+snd-soc-pxa2xx-objs := pxa2xx-pcm.o
+snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
+snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
+obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
+obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
+
+# PXA Machine Support
+snd-soc-corgi-objs := corgi.o
+snd-soc-poodle-objs := poodle.o
+snd-soc-tosa-objs := tosa.o
+snd-soc-e800-objs := e800_wm9712.o
+snd-soc-spitz-objs := spitz.o
+snd-soc-em-x270-objs := em-x270.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
+obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
+obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o
+obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
+obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o
diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c
new file mode 100644
index 0000000..2718eaf
--- /dev/null
+++ b/sound/soc/pxa/corgi.c
@@ -0,0 +1,372 @@
+/*
+ * corgi.c -- SoC audio for Corgi
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/corgi.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-i2s.h"
+
+#define CORGI_HP 0
+#define CORGI_MIC 1
+#define CORGI_LINE 2
+#define CORGI_HEADSET 3
+#define CORGI_HP_OFF 4
+#define CORGI_SPK_ON 0
+#define CORGI_SPK_OFF 1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define CORGI_AUDIO_CLOCK 12288000
+
+static int corgi_jack_func;
+static int corgi_spk_func;
+
+static void corgi_ext_control(struct snd_soc_codec *codec)
+{
+ /* set up jack connection */
+ switch (corgi_jack_func) {
+ case CORGI_HP:
+ /* set = unmute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case CORGI_MIC:
+ /* reset = mute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case CORGI_LINE:
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_enable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case CORGI_HEADSET:
+ gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Headset Jack");
+ break;
+ }
+
+ if (corgi_spk_func == CORGI_SPK_ON)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(codec);
+}
+
+static int corgi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ /* check the jack status at stream startup */
+ corgi_ext_control(codec);
+ return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on corgi */
+static int corgi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ /* set = unmute headphone */
+ gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+ gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+ return 0;
+}
+
+static int corgi_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops corgi_ops = {
+ .startup = corgi_startup,
+ .hw_params = corgi_hw_params,
+ .shutdown = corgi_shutdown,
+};
+
+static int corgi_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = corgi_jack_func;
+ return 0;
+}
+
+static int corgi_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (corgi_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ corgi_jack_func = ucontrol->value.integer.value[0];
+ corgi_ext_control(codec);
+ return 1;
+}
+
+static int corgi_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = corgi_spk_func;
+ return 0;
+}
+
+static int corgi_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (corgi_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ corgi_spk_func = ucontrol->value.integer.value[0];
+ corgi_ext_control(codec);
+ return 1;
+}
+
+static int corgi_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+static int corgi_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
+ return 0;
+}
+
+/* corgi machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event),
+SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event),
+SND_SOC_DAPM_LINE("Line Jack", NULL),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Corgi machine audio map (connections to the codec pins) */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headset Jack - in = micin, out = LHPOUT*/
+ {"Headset Jack", NULL, "LHPOUT"},
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ /* speaker connected to LOUT, ROUT */
+ {"Ext Spk", NULL, "ROUT"},
+ {"Ext Spk", NULL, "LOUT"},
+
+ /* mic is connected to MICIN (via right channel of headphone jack) */
+ {"MICIN", NULL, "Mic Jack"},
+
+ /* Same as the above but no mic bias for line signals */
+ {"MICIN", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum corgi_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_corgi_controls[] = {
+ SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack,
+ corgi_set_jack),
+ SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk,
+ corgi_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int corgi_wm8731_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ snd_soc_dapm_nc_pin(codec, "LLINEIN");
+ snd_soc_dapm_nc_pin(codec, "RLINEIN");
+
+ /* Add corgi specific controls */
+ for (i = 0; i < ARRAY_SIZE(wm8731_corgi_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8731_corgi_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* Add corgi specific widgets */
+ snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ /* Set up corgi specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+/* corgi digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link corgi_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731",
+ .cpu_dai = &pxa_i2s_dai,
+ .codec_dai = &wm8731_dai,
+ .init = corgi_wm8731_init,
+ .ops = &corgi_ops,
+};
+
+/* corgi audio machine driver */
+static struct snd_soc_machine snd_soc_machine_corgi = {
+ .name = "Corgi",
+ .dai_link = &corgi_dai,
+ .num_links = 1,
+};
+
+/* corgi audio private data */
+static struct wm8731_setup_data corgi_wm8731_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1b,
+};
+
+/* corgi audio subsystem */
+static struct snd_soc_device corgi_snd_devdata = {
+ .machine = &snd_soc_machine_corgi,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm8731,
+ .codec_data = &corgi_wm8731_setup,
+};
+
+static struct platform_device *corgi_snd_device;
+
+static int __init corgi_init(void)
+{
+ int ret;
+
+ if (!(machine_is_corgi() || machine_is_shepherd() ||
+ machine_is_husky()))
+ return -ENODEV;
+
+ corgi_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!corgi_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(corgi_snd_device, &corgi_snd_devdata);
+ corgi_snd_devdata.dev = &corgi_snd_device->dev;
+ ret = platform_device_add(corgi_snd_device);
+
+ if (ret)
+ platform_device_put(corgi_snd_device);
+
+ return ret;
+}
+
+static void __exit corgi_exit(void)
+{
+ platform_device_unregister(corgi_snd_device);
+}
+
+module_init(corgi_init);
+module_exit(corgi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Corgi");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c
new file mode 100644
index 0000000..6781c5b
--- /dev/null
+++ b/sound/soc/pxa/e800_wm9712.c
@@ -0,0 +1,89 @@
+/*
+ * e800-wm9712.c -- SoC audio for e800
+ *
+ * Based on tosa.c
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_machine e800;
+
+static struct snd_soc_dai_link e800_dai[] = {
+{
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX],
+ .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX],
+},
+};
+
+static struct snd_soc_machine e800 = {
+ .name = "Toshiba e800",
+ .dai_link = e800_dai,
+ .num_links = ARRAY_SIZE(e800_dai),
+};
+
+static struct snd_soc_device e800_snd_devdata = {
+ .machine = &e800,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm9712,
+};
+
+static struct platform_device *e800_snd_device;
+
+static int __init e800_init(void)
+{
+ int ret;
+
+ if (!machine_is_e800())
+ return -ENODEV;
+
+ e800_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!e800_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(e800_snd_device, &e800_snd_devdata);
+ e800_snd_devdata.dev = &e800_snd_device->dev;
+ ret = platform_device_add(e800_snd_device);
+
+ if (ret)
+ platform_device_put(e800_snd_device);
+
+ return ret;
+}
+
+static void __exit e800_exit(void)
+{
+ platform_device_unregister(e800_snd_device);
+}
+
+module_init(e800_init);
+module_exit(e800_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e800");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c
new file mode 100644
index 0000000..e6ff692
--- /dev/null
+++ b/sound/soc/pxa/em-x270.c
@@ -0,0 +1,102 @@
+/*
+ * em-x270.c -- SoC audio for EM-X270
+ *
+ * Copyright 2007 CompuLab, Ltd.
+ *
+ * Author: Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copied from tosa.c:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_dai_link em_x270_dai[] = {
+ {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI],
+ .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI],
+ },
+ {
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX],
+ .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX],
+ },
+};
+
+static struct snd_soc_machine em_x270 = {
+ .name = "EM-X270",
+ .dai_link = em_x270_dai,
+ .num_links = ARRAY_SIZE(em_x270_dai),
+};
+
+static struct snd_soc_device em_x270_snd_devdata = {
+ .machine = &em_x270,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm9712,
+};
+
+static struct platform_device *em_x270_snd_device;
+
+static int __init em_x270_init(void)
+{
+ int ret;
+
+ if (!machine_is_em_x270())
+ return -ENODEV;
+
+ em_x270_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!em_x270_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(em_x270_snd_device, &em_x270_snd_devdata);
+ em_x270_snd_devdata.dev = &em_x270_snd_device->dev;
+ ret = platform_device_add(em_x270_snd_device);
+
+ if (ret)
+ platform_device_put(em_x270_snd_device);
+
+ return ret;
+}
+
+static void __exit em_x270_exit(void)
+{
+ platform_device_unregister(em_x270_snd_device);
+}
+
+module_init(em_x270_init);
+module_exit(em_x270_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mike Rapoport");
+MODULE_DESCRIPTION("ALSA SoC EM-X270");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c
new file mode 100644
index 0000000..4d9930c
--- /dev/null
+++ b/sound/soc/pxa/poodle.c
@@ -0,0 +1,341 @@
+/*
+ * poodle.c -- SoC audio for Poodle
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/hardware/locomo.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/poodle.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-i2s.h"
+
+#define POODLE_HP 1
+#define POODLE_HP_OFF 0
+#define POODLE_SPK_ON 1
+#define POODLE_SPK_OFF 0
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define POODLE_AUDIO_CLOCK 12288000
+
+static int poodle_jack_func;
+static int poodle_spk_func;
+
+static void poodle_ext_control(struct snd_soc_codec *codec)
+{
+ /* set up jack connection */
+ if (poodle_jack_func == POODLE_HP) {
+ /* set = unmute headphone */
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 1);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 1);
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ } else {
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 0);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 0);
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ }
+
+ /* set the enpoints to their new connetion states */
+ if (poodle_spk_func == POODLE_SPK_ON)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(codec);
+}
+
+static int poodle_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ /* check the jack status at stream startup */
+ poodle_ext_control(codec);
+ return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on poodle */
+static void poodle_shutdown(struct snd_pcm_substream *substream)
+{
+ /* set = unmute headphone */
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 1);
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 1);
+}
+
+static int poodle_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops poodle_ops = {
+ .startup = poodle_startup,
+ .hw_params = poodle_hw_params,
+ .shutdown = poodle_shutdown,
+};
+
+static int poodle_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = poodle_jack_func;
+ return 0;
+}
+
+static int poodle_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (poodle_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ poodle_jack_func = ucontrol->value.integer.value[0];
+ poodle_ext_control(codec);
+ return 1;
+}
+
+static int poodle_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = poodle_spk_func;
+ return 0;
+}
+
+static int poodle_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (poodle_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ poodle_spk_func = ucontrol->value.integer.value[0];
+ poodle_ext_control(codec);
+ return 1;
+}
+
+static int poodle_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 0);
+ else
+ locomo_gpio_write(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 1);
+
+ return 0;
+}
+
+/* poodle machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event),
+};
+
+/* Corgi machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone Jack", NULL, "LHPOUT"},
+ {"Headphone Jack", NULL, "RHPOUT"},
+
+ /* speaker connected to LOUT, ROUT */
+ {"Ext Spk", NULL, "ROUT"},
+ {"Ext Spk", NULL, "LOUT"},
+};
+
+static const char *jack_function[] = {"Off", "Headphone"};
+static const char *spk_function[] = {"Off", "On"};
+static const struct soc_enum poodle_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_poodle_controls[] = {
+ SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack,
+ poodle_set_jack),
+ SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
+ poodle_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int poodle_wm8731_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ snd_soc_dapm_nc_pin(codec, "LLINEIN");
+ snd_soc_dapm_nc_pin(codec, "RLINEIN");
+ snd_soc_dapm_enable_pin(codec, "MICIN");
+
+ /* Add poodle specific controls */
+ for (i = 0; i < ARRAY_SIZE(wm8731_poodle_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8731_poodle_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* Add poodle specific widgets */
+ snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+ ARRAY_SIZE(wm8731_dapm_widgets));
+
+ /* Set up poodle specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+/* poodle digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link poodle_dai = {
+ .name = "WM8731",
+ .stream_name = "WM8731",
+ .cpu_dai = &pxa_i2s_dai,
+ .codec_dai = &wm8731_dai,
+ .init = poodle_wm8731_init,
+ .ops = &poodle_ops,
+};
+
+/* poodle audio machine driver */
+static struct snd_soc_machine snd_soc_machine_poodle = {
+ .name = "Poodle",
+ .dai_link = &poodle_dai,
+ .num_links = 1,
+};
+
+/* poodle audio private data */
+static struct wm8731_setup_data poodle_wm8731_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1b,
+};
+
+/* poodle audio subsystem */
+static struct snd_soc_device poodle_snd_devdata = {
+ .machine = &snd_soc_machine_poodle,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm8731,
+ .codec_data = &poodle_wm8731_setup,
+};
+
+static struct platform_device *poodle_snd_device;
+
+static int __init poodle_init(void)
+{
+ int ret;
+
+ if (!machine_is_poodle())
+ return -ENODEV;
+
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_AMP_ON, 0);
+ /* should we mute HP at startup - burning power ?*/
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_L, 0);
+ locomo_gpio_set_dir(&poodle_locomo_device.dev,
+ POODLE_LOCOMO_GPIO_MUTE_R, 0);
+
+ poodle_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!poodle_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(poodle_snd_device, &poodle_snd_devdata);
+ poodle_snd_devdata.dev = &poodle_snd_device->dev;
+ ret = platform_device_add(poodle_snd_device);
+
+ if (ret)
+ platform_device_put(poodle_snd_device);
+
+ return ret;
+}
+
+static void __exit poodle_exit(void)
+{
+ platform_device_unregister(poodle_snd_device);
+}
+
+module_init(poodle_init);
+module_exit(poodle_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Poodle");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c
new file mode 100644
index 0000000..a7a3a9c
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-ac97.c
@@ -0,0 +1,232 @@
+/*
+ * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
+ *
+ * Author: Nicolas Pitre
+ * Created: Dec 02, 2004
+ * Copyright: MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/pxa-regs.h>
+
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-ac97.h"
+
+static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ pxa2xx_ac97_try_warm_reset(ac97);
+
+ pxa2xx_ac97_finish_reset(ac97);
+}
+
+static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ pxa2xx_ac97_try_cold_reset(ac97);
+
+ pxa2xx_ac97_finish_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = pxa2xx_ac97_read,
+ .write = pxa2xx_ac97_write,
+ .warm_reset = pxa2xx_ac97_warm_reset,
+ .reset = pxa2xx_ac97_cold_reset,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_out = {
+ .name = "AC97 PCM Stereo out",
+ .dev_addr = __PREG(PCDR),
+ .drcmr = &DRCMR(12),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_in = {
+ .name = "AC97 PCM Stereo in",
+ .dev_addr = __PREG(PCDR),
+ .drcmr = &DRCMR(11),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_out = {
+ .name = "AC97 Aux PCM (Slot 5) Mono out",
+ .dev_addr = __PREG(MODR),
+ .drcmr = &DRCMR(10),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_in = {
+ .name = "AC97 Aux PCM (Slot 5) Mono in",
+ .dev_addr = __PREG(MODR),
+ .drcmr = &DRCMR(9),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_mic_mono_in = {
+ .name = "AC97 Mic PCM (Slot 6) Mono in",
+ .dev_addr = __PREG(MCDR),
+ .drcmr = &DRCMR(8),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+#ifdef CONFIG_PM
+static int pxa2xx_ac97_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_suspend();
+}
+
+static int pxa2xx_ac97_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_resume();
+}
+
+#else
+#define pxa2xx_ac97_suspend NULL
+#define pxa2xx_ac97_resume NULL
+#endif
+
+static int pxa2xx_ac97_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ return pxa2xx_ac97_hw_probe(pdev);
+}
+
+static void pxa2xx_ac97_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ pxa2xx_ac97_hw_remove(pdev);
+}
+
+static int pxa2xx_ac97_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_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->dma_data = &pxa2xx_ac97_pcm_stereo_out;
+ else
+ cpu_dai->dma_data = &pxa2xx_ac97_pcm_stereo_in;
+
+ return 0;
+}
+
+static int pxa2xx_ac97_hw_aux_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->dma_data = &pxa2xx_ac97_pcm_aux_mono_out;
+ else
+ cpu_dai->dma_data = &pxa2xx_ac97_pcm_aux_mono_in;
+
+ return 0;
+}
+
+static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ else
+ cpu_dai->dma_data = &pxa2xx_ac97_pcm_mic_mono_in;
+
+ return 0;
+}
+
+#define PXA2XX_AC97_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)
+
+/*
+ * There is only 1 physical AC97 interface for pxa2xx, but it
+ * has extra fifo's that can be used for aux DACs and ADCs.
+ */
+struct snd_soc_dai pxa_ac97_dai[] = {
+{
+ .name = "pxa2xx-ac97",
+ .id = 0,
+ .type = SND_SOC_DAI_AC97,
+ .probe = pxa2xx_ac97_probe,
+ .remove = pxa2xx_ac97_remove,
+ .suspend = pxa2xx_ac97_suspend,
+ .resume = pxa2xx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = pxa2xx_ac97_hw_params,},
+},
+{
+ .name = "pxa2xx-ac97-aux",
+ .id = 1,
+ .type = SND_SOC_DAI_AC97,
+ .playback = {
+ .stream_name = "AC97 Aux Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Aux Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = pxa2xx_ac97_hw_aux_params,},
+},
+{
+ .name = "pxa2xx-ac97-mic",
+ .id = 2,
+ .type = SND_SOC_DAI_AC97,
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = PXA2XX_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = pxa2xx_ac97_hw_mic_params,},
+},
+};
+
+EXPORT_SYMBOL_GPL(pxa_ac97_dai);
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa2xx-ac97.h b/sound/soc/pxa/pxa2xx-ac97.h
new file mode 100644
index 0000000..e390de8
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-ac97.h
@@ -0,0 +1,22 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_AC97_H
+#define _PXA2XX_AC97_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_AC97_HIFI 0
+#define PXA2XX_DAI_AC97_AUX 1
+#define PXA2XX_DAI_AC97_MIC 2
+
+extern struct snd_soc_dai pxa_ac97_dai[3];
+
+/* platform data */
+extern struct snd_ac97_bus_ops pxa2xx_ac97_ops;
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c
new file mode 100644
index 0000000..e758034
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.c
@@ -0,0 +1,410 @@
+/*
+ * pxa2xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * lrg@slimlogic.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/pxa-regs.h>
+#include <mach/pxa2xx-gpio.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-i2s.h"
+
+struct pxa2xx_gpio {
+ u32 sys;
+ u32 rx;
+ u32 tx;
+ u32 clk;
+ u32 frm;
+};
+
+/*
+ * I2S Controller Register and Bit Definitions
+ */
+#define SACR0 __REG(0x40400000) /* Global Control Register */
+#define SACR1 __REG(0x40400004) /* Serial Audio I 2 S/MSB-Justified Control Register */
+#define SASR0 __REG(0x4040000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */
+#define SAIMR __REG(0x40400014) /* Serial Audio Interrupt Mask Register */
+#define SAICR __REG(0x40400018) /* Serial Audio Interrupt Clear Register */
+#define SADIV __REG(0x40400060) /* Audio Clock Divider Register. */
+#define SADR __REG(0x40400080) /* Serial Audio Data Register (TX and RX FIFO access Register). */
+
+#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */
+#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */
+#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */
+#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */
+#define SACR0_ENB (1 << 0) /* Enable I2S Link */
+#define SACR1_ENLBF (1 << 5) /* Enable Loopback */
+#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */
+#define SACR1_DREC (1 << 3) /* Disable Recording Function */
+#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */
+
+#define SASR0_I2SOFF (1 << 7) /* Controller Status */
+#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */
+#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */
+#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */
+#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */
+#define SASR0_BSY (1 << 2) /* I2S Busy */
+#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */
+#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */
+
+#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */
+#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */
+
+#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */
+#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */
+#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */
+#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */
+
+struct pxa_i2s_port {
+ u32 sadiv;
+ u32 sacr0;
+ u32 sacr1;
+ u32 saimr;
+ int master;
+ u32 fmt;
+};
+static struct pxa_i2s_port pxa_i2s;
+static struct clk *clk_i2s;
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = {
+ .name = "I2S PCM Stereo out",
+ .dev_addr = __PREG(SADR),
+ .drcmr = &DRCMR(3),
+ .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = {
+ .name = "I2S PCM Stereo in",
+ .dev_addr = __PREG(SADR),
+ .drcmr = &DRCMR(2),
+ .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC |
+ DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_gpio gpio_bus[] = {
+ { /* I2S SoC Slave */
+ .rx = GPIO29_SDATA_IN_I2S_MD,
+ .tx = GPIO30_SDATA_OUT_I2S_MD,
+ .clk = GPIO28_BITCLK_IN_I2S_MD,
+ .frm = GPIO31_SYNC_I2S_MD,
+ },
+ { /* I2S SoC Master */
+ .rx = GPIO29_SDATA_IN_I2S_MD,
+ .tx = GPIO30_SDATA_OUT_I2S_MD,
+ .clk = GPIO28_BITCLK_OUT_I2S_MD,
+ .frm = GPIO31_SYNC_I2S_MD,
+ },
+};
+
+static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (IS_ERR(clk_i2s))
+ return PTR_ERR(clk_i2s);
+
+ if (!cpu_dai->active) {
+ SACR0 |= SACR0_RST;
+ SACR0 = 0;
+ }
+
+ return 0;
+}
+
+/* wait for I2S controller to be ready */
+static int pxa_i2s_wait(void)
+{
+ int i;
+
+ /* flush the Rx FIFO */
+ for(i = 0; i < 16; i++)
+ SADR;
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ pxa_i2s.fmt = 0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ pxa_i2s.fmt = SACR1_AMSL;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ pxa_i2s.master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ pxa_i2s.master = 0;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ if (clk_id != PXA2XX_I2S_SYSCLK)
+ return -ENODEV;
+
+ if (pxa_i2s.master && dir == SND_SOC_CLOCK_OUT)
+ pxa_gpio_mode(gpio_bus[pxa_i2s.master].sys);
+
+ return 0;
+}
+
+static int pxa2xx_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_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ pxa_gpio_mode(gpio_bus[pxa_i2s.master].rx);
+ pxa_gpio_mode(gpio_bus[pxa_i2s.master].tx);
+ pxa_gpio_mode(gpio_bus[pxa_i2s.master].frm);
+ pxa_gpio_mode(gpio_bus[pxa_i2s.master].clk);
+ BUG_ON(IS_ERR(clk_i2s));
+ clk_enable(clk_i2s);
+ pxa_i2s_wait();
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_out;
+ else
+ cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_in;
+
+ /* is port used by another stream */
+ if (!(SACR0 & SACR0_ENB)) {
+
+ SACR0 = 0;
+ SACR1 = 0;
+ if (pxa_i2s.master)
+ SACR0 |= SACR0_BCKD;
+
+ SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1);
+ SACR1 |= pxa_i2s.fmt;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ SAIMR |= SAIMR_TFS;
+ else
+ SAIMR |= SAIMR_RFS;
+
+ switch (params_rate(params)) {
+ case 8000:
+ SADIV = 0x48;
+ break;
+ case 11025:
+ SADIV = 0x34;
+ break;
+ case 16000:
+ SADIV = 0x24;
+ break;
+ case 22050:
+ SADIV = 0x1a;
+ break;
+ case 44100:
+ SADIV = 0xd;
+ break;
+ case 48000:
+ SADIV = 0xc;
+ break;
+ case 96000: /* not in manual and possibly slightly inaccurate */
+ SADIV = 0x6;
+ break;
+ }
+
+ return 0;
+}
+
+static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ SACR0 |= SACR0_ENB;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ SACR1 |= SACR1_DRPL;
+ SAIMR &= ~SAIMR_TFS;
+ } else {
+ SACR1 |= SACR1_DREC;
+ SAIMR &= ~SAIMR_RFS;
+ }
+
+ if (SACR1 & (SACR1_DREC | SACR1_DRPL)) {
+ SACR0 &= ~SACR0_ENB;
+ pxa_i2s_wait();
+ clk_disable(clk_i2s);
+ }
+
+ clk_put(clk_i2s);
+}
+
+#ifdef CONFIG_PM
+static int pxa2xx_i2s_suspend(struct platform_device *dev,
+ struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* store registers */
+ pxa_i2s.sacr0 = SACR0;
+ pxa_i2s.sacr1 = SACR1;
+ pxa_i2s.saimr = SAIMR;
+ pxa_i2s.sadiv = SADIV;
+
+ /* deactivate link */
+ SACR0 &= ~SACR0_ENB;
+ pxa_i2s_wait();
+ return 0;
+}
+
+static int pxa2xx_i2s_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ pxa_i2s_wait();
+
+ SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB;
+ SACR1 = pxa_i2s.sacr1;
+ SAIMR = pxa_i2s.saimr;
+ SADIV = pxa_i2s.sadiv;
+ SACR0 |= SACR0_ENB;
+
+ return 0;
+}
+
+#else
+#define pxa2xx_i2s_suspend NULL
+#define pxa2xx_i2s_resume NULL
+#endif
+
+#define PXA2XX_I2S_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_96000)
+
+struct snd_soc_dai pxa_i2s_dai = {
+ .name = "pxa2xx-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .suspend = pxa2xx_i2s_suspend,
+ .resume = pxa2xx_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = PXA2XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .startup = pxa2xx_i2s_startup,
+ .shutdown = pxa2xx_i2s_shutdown,
+ .trigger = pxa2xx_i2s_trigger,
+ .hw_params = pxa2xx_i2s_hw_params,},
+ .dai_ops = {
+ .set_fmt = pxa2xx_i2s_set_dai_fmt,
+ .set_sysclk = pxa2xx_i2s_set_dai_sysclk,
+ },
+};
+
+EXPORT_SYMBOL_GPL(pxa_i2s_dai);
+
+static int pxa2xx_i2s_probe(struct platform_device *dev)
+{
+ clk_i2s = clk_get(&dev->dev, "I2SCLK");
+ return IS_ERR(clk_i2s) ? PTR_ERR(clk_i2s) : 0;
+}
+
+static int __devexit pxa2xx_i2s_remove(struct platform_device *dev)
+{
+ clk_put(clk_i2s);
+ clk_i2s = ERR_PTR(-ENOENT);
+ return 0;
+}
+
+static struct platform_driver pxa2xx_i2s_driver = {
+ .probe = pxa2xx_i2s_probe,
+ .remove = __devexit_p(pxa2xx_i2s_remove),
+
+ .driver = {
+ .name = "pxa2xx-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pxa2xx_i2s_init(void)
+{
+ if (cpu_is_pxa27x())
+ gpio_bus[1].sys = GPIO113_I2S_SYSCLK_MD;
+ else
+ gpio_bus[1].sys = GPIO32_SYSCLK_I2S_MD;
+
+ clk_i2s = ERR_PTR(-ENOENT);
+ return platform_driver_register(&pxa2xx_i2s_driver);
+}
+
+static void __exit pxa2xx_i2s_exit(void)
+{
+ platform_driver_unregister(&pxa2xx_i2s_driver);
+}
+
+module_init(pxa2xx_i2s_init);
+module_exit(pxa2xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa2xx-i2s.h b/sound/soc/pxa/pxa2xx-i2s.h
new file mode 100644
index 0000000..e2def44
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-i2s.h
@@ -0,0 +1,20 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-i2s.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_I2S_H
+#define _PXA2XX_I2S_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_I2S 0
+
+/* I2S clock */
+#define PXA2XX_I2S_SYSCLK 0
+
+extern struct snd_soc_dai pxa_i2s_dai;
+
+#endif
diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c
new file mode 100644
index 0000000..afcd892
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-pcm.c
@@ -0,0 +1,123 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: (C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include "pxa2xx-pcm.h"
+#include "../../arm/pxa2xx-pcm.h"
+
+static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct pxa2xx_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pxa2xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data;
+ int ret;
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* this may get called several times by oss emulation
+ * with different params */
+ if (prtd->params == NULL) {
+ prtd->params = dma;
+ ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+ pxa2xx_pcm_dma_irq, substream);
+ if (ret < 0)
+ return ret;
+ prtd->dma_ch = ret;
+ } else if (prtd->params != dma) {
+ pxa_free_dma(prtd->dma_ch);
+ prtd->params = dma;
+ ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+ pxa2xx_pcm_dma_irq, substream);
+ if (ret < 0)
+ return ret;
+ prtd->dma_ch = ret;
+ }
+
+ return __pxa2xx_pcm_hw_params(substream, params);
+}
+
+static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
+
+ __pxa2xx_pcm_hw_free(substream);
+
+ if (prtd->dma_ch) {
+ pxa_free_dma(prtd->dma_ch);
+ prtd->dma_ch = 0;
+ }
+
+ return 0;
+}
+
+struct snd_pcm_ops pxa2xx_pcm_ops = {
+ .open = __pxa2xx_pcm_open,
+ .close = __pxa2xx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = pxa2xx_pcm_hw_params,
+ .hw_free = pxa2xx_pcm_hw_free,
+ .prepare = __pxa2xx_pcm_prepare,
+ .trigger = pxa2xx_pcm_trigger,
+ .pointer = pxa2xx_pcm_pointer,
+ .mmap = pxa2xx_pcm_mmap,
+};
+
+static u64 pxa2xx_pcm_dmamask = DMA_32BIT_MASK;
+
+static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &pxa2xx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_32BIT_MASK;
+
+ if (dai->playback.channels_min) {
+ ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform pxa2xx_soc_platform = {
+ .name = "pxa2xx-audio",
+ .pcm_ops = &pxa2xx_pcm_ops,
+ .pcm_new = pxa2xx_soc_pcm_new,
+ .pcm_free = pxa2xx_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(pxa2xx_soc_platform);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/pxa2xx-pcm.h b/sound/soc/pxa/pxa2xx-pcm.h
new file mode 100644
index 0000000..60c3b20
--- /dev/null
+++ b/sound/soc/pxa/pxa2xx-pcm.h
@@ -0,0 +1,19 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.h -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author: Nicolas Pitre
+ * Created: Nov 30, 2004
+ * Copyright: MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_PCM_H
+#define _PXA2XX_PCM_H
+
+/* platform data */
+extern struct snd_soc_platform pxa2xx_soc_platform;
+
+#endif
diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c
new file mode 100644
index 0000000..d307b67
--- /dev/null
+++ b/sound/soc/pxa/spitz.c
@@ -0,0 +1,375 @@
+/*
+ * spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/spitz.h>
+#include "../codecs/wm8750.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-i2s.h"
+
+#define SPITZ_HP 0
+#define SPITZ_MIC 1
+#define SPITZ_LINE 2
+#define SPITZ_HEADSET 3
+#define SPITZ_HP_OFF 4
+#define SPITZ_SPK_ON 0
+#define SPITZ_SPK_OFF 1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define SPITZ_AUDIO_CLOCK 12288000
+
+static int spitz_jack_func;
+static int spitz_spk_func;
+
+static void spitz_ext_control(struct snd_soc_codec *codec)
+{
+ if (spitz_spk_func == SPITZ_SPK_ON)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ /* set up jack connection */
+ switch (spitz_jack_func) {
+ case SPITZ_HP:
+ /* enable and unmute hp jack, disable mic bias */
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 1);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+ break;
+ case SPITZ_MIC:
+ /* enable mic jack and bias, mute hp */
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ case SPITZ_LINE:
+ /* enable line jack, disable mic bias and mute hp */
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_enable_pin(codec, "Line Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ case SPITZ_HEADSET:
+ /* enable and unmute headset jack enable mic bias, mute L hp */
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ snd_soc_dapm_enable_pin(codec, "Headset Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+ break;
+ case SPITZ_HP_OFF:
+
+ /* jack removed, everything off */
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ snd_soc_dapm_disable_pin(codec, "Mic Jack");
+ snd_soc_dapm_disable_pin(codec, "Line Jack");
+ gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+ gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+ break;
+ }
+ snd_soc_dapm_sync(codec);
+}
+
+static int spitz_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ /* check the jack status at stream startup */
+ spitz_ext_control(codec);
+ return 0;
+}
+
+static int spitz_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the I2S system clock as input (unused) */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops spitz_ops = {
+ .startup = spitz_startup,
+ .hw_params = spitz_hw_params,
+};
+
+static int spitz_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spitz_jack_func;
+ return 0;
+}
+
+static int spitz_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ spitz_jack_func = ucontrol->value.integer.value[0];
+ spitz_ext_control(codec);
+ return 1;
+}
+
+static int spitz_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spitz_spk_func;
+ return 0;
+}
+
+static int spitz_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (spitz_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ spitz_spk_func = ucontrol->value.integer.value[0];
+ spitz_ext_control(codec);
+ return 1;
+}
+
+static int spitz_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (machine_is_borzoi() || machine_is_spitz())
+ gpio_set_value(SPITZ_GPIO_MIC_BIAS,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ if (machine_is_akita())
+ gpio_set_value(AKITA_GPIO_MIC_BIAS,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+/* spitz machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_LINE("Line Jack", NULL),
+
+ /* headset is a mic and mono headphone */
+ SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Spitz machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LOUT1, ROUT1 */
+ {"Headphone Jack", NULL, "LOUT1"},
+ {"Headphone Jack", NULL, "ROUT1"},
+
+ /* headset connected to ROUT1 and LINPUT1 with bias (def below) */
+ {"Headset Jack", NULL, "ROUT1"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Ext Spk", NULL , "ROUT2"},
+ {"Ext Spk", NULL , "LOUT2"},
+
+ /* mic is connected to input 1 - with bias */
+ {"LINPUT1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic Jack"},
+
+ /* line is connected to input 1 - no bias */
+ {"LINPUT1", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum spitz_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
+ SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
+ spitz_set_jack),
+ SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
+ spitz_set_spk),
+};
+
+/*
+ * Logic for a wm8750 as connected on a Sharp SL-Cxx00 Device
+ */
+static int spitz_wm8750_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ /* NC codec pins */
+ snd_soc_dapm_nc_pin(codec, "RINPUT1");
+ snd_soc_dapm_nc_pin(codec, "LINPUT2");
+ snd_soc_dapm_nc_pin(codec, "RINPUT2");
+ snd_soc_dapm_nc_pin(codec, "LINPUT3");
+ snd_soc_dapm_nc_pin(codec, "RINPUT3");
+ snd_soc_dapm_nc_pin(codec, "OUT3");
+ snd_soc_dapm_nc_pin(codec, "MONO1");
+
+ /* Add spitz specific controls */
+ for (i = 0; i < ARRAY_SIZE(wm8750_spitz_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8750_spitz_controls[i], codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* Add spitz specific widgets */
+ snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+ ARRAY_SIZE(wm8750_dapm_widgets));
+
+ /* Set up spitz specific audio paths */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+/* spitz digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link spitz_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .cpu_dai = &pxa_i2s_dai,
+ .codec_dai = &wm8750_dai,
+ .init = spitz_wm8750_init,
+ .ops = &spitz_ops,
+};
+
+/* spitz audio machine driver */
+static struct snd_soc_machine snd_soc_machine_spitz = {
+ .name = "Spitz",
+ .dai_link = &spitz_dai,
+ .num_links = 1,
+};
+
+/* spitz audio private data */
+static struct wm8750_setup_data spitz_wm8750_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1b,
+};
+
+/* spitz audio subsystem */
+static struct snd_soc_device spitz_snd_devdata = {
+ .machine = &snd_soc_machine_spitz,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm8750,
+ .codec_data = &spitz_wm8750_setup,
+};
+
+static struct platform_device *spitz_snd_device;
+
+static int __init spitz_init(void)
+{
+ int ret;
+
+ if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita()))
+ return -ENODEV;
+
+ spitz_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!spitz_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(spitz_snd_device, &spitz_snd_devdata);
+ spitz_snd_devdata.dev = &spitz_snd_device->dev;
+ ret = platform_device_add(spitz_snd_device);
+
+ if (ret)
+ platform_device_put(spitz_snd_device);
+
+ return ret;
+}
+
+static void __exit spitz_exit(void)
+{
+ platform_device_unregister(spitz_snd_device);
+}
+
+module_init(spitz_init);
+module_exit(spitz_exit);
+
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Spitz");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c
new file mode 100644
index 0000000..afefe41
--- /dev/null
+++ b/sound/soc/pxa/tosa.c
@@ -0,0 +1,292 @@
+/*
+ * tosa.c -- SoC audio for Tosa
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GPIO's
+ * 1 - Jack Insertion
+ * 5 - Hookswitch (headset answer/hang up switch)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/tosa.h>
+#include <mach/pxa-regs.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-pcm.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_machine tosa;
+
+#define TOSA_HP 0
+#define TOSA_MIC_INT 1
+#define TOSA_HEADSET 2
+#define TOSA_HP_OFF 3
+#define TOSA_SPK_ON 0
+#define TOSA_SPK_OFF 1
+
+static int tosa_jack_func;
+static int tosa_spk_func;
+
+static void tosa_ext_control(struct snd_soc_codec *codec)
+{
+ /* set up jack connection */
+ switch (tosa_jack_func) {
+ case TOSA_HP:
+ snd_soc_dapm_disable_pin(codec, "Mic (Internal)");
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case TOSA_MIC_INT:
+ snd_soc_dapm_enable_pin(codec, "Mic (Internal)");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_disable_pin(codec, "Headset Jack");
+ break;
+ case TOSA_HEADSET:
+ snd_soc_dapm_disable_pin(codec, "Mic (Internal)");
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+ snd_soc_dapm_enable_pin(codec, "Headset Jack");
+ break;
+ }
+
+ if (tosa_spk_func == TOSA_SPK_ON)
+ snd_soc_dapm_enable_pin(codec, "Speaker");
+ else
+ snd_soc_dapm_disable_pin(codec, "Speaker");
+
+ snd_soc_dapm_sync(codec);
+}
+
+static int tosa_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->codec;
+
+ /* check the jack status at stream startup */
+ tosa_ext_control(codec);
+ return 0;
+}
+
+static struct snd_soc_ops tosa_ops = {
+ .startup = tosa_startup,
+};
+
+static int tosa_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tosa_jack_func;
+ return 0;
+}
+
+static int tosa_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (tosa_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tosa_jack_func = ucontrol->value.integer.value[0];
+ tosa_ext_control(codec);
+ return 1;
+}
+
+static int tosa_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = tosa_spk_func;
+ return 0;
+}
+
+static int tosa_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (tosa_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ tosa_spk_func = ucontrol->value.integer.value[0];
+ tosa_ext_control(codec);
+ return 1;
+}
+
+/* tosa dapm event handlers */
+static int tosa_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0);
+ return 0;
+}
+
+/* tosa machine dapm widgets */
+static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/* tosa audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to HPOUTL, HPOUTR */
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Headphone Jack", NULL, "HPOUTR"},
+
+ /* ext speaker connected to LOUT2, ROUT2 */
+ {"Speaker", NULL, "LOUT2"},
+ {"Speaker", NULL, "ROUT2"},
+
+ /* internal mic is connected to mic1, mic2 differential - with bias */
+ {"MIC1", NULL, "Mic Bias"},
+ {"MIC2", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic (Internal)"},
+
+ /* headset is connected to HPOUTR, and LINEINR with bias */
+ {"Headset Jack", NULL, "HPOUTR"},
+ {"LINEINR", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+ "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum tosa_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new tosa_controls[] = {
+ SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack,
+ tosa_set_jack),
+ SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk,
+ tosa_set_spk),
+};
+
+static int tosa_ac97_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ snd_soc_dapm_nc_pin(codec, "OUT3");
+ snd_soc_dapm_nc_pin(codec, "MONOOUT");
+
+ /* add tosa specific controls */
+ for (i = 0; i < ARRAY_SIZE(tosa_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&tosa_controls[i],codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* add tosa specific widgets */
+ snd_soc_dapm_new_controls(codec, tosa_dapm_widgets,
+ ARRAY_SIZE(tosa_dapm_widgets));
+
+ /* set up tosa specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+static struct snd_soc_dai_link tosa_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI],
+ .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI],
+ .init = tosa_ac97_init,
+ .ops = &tosa_ops,
+},
+{
+ .name = "AC97 Aux",
+ .stream_name = "AC97 Aux",
+ .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX],
+ .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX],
+ .ops = &tosa_ops,
+},
+};
+
+static struct snd_soc_machine tosa = {
+ .name = "Tosa",
+ .dai_link = tosa_dai,
+ .num_links = ARRAY_SIZE(tosa_dai),
+};
+
+static struct snd_soc_device tosa_snd_devdata = {
+ .machine = &tosa,
+ .platform = &pxa2xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm9712,
+};
+
+static struct platform_device *tosa_snd_device;
+
+static int __init tosa_init(void)
+{
+ int ret;
+
+ if (!machine_is_tosa())
+ return -ENODEV;
+
+ ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack");
+ if (ret)
+ return ret;
+ gpio_direction_output(TOSA_GPIO_L_MUTE, 0);
+
+ tosa_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!tosa_snd_device) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ platform_set_drvdata(tosa_snd_device, &tosa_snd_devdata);
+ tosa_snd_devdata.dev = &tosa_snd_device->dev;
+ ret = platform_device_add(tosa_snd_device);
+
+ if (!ret)
+ return 0;
+
+ platform_device_put(tosa_snd_device);
+
+err_alloc:
+ gpio_free(TOSA_GPIO_L_MUTE);
+
+ return ret;
+}
+
+static void __exit tosa_exit(void)
+{
+ platform_device_unregister(tosa_snd_device);
+ gpio_free(TOSA_GPIO_L_MUTE);
+}
+
+module_init(tosa_init);
+module_exit(tosa_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Tosa");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig
new file mode 100644
index 0000000..b9f2353
--- /dev/null
+++ b/sound/soc/s3c24xx/Kconfig
@@ -0,0 +1,46 @@
+config SND_S3C24XX_SOC
+ tristate "SoC Audio for the Samsung S3C24XX chips"
+ depends on ARCH_S3C2410
+ 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
+
+config SND_S3C2412_SOC_I2S
+ tristate
+
+config SND_S3C2443_SOC_AC97
+ tristate
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+
+config SND_S3C24XX_SOC_NEO1973_WM8753
+ tristate "SoC I2S Audio support for NEO1973 - WM8753"
+ depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01
+ select SND_S3C24XX_SOC_I2S
+ select SND_SOC_WM8753
+ help
+ Say Y if you want to add support for SoC audio on smdk2440
+ with the WM8753.
+
+config SND_S3C24XX_SOC_SMDK2443_WM9710
+ tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
+ depends on SND_S3C24XX_SOC && MACH_SMDK2443
+ select SND_S3C2443_SOC_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ Say Y if you want to add support for SoC audio on smdk2443
+ with the WM9710.
+
+config SND_S3C24XX_SOC_LN2440SBC_ALC650
+ tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
+ depends on SND_S3C24XX_SOC
+ select SND_S3C2443_SOC_AC97
+ select SND_SOC_AC97_CODEC
+ help
+ Say Y if you want to add support for SoC audio on ln2440sbc
+ with the ALC650.
+
diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile
new file mode 100644
index 0000000..0aa5fb0
--- /dev/null
+++ b/sound/soc/s3c24xx/Makefile
@@ -0,0 +1,19 @@
+# S3c24XX Platform Support
+snd-soc-s3c24xx-objs := s3c24xx-pcm.o
+snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
+snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
+snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o
+
+obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o
+obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o
+obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o
+obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
+
+# S3C24XX Machine Support
+snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
+snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
+snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
+
+obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
+obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o
+obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o
diff --git a/sound/soc/s3c24xx/lm4857.h b/sound/soc/s3c24xx/lm4857.h
new file mode 100644
index 0000000..0cf5b70
--- /dev/null
+++ b/sound/soc/s3c24xx/lm4857.h
@@ -0,0 +1,32 @@
+/*
+ * lm4857.h -- ALSA Soc Audio Layer
+ *
+ * Copyright 2007 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
+ * 18th Jun 2007 Initial version.
+ */
+
+#ifndef LM4857_H_
+#define LM4857_H_
+
+/* The register offsets in the cache array */
+#define LM4857_MVOL 0
+#define LM4857_LVOL 1
+#define LM4857_RVOL 2
+#define LM4857_CTRL 3
+
+/* the shifts required to set these bits */
+#define LM4857_3D 5
+#define LM4857_WAKEUP 5
+#define LM4857_EPGAIN 4
+
+#endif /*LM4857_H_*/
+
diff --git a/sound/soc/s3c24xx/ln2440sbc_alc650.c b/sound/soc/s3c24xx/ln2440sbc_alc650.c
new file mode 100644
index 0000000..4eab2c1
--- /dev/null
+++ b/sound/soc/s3c24xx/ln2440sbc_alc650.c
@@ -0,0 +1,85 @@
+/*
+ * SoC audio for ln2440sbc
+ *
+ * Copyright 2007 KonekTel, a.s.
+ * Author: Ivan Kuten
+ * ivan.kuten@promwad.com
+ *
+ * Heavily based on smdk2443_wm9710.c
+ * Copyright 2007 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 version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/ac97.h"
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-ac97.h"
+
+static struct snd_soc_machine ln2440sbc;
+
+static struct snd_soc_dai_link ln2440sbc_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &s3c2443_ac97_dai[0],
+ .codec_dai = &ac97_dai,
+},
+};
+
+static struct snd_soc_machine ln2440sbc = {
+ .name = "LN2440SBC",
+ .dai_link = ln2440sbc_dai,
+ .num_links = ARRAY_SIZE(ln2440sbc_dai),
+};
+
+static struct snd_soc_device ln2440sbc_snd_ac97_devdata = {
+ .machine = &ln2440sbc,
+ .platform = &s3c24xx_soc_platform,
+ .codec_dev = &soc_codec_dev_ac97,
+};
+
+static struct platform_device *ln2440sbc_snd_ac97_device;
+
+static int __init ln2440sbc_init(void)
+{
+ int ret;
+
+ ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!ln2440sbc_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(ln2440sbc_snd_ac97_device,
+ &ln2440sbc_snd_ac97_devdata);
+ ln2440sbc_snd_ac97_devdata.dev = &ln2440sbc_snd_ac97_device->dev;
+ ret = platform_device_add(ln2440sbc_snd_ac97_device);
+
+ if (ret)
+ platform_device_put(ln2440sbc_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit ln2440sbc_exit(void)
+{
+ platform_device_unregister(ln2440sbc_snd_ac97_device);
+}
+
+module_init(ln2440sbc_init);
+module_exit(ln2440sbc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ivan Kuten");
+MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c
new file mode 100644
index 0000000..87ddfef
--- /dev/null
+++ b/sound/soc/s3c24xx/neo1973_wm8753.c
@@ -0,0 +1,722 @@
+/*
+ * neo1973_wm8753.c -- SoC audio for Neo1973
+ *
+ * Copyright 2007 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include <asm/mach-types.h>
+#include <asm/hardware/scoop.h>
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+#include <linux/io.h>
+#include <mach/spi-gpio.h>
+
+#include <asm/plat-s3c24xx/regs-iis.h>
+
+#include "../codecs/wm8753.h"
+#include "lm4857.h"
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-i2s.h"
+
+/* Debugging stuff */
+#define S3C24XX_SOC_NEO1973_WM8753_DEBUG 0
+#if S3C24XX_SOC_NEO1973_WM8753_DEBUG
+#define DBG(x...) printk(KERN_DEBUG "s3c24xx-soc-neo1973-wm8753: " x)
+#else
+#define DBG(x...)
+#endif
+
+/* define the scenarios */
+#define NEO_AUDIO_OFF 0
+#define NEO_GSM_CALL_AUDIO_HANDSET 1
+#define NEO_GSM_CALL_AUDIO_HEADSET 2
+#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3
+#define NEO_STEREO_TO_SPEAKERS 4
+#define NEO_STEREO_TO_HEADPHONES 5
+#define NEO_CAPTURE_HANDSET 6
+#define NEO_CAPTURE_HEADSET 7
+#define NEO_CAPTURE_BLUETOOTH 8
+
+static struct snd_soc_machine neo1973;
+static struct i2c_client *i2c;
+
+static int neo1973_hifi_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_dai *codec_dai = rtd->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ unsigned int pll_out = 0, bclk = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ DBG("Entered %s\n", __func__);
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ pll_out = 12288000;
+ break;
+ case 48000:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 12288000;
+ break;
+ case 96000:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 12288000;
+ break;
+ case 11025:
+ bclk = WM8753_BCLK_DIV_16;
+ pll_out = 11289600;
+ break;
+ case 22050:
+ bclk = WM8753_BCLK_DIV_8;
+ pll_out = 11289600;
+ break;
+ case 44100:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 11289600;
+ break;
+ case 88200:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 11289600;
+ break;
+ }
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set codec BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(4, 4));
+ if (ret < 0)
+ return ret;
+
+ /* codec PLL input is PCLK/4 */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1,
+ iis_clkrate / 4, pll_out);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+
+ DBG("Entered %s\n", __func__);
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0);
+}
+
+/*
+ * Neo1973 WM8753 HiFi DAI opserations.
+ */
+static struct snd_soc_ops neo1973_hifi_ops = {
+ .hw_params = neo1973_hifi_hw_params,
+ .hw_free = neo1973_hifi_hw_free,
+};
+
+static int neo1973_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+ unsigned int pcmdiv = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ DBG("Entered %s\n", __func__);
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ if (params_rate(params) != 8000)
+ return -EINVAL;
+ if (params_channels(params) != 1)
+ return -EINVAL;
+
+ pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
+
+ /* todo: gg check mode (DSP_B) against CSR datasheet */
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set codec PCM division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
+ if (ret < 0)
+ return ret;
+
+ /* configue and enable PLL for 12.288MHz output */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2,
+ iis_clkrate / 4, 12288000);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_voice_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+
+ DBG("Entered %s\n", __func__);
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0);
+}
+
+static struct snd_soc_ops neo1973_voice_ops = {
+ .hw_params = neo1973_voice_hw_params,
+ .hw_free = neo1973_voice_hw_free,
+};
+
+static int neo1973_scenario;
+
+static int neo1973_get_scenario(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = neo1973_scenario;
+ return 0;
+}
+
+static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario)
+{
+ DBG("Entered %s\n", __func__);
+
+ switch (neo1973_scenario) {
+ case NEO_AUDIO_OFF:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_GSM_CALL_AUDIO_HANDSET:
+ snd_soc_dapm_enable_pin(codec, "Audio Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_enable_pin(codec, "Call Mic");
+ break;
+ case NEO_GSM_CALL_AUDIO_HEADSET:
+ snd_soc_dapm_enable_pin(codec, "Audio Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line In");
+ snd_soc_dapm_enable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_GSM_CALL_AUDIO_BLUETOOTH:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_enable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_STEREO_TO_SPEAKERS:
+ snd_soc_dapm_enable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_STEREO_TO_HEADPHONES:
+ snd_soc_dapm_enable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_CAPTURE_HANDSET:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_enable_pin(codec, "Call Mic");
+ break;
+ case NEO_CAPTURE_HEADSET:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_enable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ case NEO_CAPTURE_BLUETOOTH:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ break;
+ default:
+ snd_soc_dapm_disable_pin(codec, "Audio Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+ snd_soc_dapm_disable_pin(codec, "GSM Line In");
+ snd_soc_dapm_disable_pin(codec, "Headset Mic");
+ snd_soc_dapm_disable_pin(codec, "Call Mic");
+ }
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static int neo1973_set_scenario(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ DBG("Entered %s\n", __func__);
+
+ if (neo1973_scenario == ucontrol->value.integer.value[0])
+ return 0;
+
+ neo1973_scenario = ucontrol->value.integer.value[0];
+ set_scenario_endpoints(codec, neo1973_scenario);
+ return 1;
+}
+
+static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0};
+
+static void lm4857_write_regs(void)
+{
+ DBG("Entered %s\n", __func__);
+
+ if (i2c_master_send(i2c, lm4857_regs, 4) != 4)
+ printk(KERN_ERR "lm4857: i2c write failed\n");
+}
+
+static int lm4857_get_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg = kcontrol->private_value & 0xFF;
+ int shift = (kcontrol->private_value >> 8) & 0x0F;
+ int mask = (kcontrol->private_value >> 16) & 0xFF;
+
+ DBG("Entered %s\n", __func__);
+
+ ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask;
+ return 0;
+}
+
+static int lm4857_set_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg = kcontrol->private_value & 0xFF;
+ int shift = (kcontrol->private_value >> 8) & 0x0F;
+ int mask = (kcontrol->private_value >> 16) & 0xFF;
+
+ if (((lm4857_regs[reg] >> shift) & mask) ==
+ ucontrol->value.integer.value[0])
+ return 0;
+
+ lm4857_regs[reg] &= ~(mask << shift);
+ lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift;
+ lm4857_write_regs();
+ return 1;
+}
+
+static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = lm4857_regs[LM4857_CTRL] & 0x0F;
+
+ DBG("Entered %s\n", __func__);
+
+ if (value)
+ value -= 5;
+
+ ucontrol->value.integer.value[0] = value;
+ return 0;
+}
+
+static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 value = ucontrol->value.integer.value[0];
+
+ DBG("Entered %s\n", __func__);
+
+ if (value)
+ value += 5;
+
+ if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value)
+ return 0;
+
+ lm4857_regs[LM4857_CTRL] &= 0xF0;
+ lm4857_regs[LM4857_CTRL] |= value;
+ lm4857_write_regs();
+ return 1;
+}
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Audio Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line In", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Call Mic", NULL),
+};
+
+
+static const struct snd_soc_dapm_route dapm_routes[] = {
+
+ /* Connections to the lm4857 amp */
+ {"Audio Out", NULL, "LOUT1"},
+ {"Audio Out", NULL, "ROUT1"},
+
+ /* Connections to the GSM Module */
+ {"GSM Line Out", NULL, "MONO1"},
+ {"GSM Line Out", NULL, "MONO2"},
+ {"RXP", NULL, "GSM Line In"},
+ {"RXN", NULL, "GSM Line In"},
+
+ /* Connections to Headset */
+ {"MIC1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Mic"},
+
+ /* Call Mic */
+ {"MIC2", NULL, "Mic Bias"},
+ {"MIC2N", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Call Mic"},
+
+ /* Connect the ALC pins */
+ {"ACIN", NULL, "ACOP"},
+};
+
+static const char *lm4857_mode[] = {
+ "Off",
+ "Call Speaker",
+ "Stereo Speakers",
+ "Stereo Speakers + Headphones",
+ "Headphones"
+};
+
+static const struct soc_enum lm4857_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode),
+};
+
+static const char *neo_scenarios[] = {
+ "Off",
+ "GSM Handset",
+ "GSM Headset",
+ "GSM Bluetooth",
+ "Speakers",
+ "Headphones",
+ "Capture Handset",
+ "Capture Headset",
+ "Capture Bluetooth"
+};
+
+static const struct soc_enum neo_scenario_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios),
+};
+
+static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
+
+static const struct snd_kcontrol_new wm8753_neo1973_controls[] = {
+ SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0,
+ lm4857_get_reg, lm4857_set_reg, stereo_tlv),
+ SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0,
+ lm4857_get_reg, lm4857_set_reg, stereo_tlv),
+ SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
+ lm4857_get_reg, lm4857_set_reg, mono_tlv),
+ SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0],
+ lm4857_get_mode, lm4857_set_mode),
+ SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0],
+ neo1973_get_scenario, neo1973_set_scenario),
+ SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0,
+ lm4857_get_reg, lm4857_set_reg),
+ SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0,
+ lm4857_get_reg, lm4857_set_reg),
+ SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0,
+ lm4857_get_reg, lm4857_set_reg),
+ SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0,
+ lm4857_get_reg, lm4857_set_reg),
+};
+
+/*
+ * This is an example machine initialisation for a wm8753 connected to a
+ * neo1973 II. It is missing logic to detect hp/mic insertions and logic
+ * to re-route the audio in such an event.
+ */
+static int neo1973_wm8753_init(struct snd_soc_codec *codec)
+{
+ int i, err;
+
+ DBG("Entered %s\n", __func__);
+
+ /* set up NC codec pins */
+ snd_soc_dapm_nc_pin(codec, "LOUT2");
+ snd_soc_dapm_nc_pin(codec, "ROUT2");
+ snd_soc_dapm_nc_pin(codec, "OUT3");
+ snd_soc_dapm_nc_pin(codec, "OUT4");
+ snd_soc_dapm_nc_pin(codec, "LINE1");
+ snd_soc_dapm_nc_pin(codec, "LINE2");
+
+ /* Add neo1973 specific widgets */
+ snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+ ARRAY_SIZE(wm8753_dapm_widgets));
+
+ /* set endpoints to default mode */
+ set_scenario_endpoints(codec, NEO_AUDIO_OFF);
+
+ /* add neo1973 specific controls */
+ for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_controls); i++) {
+ err = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8753_neo1973_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+
+ /* set up neo1973 specific audio routes */
+ err = snd_soc_dapm_add_routes(codec, dapm_routes,
+ ARRAY_SIZE(dapm_routes));
+
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+/*
+ * BT Codec DAI
+ */
+static struct snd_soc_dai bt_dai = {
+ .name = "Bluetooth",
+ .id = 0,
+ .type = SND_SOC_DAI_PCM,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_dai_link neo1973_dai[] = {
+{ /* Hifi Playback - for similatious use with voice below */
+ .name = "WM8753",
+ .stream_name = "WM8753 HiFi",
+ .cpu_dai = &s3c24xx_i2s_dai,
+ .codec_dai = &wm8753_dai[WM8753_DAI_HIFI],
+ .init = neo1973_wm8753_init,
+ .ops = &neo1973_hifi_ops,
+},
+{ /* Voice via BT */
+ .name = "Bluetooth",
+ .stream_name = "Voice",
+ .cpu_dai = &bt_dai,
+ .codec_dai = &wm8753_dai[WM8753_DAI_VOICE],
+ .ops = &neo1973_voice_ops,
+},
+};
+
+static struct snd_soc_machine neo1973 = {
+ .name = "neo1973",
+ .dai_link = neo1973_dai,
+ .num_links = ARRAY_SIZE(neo1973_dai),
+};
+
+static struct wm8753_setup_data neo1973_wm8753_setup = {
+ .i2c_bus = 0,
+ .i2c_address = 0x1a,
+};
+
+static struct snd_soc_device neo1973_snd_devdata = {
+ .machine = &neo1973,
+ .platform = &s3c24xx_soc_platform,
+ .codec_dev = &soc_codec_dev_wm8753,
+ .codec_data = &neo1973_wm8753_setup,
+};
+
+static int lm4857_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ DBG("Entered %s\n", __func__);
+
+ i2c = client;
+
+ lm4857_write_regs();
+ return 0;
+}
+
+static int lm4857_i2c_remove(struct i2c_client *client)
+{
+ DBG("Entered %s\n", __func__);
+
+ i2c = NULL;
+
+ return 0;
+}
+
+static u8 lm4857_state;
+
+static int lm4857_suspend(struct i2c_client *dev, pm_message_t state)
+{
+ DBG("Entered %s\n", __func__);
+
+ dev_dbg(&dev->dev, "lm4857_suspend\n");
+ lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf;
+ if (lm4857_state) {
+ lm4857_regs[LM4857_CTRL] &= 0xf0;
+ lm4857_write_regs();
+ }
+ return 0;
+}
+
+static int lm4857_resume(struct i2c_client *dev)
+{
+ DBG("Entered %s\n", __func__);
+
+ if (lm4857_state) {
+ lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f);
+ lm4857_write_regs();
+ }
+ return 0;
+}
+
+static void lm4857_shutdown(struct i2c_client *dev)
+{
+ DBG("Entered %s\n", __func__);
+
+ dev_dbg(&dev->dev, "lm4857_shutdown\n");
+ lm4857_regs[LM4857_CTRL] &= 0xf0;
+ lm4857_write_regs();
+}
+
+static const struct i2c_device_id lm4857_i2c_id[] = {
+ { "neo1973_lm4857", 0 },
+ { }
+};
+
+static struct i2c_driver lm4857_i2c_driver = {
+ .driver = {
+ .name = "LM4857 I2C Amp",
+ .owner = THIS_MODULE,
+ },
+ .suspend = lm4857_suspend,
+ .resume = lm4857_resume,
+ .shutdown = lm4857_shutdown,
+ .probe = lm4857_i2c_probe,
+ .remove = lm4857_i2c_remove,
+ .id_table = lm4857_i2c_id,
+};
+
+static struct platform_device *neo1973_snd_device;
+
+static int __init neo1973_init(void)
+{
+ int ret;
+
+ DBG("Entered %s\n", __func__);
+
+ if (!machine_is_neo1973_gta01()) {
+ printk(KERN_INFO
+ "Only GTA01 hardware supported by ASoC driver\n");
+ return -ENODEV;
+ }
+
+ neo1973_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!neo1973_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(neo1973_snd_device, &neo1973_snd_devdata);
+ neo1973_snd_devdata.dev = &neo1973_snd_device->dev;
+ ret = platform_device_add(neo1973_snd_device);
+
+ if (ret) {
+ platform_device_put(neo1973_snd_device);
+ return ret;
+ }
+
+ ret = i2c_add_driver(&lm4857_i2c_driver);
+
+ if (ret != 0)
+ platform_device_unregister(neo1973_snd_device);
+
+ return ret;
+}
+
+static void __exit neo1973_exit(void)
+{
+ DBG("Entered %s\n", __func__);
+
+ i2c_del_driver(&lm4857_i2c_driver);
+ platform_device_unregister(neo1973_snd_device);
+}
+
+module_init(neo1973_init);
+module_exit(neo1973_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c
new file mode 100644
index 0000000..ded7d99
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c2412-i2s.c
@@ -0,0 +1,745 @@
+/* sound/soc/s3c24xx/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com
+ * linux@wolfsonmicro.com
+ *
+ * Copyright (c) 2007, 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <mach/hardware.h>
+
+#include <linux/io.h>
+#include <asm/dma.h>
+
+#include <asm/plat-s3c24xx/regs-s3c2412-iis.h>
+
+#include <mach/regs-gpio.h>
+#include <mach/audio.h>
+#include <mach/dma.h>
+
+#include "s3c24xx-pcm.h"
+#include "s3c2412-i2s.h"
+
+#define S3C2412_I2S_DEBUG 0
+#define S3C2412_I2S_DEBUG_CON 0
+
+#if S3C2412_I2S_DEBUG
+#define DBG(x...) printk(KERN_INFO x)
+#else
+#define DBG(x...) do { } while (0)
+#endif
+
+static struct s3c2410_dma_client s3c2412_dma_client_out = {
+ .name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c2412_dma_client_in = {
+ .name = "I2S PCM Stereo in"
+};
+
+static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_out = {
+ .client = &s3c2412_dma_client_out,
+ .channel = DMACH_I2S_OUT,
+ .dma_addr = S3C2410_PA_IIS + S3C2412_IISTXD,
+ .dma_size = 4,
+};
+
+static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_in = {
+ .client = &s3c2412_dma_client_in,
+ .channel = DMACH_I2S_IN,
+ .dma_addr = S3C2410_PA_IIS + S3C2412_IISRXD,
+ .dma_size = 4,
+};
+
+struct s3c2412_i2s_info {
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *iis_clk;
+ struct clk *iis_pclk;
+ struct clk *iis_cclk;
+
+ u32 suspend_iismod;
+ u32 suspend_iiscon;
+ u32 suspend_iispsr;
+};
+
+static struct s3c2412_i2s_info s3c2412_i2s;
+
+#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
+
+#if S3C2412_I2S_DEBUG_CON
+static void dbg_showcon(const char *fn, u32 con)
+{
+ printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_LRINDEX),
+ bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
+ bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
+
+ printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
+ fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
+ printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
+}
+#else
+static inline void dbg_showcon(const char *fn, u32 con)
+{
+}
+#endif
+
+/* Turn on or off the transmission path. */
+static void s3c2412_snd_txctrl(int on)
+{
+ struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ DBG("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_TXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXONLY:
+ case S3C2412_IISMOD_MODE_TXRX:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_RXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n");
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ } else {
+ /* Note, we do not have any indication that the FIFO problems
+ * tha the S3C2410/2440 had apply here, so we should be able
+ * to disable the DMA and TX without resetting the FIFOS.
+ */
+
+ con |= S3C2412_IISCON_TXDMA_PAUSE;
+ con |= S3C2412_IISCON_TXCH_PAUSE;
+ con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_RXONLY;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n");
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ dbg_showcon(__func__, con);
+ DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+static void s3c2412_snd_rxctrl(int on)
+{
+ struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ DBG("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ case S3C2412_IISMOD_MODE_RXONLY:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ } else {
+ /* See txctrl notes on FIFOs. */
+
+ con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+ con |= S3C2412_IISCON_RXDMA_PAUSE;
+ con |= S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_RXONLY:
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXONLY;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+
+/*
+ * 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 s3c2412_snd_lrsync(void)
+{
+ u32 iiscon;
+ unsigned long timeout = jiffies + msecs_to_jiffies(5);
+
+ DBG("Entered %s\n", __func__);
+
+ while (1) {
+ iiscon = readl(s3c2412_i2s.regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_LRINDEX)
+ break;
+
+ if (timeout < jiffies) {
+ printk(KERN_ERR "%s: timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c2412_snd_is_clkmaster(void)
+{
+ u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
+
+ DBG("Entered %s\n", __func__);
+
+ iismod &= S3C2412_IISMOD_MASTER_MASK;
+ return !(iismod == S3C2412_IISMOD_SLAVE);
+}
+
+/*
+ * Set S3C2412 I2S DAI format
+ */
+static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ u32 iismod;
+
+
+ DBG("Entered %s\n", __func__);
+
+ iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
+ DBG("hw_params r: IISMOD: %x \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iismod &= ~S3C2412_IISMOD_MASTER_MASK;
+ iismod |= S3C2412_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iismod &= ~S3C2412_IISMOD_MASTER_MASK;
+ iismod |= S3C2412_IISMOD_MASTER_INTERNAL;
+ break;
+ default:
+ DBG("unknwon master/slave format\n");
+ return -EINVAL;
+ }
+
+ iismod &= ~S3C2412_IISMOD_SDF_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iismod |= S3C2412_IISMOD_SDF_MSB;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2412_IISMOD_SDF_LSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod |= S3C2412_IISMOD_SDF_IIS;
+ break;
+ default:
+ DBG("Unknown data format\n");
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
+ DBG("hw_params w: IISMOD: %x \n", iismod);
+ return 0;
+}
+
+static int s3c2412_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", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_out;
+ else
+ rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_in;
+
+ /* Working copies of register */
+ iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
+ DBG("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ iismod |= S3C2412_IISMOD_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iismod &= ~S3C2412_IISMOD_8BIT;
+ break;
+ }
+
+ writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
+ DBG("%s: w: IISMOD: %x\n", __func__, iismod);
+ return 0;
+}
+
+static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ unsigned long irqs;
+ int ret = 0;
+
+ DBG("Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* On start, ensure that the FIFOs are cleared and reset. */
+
+ writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
+ s3c2412_i2s.regs + S3C2412_IISFIC);
+
+ /* clear again, just in case */
+ writel(0x0, s3c2412_i2s.regs + S3C2412_IISFIC);
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!s3c2412_snd_is_clkmaster()) {
+ ret = s3c2412_snd_lrsync();
+ if (ret)
+ goto exit_err;
+ }
+
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(1);
+ else
+ s3c2412_snd_txctrl(1);
+
+ local_irq_restore(irqs);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(0);
+ else
+ s3c2412_snd_txctrl(0);
+
+ local_irq_restore(irqs);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/* default table of all avaialable root fs divisors */
+static unsigned int s3c2412_iis_fs[] = { 256, 512, 384, 768, 0 };
+
+int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk)
+{
+ unsigned long clkrate = clk_get_rate(clk);
+ unsigned int div;
+ unsigned int fsclk;
+ unsigned int actual;
+ unsigned int fs;
+ unsigned int fsdiv;
+ signed int deviation = 0;
+ unsigned int best_fs = 0;
+ unsigned int best_div = 0;
+ unsigned int best_rate = 0;
+ unsigned int best_deviation = INT_MAX;
+
+
+ if (fstab == NULL)
+ fstab = s3c2412_iis_fs;
+
+ for (fs = 0;; fs++) {
+ fsdiv = s3c2412_iis_fs[fs];
+
+ if (fsdiv == 0)
+ break;
+
+ fsclk = clkrate / fsdiv;
+ div = fsclk / rate;
+
+ if ((fsclk % rate) > (rate / 2))
+ div++;
+
+ if (div <= 1)
+ continue;
+
+ actual = clkrate / (fsdiv * div);
+ deviation = actual - rate;
+
+ printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n",
+ fsdiv, div, actual, deviation);
+
+ deviation = abs(deviation);
+
+ if (deviation < best_deviation) {
+ best_fs = fsdiv;
+ best_div = div;
+ best_rate = actual;
+ best_deviation = deviation;
+ }
+
+ if (deviation == 0)
+ break;
+ }
+
+ printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n",
+ best_fs, best_div, best_rate);
+
+ info->fs_div = best_fs;
+ info->clk_div = best_div;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate);
+
+/*
+ * Set S3C2412 Clock source
+ */
+static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
+
+ DBG("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id,
+ freq, dir);
+
+ switch (clk_id) {
+ case S3C2412_CLKSRC_PCLK:
+ iismod &= ~S3C2412_IISMOD_MASTER_MASK;
+ iismod |= S3C2412_IISMOD_MASTER_INTERNAL;
+ break;
+ case S3C2412_CLKSRC_I2SCLK:
+ iismod &= ~S3C2412_IISMOD_MASTER_MASK;
+ iismod |= S3C2412_IISMOD_MASTER_EXTERNAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
+ return 0;
+}
+
+/*
+ * Set S3C2412 Clock dividers
+ */
+static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
+ u32 reg;
+
+ DBG("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
+
+ switch (div_id) {
+ case S3C2412_DIV_BCLK:
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_BCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+
+ DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C2412_DIV_RCLK:
+ if (div > 3) {
+ /* convert value to bit field */
+
+ switch (div) {
+ case 256:
+ div = S3C2412_IISMOD_RCLK_256FS;
+ break;
+
+ case 384:
+ div = S3C2412_IISMOD_RCLK_384FS;
+ break;
+
+ case 512:
+ div = S3C2412_IISMOD_RCLK_512FS;
+ break;
+
+ case 768:
+ div = S3C2412_IISMOD_RCLK_768FS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ reg = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_RCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+ DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C2412_DIV_PRESCALER:
+ if (div >= 0) {
+ writel((div << 8) | S3C2412_IISPSR_PSREN,
+ i2s->regs + S3C2412_IISPSR);
+ } else {
+ writel(0x0, i2s->regs + S3C2412_IISPSR);
+ }
+ DBG("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct clk *s3c2412_get_iisclk(void)
+{
+ return s3c2412_i2s.iis_clk;
+}
+EXPORT_SYMBOL_GPL(s3c2412_get_iisclk);
+
+
+static int s3c2412_i2s_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ DBG("Entered %s\n", __func__);
+
+ s3c2412_i2s.dev = &pdev->dev;
+
+ s3c2412_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
+ if (s3c2412_i2s.regs == NULL)
+ return -ENXIO;
+
+ s3c2412_i2s.iis_pclk = clk_get(&pdev->dev, "iis");
+ if (s3c2412_i2s.iis_pclk == NULL) {
+ DBG("failed to get iis_clock\n");
+ iounmap(s3c2412_i2s.regs);
+ return -ENODEV;
+ }
+
+ s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk");
+ if (s3c2412_i2s.iis_cclk == NULL) {
+ DBG("failed to get i2sclk clock\n");
+ iounmap(s3c2412_i2s.regs);
+ return -ENODEV;
+ }
+
+ clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
+
+ clk_enable(s3c2412_i2s.iis_pclk);
+ clk_enable(s3c2412_i2s.iis_cclk);
+
+ s3c2412_i2s.iis_clk = s3c2412_i2s.iis_pclk;
+
+ /* 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);
+
+ s3c2412_snd_txctrl(0);
+ s3c2412_snd_rxctrl(0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c2412_i2s_suspend(struct platform_device *dev,
+ struct snd_soc_dai *dai)
+{
+ struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
+ u32 iismod;
+
+ if (dai->active) {
+ i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+ i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+ i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+
+ /* some basic suspend checks */
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C2412_IISCON_RXDMA_ACTIVE)
+ dev_warn(&dev->dev, "%s: RXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_TXDMA_ACTIVE)
+ dev_warn(&dev->dev, "%s: TXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_IIS_ACTIVE)
+ dev_warn(&dev->dev, "%s: IIS active\n", __func__);
+ }
+
+ return 0;
+}
+
+static int s3c2412_i2s_resume(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
+
+ dev_info(&pdev->dev, "dai_active %d, IISMOD %08x, IISCON %08x\n",
+ dai->active, i2s->suspend_iismod, i2s->suspend_iiscon);
+
+ if (dai->active) {
+ writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+ writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+ writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+
+ writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ ndelay(250);
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+
+ }
+
+ return 0;
+}
+#else
+#define s3c2412_i2s_suspend NULL
+#define s3c2412_i2s_resume NULL
+#endif /* CONFIG_PM */
+
+#define S3C2412_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_dai s3c2412_i2s_dai = {
+ .name = "s3c2412-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .probe = s3c2412_i2s_probe,
+ .suspend = s3c2412_i2s_suspend,
+ .resume = s3c2412_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = {
+ .trigger = s3c2412_i2s_trigger,
+ .hw_params = s3c2412_i2s_hw_params,
+ },
+ .dai_ops = {
+ .set_fmt = s3c2412_i2s_set_fmt,
+ .set_clkdiv = s3c2412_i2s_set_clkdiv,
+ .set_sysclk = s3c2412_i2s_set_sysclk,
+ },
+};
+EXPORT_SYMBOL_GPL(s3c2412_i2s_dai);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h
new file mode 100644
index 0000000..aac08a2
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c2412-i2s.h
@@ -0,0 +1,38 @@
+/* sound/soc/s3c24xx/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2007 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.
+*/
+
+#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
+#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
+
+#define S3C2412_DIV_BCLK (1)
+#define S3C2412_DIV_RCLK (2)
+#define S3C2412_DIV_PRESCALER (3)
+
+#define S3C2412_CLKSRC_PCLK (0)
+#define S3C2412_CLKSRC_I2SCLK (1)
+
+extern struct clk *s3c2412_get_iisclk(void);
+
+extern struct snd_soc_dai s3c2412_i2s_dai;
+
+struct s3c2412_rate_calc {
+ unsigned int clk_div; /* for prescaler */
+ unsigned int fs_div; /* for root frame clock */
+};
+
+extern int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk);
+
+#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */
diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c
new file mode 100644
index 0000000..19c5c3c
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c2443-ac97.c
@@ -0,0 +1,398 @@
+/*
+ * s3c2443-ac97.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2007 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright (C) 2005, Sean Choi <sh428.choi@samsung.com>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <asm/plat-s3c/regs-ac97.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-clock.h>
+#include <mach/audio.h>
+#include <asm/dma.h>
+#include <mach/dma.h>
+
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-ac97.h"
+
+struct s3c24xx_ac97_info {
+ void __iomem *regs;
+ struct clk *ac97_clk;
+};
+static struct s3c24xx_ac97_info s3c24xx_ac97;
+
+static DECLARE_COMPLETION(ac97_completion);
+static u32 codec_ready;
+static DECLARE_MUTEX(ac97_mutex);
+
+static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ u32 ac_glbctrl;
+ u32 ac_codec_cmd;
+ u32 stat, addr, data;
+
+ down(&ac97_mutex);
+
+ codec_ready = S3C_AC97_GLBSTAT_CODECREADY;
+ ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
+ writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+
+ wait_for_completion(&ac97_completion);
+
+ stat = readl(s3c24xx_ac97.regs + S3C_AC97_STAT);
+ addr = (stat >> 16) & 0x7f;
+ data = (stat & 0xffff);
+
+ if (addr != reg)
+ printk(KERN_ERR "s3c24xx-ac97: req addr = %02x,"
+ " rep addr = %02x\n", reg, addr);
+
+ up(&ac97_mutex);
+
+ return (unsigned short)data;
+}
+
+static void s3c2443_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ u32 ac_glbctrl;
+ u32 ac_codec_cmd;
+
+ down(&ac97_mutex);
+
+ codec_ready = S3C_AC97_GLBSTAT_CODECREADY;
+ ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
+ writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+
+ wait_for_completion(&ac97_completion);
+
+ ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
+ writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ up(&ac97_mutex);
+
+}
+
+static void s3c2443_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ u32 ac_glbctrl;
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_WARMRESET;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = 0;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+}
+
+static void s3c2443_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ u32 ac_glbctrl;
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = 0;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA |
+ S3C_AC97_GLBCTRL_PCMINTM_DMA | S3C_AC97_GLBCTRL_MICINTM_DMA;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+}
+
+static irqreturn_t s3c2443_ac97_irq(int irq, void *dev_id)
+{
+ int status;
+ u32 ac_glbctrl;
+
+ status = readl(s3c24xx_ac97.regs + S3C_AC97_GLBSTAT) & codec_ready;
+
+ if (status) {
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ complete(&ac97_completion);
+ }
+ return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = s3c2443_ac97_read,
+ .write = s3c2443_ac97_write,
+ .warm_reset = s3c2443_ac97_warm_reset,
+ .reset = s3c2443_ac97_cold_reset,
+};
+
+static struct s3c2410_dma_client s3c2443_dma_client_out = {
+ .name = "AC97 PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c2443_dma_client_in = {
+ .name = "AC97 PCM Stereo in"
+};
+
+static struct s3c2410_dma_client s3c2443_dma_client_micin = {
+ .name = "AC97 Mic Mono in"
+};
+
+static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_out = {
+ .client = &s3c2443_dma_client_out,
+ .channel = DMACH_PCM_OUT,
+ .dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
+ .dma_size = 4,
+};
+
+static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_in = {
+ .client = &s3c2443_dma_client_in,
+ .channel = DMACH_PCM_IN,
+ .dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
+ .dma_size = 4,
+};
+
+static struct s3c24xx_pcm_dma_params s3c2443_ac97_mic_mono_in = {
+ .client = &s3c2443_dma_client_micin,
+ .channel = DMACH_MIC_IN,
+ .dma_addr = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
+ .dma_size = 4,
+};
+
+static int s3c2443_ac97_probe(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ u32 ac_glbctrl;
+
+ s3c24xx_ac97.regs = ioremap(S3C2440_PA_AC97, 0x100);
+ if (s3c24xx_ac97.regs == NULL)
+ return -ENXIO;
+
+ s3c24xx_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
+ if (s3c24xx_ac97.ac97_clk == NULL) {
+ printk(KERN_ERR "s3c2443-ac97 failed to get ac97_clock\n");
+ iounmap(s3c24xx_ac97.regs);
+ return -ENODEV;
+ }
+ clk_enable(s3c24xx_ac97.ac97_clk);
+
+ s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2443_GPE0_AC_nRESET);
+ s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2443_GPE1_AC_SYNC);
+ s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2443_GPE2_AC_BITCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2443_GPE3_AC_SDI);
+ s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2443_GPE4_AC_SDO);
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = 0;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+
+ ret = request_irq(IRQ_S3C244x_AC97, s3c2443_ac97_irq,
+ IRQF_DISABLED, "AC97", NULL);
+ if (ret < 0) {
+ printk(KERN_ERR "s3c24xx-ac97: interrupt request failed.\n");
+ clk_disable(s3c24xx_ac97.ac97_clk);
+ clk_put(s3c24xx_ac97.ac97_clk);
+ iounmap(s3c24xx_ac97.regs);
+ }
+ return ret;
+}
+
+static void s3c2443_ac97_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ free_irq(IRQ_S3C244x_AC97, NULL);
+ clk_disable(s3c24xx_ac97.ac97_clk);
+ clk_put(s3c24xx_ac97.ac97_clk);
+ iounmap(s3c24xx_ac97.regs);
+}
+
+static int s3c2443_ac97_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_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_out;
+ else
+ cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_in;
+
+ return 0;
+}
+
+static int s3c2443_ac97_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ u32 ac_glbctrl;
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
+ else
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
+ else
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
+ break;
+ }
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+
+ return 0;
+}
+
+static int s3c2443_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ else
+ cpu_dai->dma_data = &s3c2443_ac97_mic_mono_in;
+
+ return 0;
+}
+
+static int s3c2443_ac97_mic_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ u32 ac_glbctrl;
+
+ ac_glbctrl = readl(s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
+ }
+ writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL);
+
+ return 0;
+}
+
+#define s3c2443_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+struct snd_soc_dai s3c2443_ac97_dai[] = {
+{
+ .name = "s3c2443-ac97",
+ .id = 0,
+ .type = SND_SOC_DAI_AC97,
+ .probe = s3c2443_ac97_probe,
+ .remove = s3c2443_ac97_remove,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = s3c2443_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = s3c2443_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = s3c2443_ac97_hw_params,
+ .trigger = s3c2443_ac97_trigger},
+},
+{
+ .name = "pxa2xx-ac97-mic",
+ .id = 1,
+ .type = SND_SOC_DAI_AC97,
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = s3c2443_AC97_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = {
+ .hw_params = s3c2443_ac97_hw_mic_params,
+ .trigger = s3c2443_ac97_mic_trigger,},
+},
+};
+EXPORT_SYMBOL_GPL(s3c2443_ac97_dai);
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+MODULE_AUTHOR("Graeme Gregory");
+MODULE_DESCRIPTION("AC97 driver for the Samsung s3c2443 chip");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c24xx-ac97.h b/sound/soc/s3c24xx/s3c24xx-ac97.h
new file mode 100644
index 0000000..a96dcad
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-ac97.h
@@ -0,0 +1,31 @@
+/*
+ * s3c24xx-ac97.c -- ALSA Soc Audio Layer
+ *
+ * (c) 2007 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 S3C24XXAC97_H_
+#define S3C24XXAC97_H_
+
+#define AC_CMD_ADDR(x) (x << 16)
+#define AC_CMD_DATA(x) (x & 0xffff)
+
+#ifdef CONFIG_CPU_S3C2440
+#define IRQ_S3C244x_AC97 IRQ_S3C2440_AC97
+#else
+#define IRQ_S3C244x_AC97 IRQ_S3C2443_AC97
+#endif
+
+extern struct snd_soc_dai s3c2443_ac97_dai[];
+
+#endif /*S3C24XXAC97_H_*/
diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c
new file mode 100644
index 0000000..ba4476b
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-i2s.c
@@ -0,0 +1,483 @@
+/*
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-clock.h>
+#include <mach/audio.h>
+#include <asm/dma.h>
+#include <mach/dma.h>
+
+#include <asm/plat-s3c24xx/regs-iis.h>
+
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-i2s.h"
+
+#define S3C24XX_I2S_DEBUG 0
+#if S3C24XX_I2S_DEBUG
+#define DBG(x...) printk(KERN_DEBUG "s3c24xx-i2s: " 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;
+ u32 iiscon;
+ u32 iismod;
+ u32 iisfcon;
+ u32 iispsr;
+};
+static struct s3c24xx_i2s_info s3c24xx_i2s;
+
+static void s3c24xx_snd_txctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ DBG("Entered %s\n", __func__);
+
+ 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", __func__);
+
+ 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;
+ int timeout = 50; /* 5ms */
+
+ DBG("Entered %s\n", __func__);
+
+ while (1) {
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ if (iiscon & S3C2410_IISCON_LRINDEX)
+ break;
+
+ if (!timeout--)
+ return -ETIMEDOUT;
+ udelay(100);
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c24xx_snd_is_clkmaster(void)
+{
+ DBG("Entered %s\n", __func__);
+
+ 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_dai *cpu_dai,
+ unsigned int fmt)
+{
+ u32 iismod;
+
+ DBG("Entered %s\n", __func__);
+
+ 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:
+ iismod &= ~S3C2410_IISMOD_SLAVE;
+ 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:
+ iismod &= ~S3C2410_IISMOD_MSB;
+ 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", __func__);
+
+ 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", __func__);
+
+ 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_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ DBG("Entered %s\n", __func__);
+
+ 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_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 reg;
+
+ DBG("Entered %s\n", __func__);
+
+ switch (div_id) {
+ case S3C24XX_DIV_BCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_MCLK:
+ 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,
+ struct snd_soc_dai *dai)
+{
+ DBG("Entered %s\n", __func__);
+
+ 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");
+ iounmap(s3c24xx_i2s.regs);
+ 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;
+}
+
+#ifdef CONFIG_PM
+static int s3c24xx_i2s_suspend(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ DBG("Entered %s\n", __func__);
+
+ s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ clk_disable(s3c24xx_i2s.iis_clk);
+
+ return 0;
+}
+
+static int s3c24xx_i2s_resume(struct platform_device *pdev,
+ struct snd_soc_dai *cpu_dai)
+{
+ DBG("Entered %s\n", __func__);
+ clk_enable(s3c24xx_i2s.iis_clk);
+
+ writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ return 0;
+}
+#else
+#define s3c24xx_i2s_suspend NULL
+#define s3c24xx_i2s_resume NULL
+#endif
+
+
+#define S3C24XX_I2S_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+struct snd_soc_dai s3c24xx_i2s_dai = {
+ .name = "s3c24xx-i2s",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .probe = s3c24xx_i2s_probe,
+ .suspend = s3c24xx_i2s_suspend,
+ .resume = s3c24xx_i2s_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .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 0000000..726d91c
--- /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_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 0000000..e13e614
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c24xx-pcm.c
@@ -0,0 +1,470 @@
+/*
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "s3c24xx-pcm.h"
+
+#define S3C24XX_PCM_DEBUG 0
+#if S3C24XX_PCM_DEBUG
+#define DBG(x...) printk(KERN_DEBUG "s3c24xx-pcm: " 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 |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .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", __func__);
+
+ 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",
+ __func__, 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", __func__);
+
+ 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", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!dma)
+ return 0;
+
+ /* this may get called several times by oss emulation
+ * with different params -HW */
+ if (prtd->params == NULL) {
+ /* 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 < 0) {
+ DBG(KERN_ERR "failed to get dma channel\n");
+ return ret;
+ }
+ }
+
+ 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", __func__);
+
+ /* 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", __func__);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME */
+ if (!prtd->params)
+ return 0;
+
+ /* channel needs configuring for mem=>device, increment memory addr,
+ * sync to pclk, half-word transfers to the IIS-FIFO. */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC |
+ S3C2410_DISRCC_APB, prtd->params->dma_addr);
+
+ s3c2410_dma_config(prtd->params->channel,
+ prtd->params->dma_size,
+ S3C2410_DCON_SYNC_PCLK |
+ S3C2410_DCON_HANDSHAKE);
+ } else {
+ s3c2410_dma_config(prtd->params->channel,
+ prtd->params->dma_size,
+ S3C2410_DCON_HANDSHAKE |
+ S3C2410_DCON_SYNC_PCLK);
+
+ s3c2410_dma_devconfig(prtd->params->channel,
+ S3C2410_DMASRC_HW, 0x3,
+ prtd->params->dma_addr);
+ }
+
+ /* flush the DMA channel */
+ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
+ prtd->dma_loaded = 0;
+ 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", __func__);
+
+ 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", __func__);
+
+ 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", __func__);
+
+ snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&prtd->lock);
+
+ 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", __func__);
+
+ if (!prtd)
+ DBG("s3c24xx_pcm_close called with prtd == NULL\n");
+
+ kfree(prtd);
+
+ 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", __func__);
+
+ 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", __func__);
+
+ 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", __func__);
+
+ 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_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ DBG("Entered %s\n", __func__);
+
+ 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 0000000..0088c79
--- /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/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c
new file mode 100644
index 0000000..8515d6f
--- /dev/null
+++ b/sound/soc/s3c24xx/smdk2443_wm9710.c
@@ -0,0 +1,81 @@
+/*
+ * smdk2443_wm9710.c -- SoC audio for smdk2443
+ *
+ * Copyright 2007 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/ac97.h"
+#include "s3c24xx-pcm.h"
+#include "s3c24xx-ac97.h"
+
+static struct snd_soc_machine smdk2443;
+
+static struct snd_soc_dai_link smdk2443_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &s3c2443_ac97_dai[0],
+ .codec_dai = &ac97_dai,
+},
+};
+
+static struct snd_soc_machine smdk2443 = {
+ .name = "SMDK2443",
+ .dai_link = smdk2443_dai,
+ .num_links = ARRAY_SIZE(smdk2443_dai),
+};
+
+static struct snd_soc_device smdk2443_snd_ac97_devdata = {
+ .machine = &smdk2443,
+ .platform = &s3c24xx_soc_platform,
+ .codec_dev = &soc_codec_dev_ac97,
+};
+
+static struct platform_device *smdk2443_snd_ac97_device;
+
+static int __init smdk2443_init(void)
+{
+ int ret;
+
+ smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk2443_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk2443_snd_ac97_device,
+ &smdk2443_snd_ac97_devdata);
+ smdk2443_snd_ac97_devdata.dev = &smdk2443_snd_ac97_device->dev;
+ ret = platform_device_add(smdk2443_snd_ac97_device);
+
+ if (ret)
+ platform_device_put(smdk2443_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit smdk2443_exit(void)
+{
+ platform_device_unregister(smdk2443_snd_ac97_device);
+}
+
+module_init(smdk2443_init);
+module_exit(smdk2443_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
new file mode 100644
index 0000000..54bd604
--- /dev/null
+++ b/sound/soc/sh/Kconfig
@@ -0,0 +1,38 @@
+menu "SoC Audio support for SuperH"
+ depends on SUPERH
+
+config SND_SOC_PCM_SH7760
+ tristate "SoC Audio support for Renesas SH7760"
+ depends on CPU_SUBTYPE_SH7760 && SH_DMABRG
+ help
+ Enable this option for SH7760 AC97/I2S audio support.
+
+
+##
+## Audio unit modules
+##
+
+config SND_SOC_SH4_HAC
+ tristate
+ select AC97_BUS
+ select SND_SOC_AC97_BUS
+
+config SND_SOC_SH4_SSI
+ tristate
+
+
+
+##
+## Boards
+##
+
+config SND_SH7760_AC97
+ tristate "SH7760 AC97 sound support"
+ depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760
+ select SND_SOC_SH4_HAC
+ select SND_SOC_AC97_CODEC
+ help
+ This option enables generic sound support for the first
+ AC97 unit of the SH7760.
+
+endmenu
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
new file mode 100644
index 0000000..a8e8ab8
--- /dev/null
+++ b/sound/soc/sh/Makefile
@@ -0,0 +1,14 @@
+## DMA engines
+snd-soc-dma-sh7760-objs := dma-sh7760.o
+obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o
+
+## audio units found on some SH-4
+snd-soc-hac-objs := hac.o
+snd-soc-ssi-objs := ssi.o
+obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o
+obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o
+
+## boards
+snd-soc-sh7760-ac97-objs := sh7760-ac97.o
+
+obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o
diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c
new file mode 100644
index 0000000..9faa126
--- /dev/null
+++ b/sound/soc/sh/dma-sh7760.c
@@ -0,0 +1,353 @@
+/*
+ * SH7760 ("camelot") DMABRG audio DMA unit support
+ *
+ * Copyright (C) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which
+ * trigger an interrupt when one half of the programmed transfer size
+ * has been xmitted.
+ *
+ * FIXME: little-endian only for now
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <asm/dmabrg.h>
+
+
+/* registers and bits */
+#define BRGATXSAR 0x00
+#define BRGARXDAR 0x04
+#define BRGATXTCR 0x08
+#define BRGARXTCR 0x0C
+#define BRGACR 0x10
+#define BRGATXTCNT 0x14
+#define BRGARXTCNT 0x18
+
+#define ACR_RAR (1 << 18)
+#define ACR_RDS (1 << 17)
+#define ACR_RDE (1 << 16)
+#define ACR_TAR (1 << 2)
+#define ACR_TDS (1 << 1)
+#define ACR_TDE (1 << 0)
+
+/* receiver/transmitter data alignment */
+#define ACR_RAM_NONE (0 << 24)
+#define ACR_RAM_4BYTE (1 << 24)
+#define ACR_RAM_2WORD (2 << 24)
+#define ACR_TAM_NONE (0 << 8)
+#define ACR_TAM_4BYTE (1 << 8)
+#define ACR_TAM_2WORD (2 << 8)
+
+
+struct camelot_pcm {
+ unsigned long mmio; /* DMABRG audio channel control reg MMIO */
+ unsigned int txid; /* ID of first DMABRG IRQ for this unit */
+
+ struct snd_pcm_substream *tx_ss;
+ unsigned long tx_period_size;
+ unsigned int tx_period;
+
+ struct snd_pcm_substream *rx_ss;
+ unsigned long rx_period_size;
+ unsigned int rx_period;
+
+} cam_pcm_data[2] = {
+ {
+ .mmio = 0xFE3C0040,
+ .txid = DMABRGIRQ_A0TXF,
+ },
+ {
+ .mmio = 0xFE3C0060,
+ .txid = DMABRGIRQ_A1TXF,
+ },
+};
+
+#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x)))
+
+/*
+ * set a minimum of 16kb per period, to avoid interrupt-"storm" and
+ * resulting skipping. In general, the bigger the minimum size, the
+ * better for overall system performance. (The SH7760 is a puny CPU
+ * with a slow SDRAM interface and poor internal bus bandwidth,
+ * *especially* when the LCDC is active). The minimum for the DMAC
+ * is 8 bytes; 16kbytes are enough to get skip-free playback of a
+ * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain
+ * reasonable responsiveness in MPlayer.
+ */
+#define DMABRG_PERIOD_MIN 16 * 1024
+#define DMABRG_PERIOD_MAX 0x03fffffc
+#define DMABRG_PREALLOC_BUFFER 32 * 1024
+#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024
+
+/* support everything the SSI supports */
+#define DMABRG_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define DMABRG_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_pcm_hardware camelot_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = DMABRG_FMTS,
+ .rates = DMABRG_RATES,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 8, /* max of the SSI */
+ .buffer_bytes_max = DMABRG_PERIOD_MAX,
+ .period_bytes_min = DMABRG_PERIOD_MIN,
+ .period_bytes_max = DMABRG_PERIOD_MAX / 2,
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 128,
+};
+
+static void camelot_txdma(void *data)
+{
+ struct camelot_pcm *cam = data;
+ cam->tx_period ^= 1;
+ snd_pcm_period_elapsed(cam->tx_ss);
+}
+
+static void camelot_rxdma(void *data)
+{
+ struct camelot_pcm *cam = data;
+ cam->rx_period ^= 1;
+ snd_pcm_period_elapsed(cam->rx_ss);
+}
+
+static int camelot_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int ret, dmairq;
+
+ snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware);
+
+ /* DMABRG buffer half/full events */
+ dmairq = (recv) ? cam->txid + 2 : cam->txid;
+ if (recv) {
+ cam->rx_ss = substream;
+ ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam);
+ if (unlikely(ret)) {
+ pr_debug("audio unit %d irqs already taken!\n",
+ rtd->dai->cpu_dai->id);
+ return -EBUSY;
+ }
+ (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam);
+ } else {
+ cam->tx_ss = substream;
+ ret = dmabrg_request_irq(dmairq, camelot_txdma, cam);
+ if (unlikely(ret)) {
+ pr_debug("audio unit %d irqs already taken!\n",
+ rtd->dai->cpu_dai->id);
+ return -EBUSY;
+ }
+ (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam);
+ }
+ return 0;
+}
+
+static int camelot_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int dmairq;
+
+ dmairq = (recv) ? cam->txid + 2 : cam->txid;
+
+ if (recv)
+ cam->rx_ss = NULL;
+ else
+ cam->tx_ss = NULL;
+
+ dmabrg_free_irq(dmairq + 1);
+ dmabrg_free_irq(dmairq);
+
+ return 0;
+}
+
+static int camelot_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ int ret;
+
+ ret = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (ret < 0)
+ return ret;
+
+ if (recv) {
+ cam->rx_period_size = params_period_bytes(hw_params);
+ cam->rx_period = 0;
+ } else {
+ cam->tx_period_size = params_period_bytes(hw_params);
+ cam->tx_period = 0;
+ }
+ return 0;
+}
+
+static int camelot_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int camelot_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+
+ pr_debug("PCM data: addr 0x%08ulx len %d\n",
+ (u32)runtime->dma_addr, runtime->dma_bytes);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area;
+ BRGREG(BRGATXTCR) = runtime->dma_bytes;
+ } else {
+ BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area;
+ BRGREG(BRGARXTCR) = runtime->dma_bytes;
+ }
+
+ return 0;
+}
+
+static inline void dmabrg_play_dma_start(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* start DMABRG engine: XFER start, auto-addr-reload */
+ BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD;
+}
+
+static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* forcibly terminate data transmission */
+ BRGREG(BRGACR) = acr | ACR_TDS;
+}
+
+static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* start DMABRG engine: recv start, auto-reload */
+ BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD;
+}
+
+static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam)
+{
+ unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+ /* forcibly terminate data receiver */
+ BRGREG(BRGACR) = acr | ACR_RDS;
+}
+
+static int camelot_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (recv)
+ dmabrg_rec_dma_start(cam);
+ else
+ dmabrg_play_dma_start(cam);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (recv)
+ dmabrg_rec_dma_stop(cam);
+ else
+ dmabrg_play_dma_stop(cam);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id];
+ int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+ unsigned long pos;
+
+ /* cannot use the DMABRG pointer register: under load, by the
+ * time ALSA comes around to read the register, it is already
+ * far ahead (or worse, already done with the fragment) of the
+ * position at the time the IRQ was triggered, which results in
+ * fast-playback sound in my test application (ScummVM)
+ */
+ if (recv)
+ pos = cam->rx_period ? cam->rx_period_size : 0;
+ else
+ pos = cam->tx_period ? cam->tx_period_size : 0;
+
+ return bytes_to_frames(runtime, pos);
+}
+
+static struct snd_pcm_ops camelot_pcm_ops = {
+ .open = camelot_pcm_open,
+ .close = camelot_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = camelot_hw_params,
+ .hw_free = camelot_hw_free,
+ .prepare = camelot_prepare,
+ .trigger = camelot_trigger,
+ .pointer = camelot_pos,
+};
+
+static void camelot_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int camelot_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
+ * in MMAP mode (i.e. aplay -M)
+ */
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX);
+
+ return 0;
+}
+
+struct snd_soc_platform sh7760_soc_platform = {
+ .name = "sh7760-pcm",
+ .pcm_ops = &camelot_pcm_ops,
+ .pcm_new = camelot_pcm_new,
+ .pcm_free = camelot_pcm_free,
+};
+EXPORT_SYMBOL_GPL(sh7760_soc_platform);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c
new file mode 100644
index 0000000..df7bc34
--- /dev/null
+++ b/sound/soc/sh/hac.c
@@ -0,0 +1,318 @@
+/*
+ * Hitachi Audio Controller (AC97) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable HAC output pins!
+ */
+
+/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only
+ * the FIRST can be used since ASoC does not pass any information to the
+ * ac97_read/write() functions regarding WHICH unit to use. You'll have
+ * to edit the code a bit to use the other AC97 unit. --mlau
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+/* regs and bits */
+#define HACCR 0x08
+#define HACCSAR 0x20
+#define HACCSDR 0x24
+#define HACPCML 0x28
+#define HACPCMR 0x2C
+#define HACTIER 0x50
+#define HACTSR 0x54
+#define HACRIER 0x58
+#define HACRSR 0x5C
+#define HACACR 0x60
+
+#define CR_CR (1 << 15) /* "codec-ready" indicator */
+#define CR_CDRT (1 << 11) /* cold reset */
+#define CR_WMRT (1 << 10) /* warm reset */
+#define CR_B9 (1 << 9) /* the mysterious "bit 9" */
+#define CR_ST (1 << 5) /* AC97 link start bit */
+
+#define CSAR_RD (1 << 19) /* AC97 data read bit */
+#define CSAR_WR (0)
+
+#define TSR_CMDAMT (1 << 31)
+#define TSR_CMDDMT (1 << 30)
+
+#define RSR_STARY (1 << 22)
+#define RSR_STDRY (1 << 21)
+
+#define ACR_DMARX16 (1 << 30)
+#define ACR_DMATX16 (1 << 29)
+#define ACR_TX12ATOM (1 << 26)
+#define ACR_DMARX20 ((1 << 24) | (1 << 22))
+#define ACR_DMATX20 ((1 << 23) | (1 << 21))
+
+#define CSDR_SHIFT 4
+#define CSDR_MASK (0xffff << CSDR_SHIFT)
+#define CSAR_SHIFT 12
+#define CSAR_MASK (0x7f << CSAR_SHIFT)
+
+#define AC97_WRITE_RETRY 1
+#define AC97_READ_RETRY 5
+
+/* manual-suggested AC97 codec access timeouts (us) */
+#define TMO_E1 500 /* 21 < E1 < 1000 */
+#define TMO_E2 13 /* 13 < E2 */
+#define TMO_E3 21 /* 21 < E3 */
+#define TMO_E4 500 /* 21 < E4 < 1000 */
+
+struct hac_priv {
+ unsigned long mmio; /* HAC base address */
+} hac_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+ {
+ .mmio = 0xFE240000,
+ },
+ {
+ .mmio = 0xFE250000,
+ },
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+ {
+ .mmio = 0xFFE40000,
+ },
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg)))
+
+/*
+ * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906)
+ */
+static int hac_get_codec_data(struct hac_priv *hac, unsigned short r,
+ unsigned short *v)
+{
+ unsigned int to1, to2, i;
+ unsigned short adr;
+
+ for (i = AC97_READ_RETRY; i; i--) {
+ *v = 0;
+ /* wait for HAC to receive something from the codec */
+ for (to1 = TMO_E4;
+ to1 && !(HACREG(HACRSR) & RSR_STARY);
+ --to1)
+ udelay(1);
+ for (to2 = TMO_E4;
+ to2 && !(HACREG(HACRSR) & RSR_STDRY);
+ --to2)
+ udelay(1);
+
+ if (!to1 && !to2)
+ return 0; /* codec comm is down */
+
+ adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT);
+ *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT);
+
+ HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+
+ if (r == adr)
+ break;
+
+ /* manual says: wait at least 21 usec before retrying */
+ udelay(21);
+ }
+ HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+ return i;
+}
+
+static unsigned short hac_read_codec_aux(struct hac_priv *hac,
+ unsigned short reg)
+{
+ unsigned short val;
+ unsigned int i, to;
+
+ for (i = AC97_READ_RETRY; i; i--) {
+ /* send_read_request */
+ local_irq_disable();
+ HACREG(HACTSR) &= ~(TSR_CMDAMT);
+ HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD;
+ local_irq_enable();
+
+ for (to = TMO_E3;
+ to && !(HACREG(HACTSR) & TSR_CMDAMT);
+ --to)
+ udelay(1);
+
+ HACREG(HACTSR) &= ~TSR_CMDAMT;
+ val = 0;
+ if (hac_get_codec_data(hac, reg, &val) != 0)
+ break;
+ }
+
+ return i ? val : ~0;
+}
+
+static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ unsigned int i, to;
+ /* write_codec_aux */
+ for (i = AC97_WRITE_RETRY; i; i--) {
+ /* send_write_request */
+ local_irq_disable();
+ HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT);
+ HACREG(HACCSDR) = (val << CSDR_SHIFT);
+ HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD);
+ local_irq_enable();
+
+ /* poll-wait for CMDAMT and CMDDMT */
+ for (to = TMO_E1;
+ to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT));
+ --to)
+ udelay(1);
+
+ HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT);
+ if (to)
+ break;
+ /* timeout, try again */
+ }
+}
+
+static unsigned short hac_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ return hac_read_codec_aux(hac, reg);
+}
+
+static void hac_ac97_warmrst(struct snd_ac97 *ac97)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac = &hac_cpu_data[unit_id];
+ unsigned int tmo;
+
+ HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9;
+ msleep(10);
+ HACREG(HACCR) = CR_ST | CR_B9;
+ for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--)
+ udelay(1);
+
+ if (!tmo)
+ printk(KERN_INFO "hac: reset: AC97 link down!\n");
+ /* settings this bit lets us have a conversation with codec */
+ HACREG(HACACR) |= ACR_TX12ATOM;
+}
+
+static void hac_ac97_coldrst(struct snd_ac97 *ac97)
+{
+ int unit_id = 0 /* ac97->private_data */;
+ struct hac_priv *hac;
+ hac = &hac_cpu_data[unit_id];
+
+ HACREG(HACCR) = 0;
+ HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9;
+ msleep(10);
+ hac_ac97_warmrst(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = hac_ac97_read,
+ .write = hac_ac97_write,
+ .reset = hac_ac97_coldrst,
+ .warm_reset = hac_ac97_warmrst,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int hac_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hac_priv *hac = &hac_cpu_data[rtd->dai->cpu_dai->id];
+ int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+ switch (params->msbits) {
+ case 16:
+ HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16;
+ HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20;
+ break;
+ case 20:
+ HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16;
+ HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20;
+ break;
+ default:
+ pr_debug("hac: invalid depth %d bit\n", params->msbits);
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+#define AC97_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+#define AC97_FMTS \
+ SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai sh4_hac_dai[] = {
+{
+ .name = "HAC0",
+ .id = 0,
+ .type = SND_SOC_DAI_AC97,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = {
+ .hw_params = hac_hw_params,
+ },
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+ .name = "HAC1",
+ .id = 1,
+ .type = SND_SOC_DAI_AC97,
+ .playback = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .capture = {
+ .rates = AC97_RATES,
+ .formats = AC97_FMTS,
+ .channels_min = 2,
+ .channels_max = 2,
+ },
+ .ops = {
+ .hw_params = hac_hw_params,
+ },
+
+},
+#endif
+};
+EXPORT_SYMBOL_GPL(sh4_hac_dai);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c
new file mode 100644
index 0000000..92bfaf4
--- /dev/null
+++ b/sound/soc/sh/sh7760-ac97.c
@@ -0,0 +1,91 @@
+/*
+ * Generic AC97 sound support for SH7760
+ *
+ * (c) 2007 Manuel Lauss
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/io.h>
+
+#include "../codecs/ac97.h"
+
+#define IPSEL 0xFE400034
+
+/* platform specific structs can be declared here */
+extern struct snd_soc_dai sh4_hac_dai[2];
+extern struct snd_soc_platform sh7760_soc_platform;
+
+static int machine_init(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+static struct snd_soc_dai_link sh7760_ac97_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 HiFi",
+ .cpu_dai = &sh4_hac_dai[0], /* HAC0 */
+ .codec_dai = &ac97_dai,
+ .init = machine_init,
+ .ops = NULL,
+};
+
+static struct snd_soc_machine sh7760_ac97_soc_machine = {
+ .name = "SH7760 AC97",
+ .dai_link = &sh7760_ac97_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device sh7760_ac97_snd_devdata = {
+ .machine = &sh7760_ac97_soc_machine,
+ .platform = &sh7760_soc_platform,
+ .codec_dev = &soc_codec_dev_ac97,
+};
+
+static struct platform_device *sh7760_ac97_snd_device;
+
+static int __init sh7760_ac97_init(void)
+{
+ int ret;
+ unsigned short ipsel;
+
+ /* enable both AC97 controllers in pinmux reg */
+ ipsel = ctrl_inw(IPSEL);
+ ctrl_outw(ipsel | (3 << 10), IPSEL);
+
+ ret = -ENOMEM;
+ sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!sh7760_ac97_snd_device)
+ goto out;
+
+ platform_set_drvdata(sh7760_ac97_snd_device,
+ &sh7760_ac97_snd_devdata);
+ sh7760_ac97_snd_devdata.dev = &sh7760_ac97_snd_device->dev;
+ ret = platform_device_add(sh7760_ac97_snd_device);
+
+ if (ret)
+ platform_device_put(sh7760_ac97_snd_device);
+
+out:
+ return ret;
+}
+
+static void __exit sh7760_ac97_exit(void)
+{
+ platform_device_unregister(sh7760_ac97_snd_device);
+}
+
+module_init(sh7760_ac97_init);
+module_exit(sh7760_ac97_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c
new file mode 100644
index 0000000..55c3464
--- /dev/null
+++ b/sound/soc/sh/ssi.c
@@ -0,0 +1,399 @@
+/*
+ * Serial Sound Interface (I2S) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * licensed under the terms outlined in the file COPYING at the root
+ * of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable SSI output pins!
+ */
+
+/*
+ * LIMITATIONS:
+ * The SSI unit has only one physical data line, so full duplex is
+ * impossible. This can be remedied on the SH7760 by using the
+ * other SSI unit for recording; however the SH7780 has only 1 SSI
+ * unit, and its pins are shared with the AC97 unit, among others.
+ *
+ * FEATURES:
+ * The SSI features "compressed mode": in this mode it continuously
+ * streams PCM data over the I2S lines and uses LRCK as a handshake
+ * signal. Can be used to send compressed data (AC3/DTS) to a DSP.
+ * The number of bits sent over the wire in a frame can be adjusted
+ * and can be independent from the actual sample bit depth. This is
+ * useful to support TDM mode codecs like the AD1939 which have a
+ * fixed TDM slot size, regardless of sample resolution.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/io.h>
+
+#define SSICR 0x00
+#define SSISR 0x04
+
+#define CR_DMAEN (1 << 28)
+#define CR_CHNL_SHIFT 22
+#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT)
+#define CR_DWL_SHIFT 19
+#define CR_DWL_MASK (7 << CR_DWL_SHIFT)
+#define CR_SWL_SHIFT 16
+#define CR_SWL_MASK (7 << CR_SWL_SHIFT)
+#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */
+#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */
+#define CR_SCKP (1 << 13) /* I2Sclock polarity */
+#define CR_SWSP (1 << 12) /* LRCK polarity */
+#define CR_SPDP (1 << 11)
+#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */
+#define CR_PDTA (1 << 9) /* fifo data alignment */
+#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */
+#define CR_BREN (1 << 7) /* clock gating in burst mode */
+#define CR_CKDIV_SHIFT 4
+#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */
+#define CR_MUTE (1 << 3) /* SSI mute */
+#define CR_CPEN (1 << 2) /* compressed mode */
+#define CR_TRMD (1 << 1) /* transmit/receive select */
+#define CR_EN (1 << 0) /* enable SSI */
+
+#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg)))
+
+struct ssi_priv {
+ unsigned long mmio;
+ unsigned long sysclk;
+ int inuse;
+} ssi_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+ {
+ .mmio = 0xFE680000,
+ },
+ {
+ .mmio = 0xFE690000,
+ },
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+ {
+ .mmio = 0xFFE70000,
+ },
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+/*
+ * track usage of the SSI; it is simplex-only so prevent attempts of
+ * concurrent playback + capture. FIXME: any locking required?
+ */
+static int ssi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id];
+ if (ssi->inuse) {
+ pr_debug("ssi: already in use!\n");
+ return -EBUSY;
+ } else
+ ssi->inuse = 1;
+ return 0;
+}
+
+static void ssi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id];
+
+ ssi->inuse = 0;
+}
+
+static int ssi_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id];
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ SSIREG(SSICR) |= CR_DMAEN | CR_EN;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id];
+ unsigned long ssicr = SSIREG(SSICR);
+ unsigned int bits, channels, swl, recv, i;
+
+ channels = params_channels(params);
+ bits = params->msbits;
+ recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1;
+
+ pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr);
+ pr_debug("bits: %d channels: %d\n", bits, channels);
+
+ ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA |
+ CR_SWL_MASK);
+
+ /* direction (send/receive) */
+ if (!recv)
+ ssicr |= CR_TRMD; /* transmit */
+
+ /* channels */
+ if ((channels < 2) || (channels > 8) || (channels & 1)) {
+ pr_debug("ssi: invalid number of channels\n");
+ return -EINVAL;
+ }
+ ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT;
+
+ /* DATA WORD LENGTH (DWL): databits in audio sample */
+ i = 0;
+ switch (bits) {
+ case 32: ++i;
+ case 24: ++i;
+ case 22: ++i;
+ case 20: ++i;
+ case 18: ++i;
+ case 16: ++i;
+ ssicr |= i << CR_DWL_SHIFT;
+ case 8: break;
+ default:
+ pr_debug("ssi: invalid sample width\n");
+ return -EINVAL;
+ }
+
+ /*
+ * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S
+ * wires. This is usually bits_per_sample x channels/2; i.e. in
+ * Stereo mode the SWL equals DWL. SWL can be bigger than the
+ * product of (channels_per_slot x samplebits), e.g. for codecs
+ * like the AD1939 which only accept 32bit wide TDM slots. For
+ * "standard" I2S operation we set SWL = chans / 2 * DWL here.
+ * Waiting for ASoC to get TDM support ;-)
+ */
+ if ((bits > 16) && (bits <= 24)) {
+ bits = 24; /* these are padded by the SSI */
+ /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */
+ }
+ i = 0;
+ swl = (bits * channels) / 2;
+ switch (swl) {
+ case 256: ++i;
+ case 128: ++i;
+ case 64: ++i;
+ case 48: ++i;
+ case 32: ++i;
+ case 16: ++i;
+ ssicr |= i << CR_SWL_SHIFT;
+ case 8: break;
+ default:
+ pr_debug("ssi: invalid system word length computed\n");
+ return -EINVAL;
+ }
+
+ SSIREG(SSICR) = ssicr;
+
+ pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr);
+ return 0;
+}
+
+static int ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id];
+
+ ssi->sysclk = freq;
+
+ return 0;
+}
+
+/*
+ * This divider is used to generate the SSI_SCK (I2S bitclock) from the
+ * clock at the HAC_BIT_CLK ("oversampling clock") pin.
+ */
+static int ssi_set_clkdiv(struct snd_soc_dai *dai, int did, int div)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ unsigned long ssicr;
+ int i;
+
+ i = 0;
+ ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK;
+ switch (div) {
+ case 16: ++i;
+ case 8: ++i;
+ case 4: ++i;
+ case 2: ++i;
+ SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT);
+ case 1: break;
+ default:
+ pr_debug("ssi: invalid sck divider %d\n", div);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+ unsigned long ssicr = SSIREG(SSICR);
+
+ pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr);
+
+ ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP |
+ CR_SWS_MASTER | CR_SCK_MASTER);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ssicr |= CR_DEL | CR_PDTA;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ssicr |= CR_DEL;
+ break;
+ default:
+ pr_debug("ssi: unsupported format\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT:
+ break;
+ case SND_SOC_DAIFMT_GATED:
+ ssicr |= CR_BREN;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ ssicr |= CR_SCKP; /* sample data at low clkedge */
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ ssicr |= CR_SCKP | CR_SWSP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ ssicr |= CR_SWSP; /* word select starts low */
+ break;
+ default:
+ pr_debug("ssi: invalid inversion\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ ssicr |= CR_SCK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ ssicr |= CR_SWS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ ssicr |= CR_SWS_MASTER | CR_SCK_MASTER;
+ break;
+ default:
+ pr_debug("ssi: invalid master/slave configuration\n");
+ return -EINVAL;
+ }
+
+ SSIREG(SSICR) = ssicr;
+ pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr);
+
+ return 0;
+}
+
+/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in
+ * Master mode, so really this is board specific; the SSI can do any
+ * rate with the right bitclk and divider settings.
+ */
+#define SSI_RATES \
+ SNDRV_PCM_RATE_8000_192000
+
+/* the SSI can do 8-32 bit samples, with 8 possible channels */
+#define SSI_FMTS \
+ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE)
+
+struct snd_soc_dai sh4_ssi_dai[] = {
+{
+ .name = "SSI0",
+ .id = 0,
+ .type = SND_SOC_DAI_I2S,
+ .playback = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .ops = {
+ .startup = ssi_startup,
+ .shutdown = ssi_shutdown,
+ .trigger = ssi_trigger,
+ .hw_params = ssi_hw_params,
+ },
+ .dai_ops = {
+ .set_sysclk = ssi_set_sysclk,
+ .set_clkdiv = ssi_set_clkdiv,
+ .set_fmt = ssi_set_fmt,
+ },
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+ .name = "SSI1",
+ .id = 1,
+ .type = SND_SOC_DAI_I2S,
+ .playback = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .capture = {
+ .rates = SSI_RATES,
+ .formats = SSI_FMTS,
+ .channels_min = 2,
+ .channels_max = 8,
+ },
+ .ops = {
+ .startup = ssi_startup,
+ .shutdown = ssi_shutdown,
+ .trigger = ssi_trigger,
+ .hw_params = ssi_hw_params,
+ },
+ .dai_ops = {
+ .set_sysclk = ssi_set_sysclk,
+ .set_clkdiv = ssi_set_clkdiv,
+ .set_fmt = ssi_set_fmt,
+ },
+},
+#endif
+};
+EXPORT_SYMBOL_GPL(sh4_ssi_dai);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
new file mode 100644
index 0000000..16c7453
--- /dev/null
+++ b/sound/soc/soc-core.c
@@ -0,0 +1,1891 @@
+/*
+ * soc-core.c -- ALSA SoC Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ * with code, comments and ideas from :-
+ * Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * TODO:
+ * o Add hw rules to enforce rates, etc.
+ * o More testing with other codecs/machines.
+ * o Add more codecs and platforms to ensure good API coverage.
+ * o Support TDM on PCM and I2S
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.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>
+
+/* debug */
+#define SOC_DEBUG 0
+#if SOC_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+static DEFINE_MUTEX(pcm_mutex);
+static DEFINE_MUTEX(io_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
+
+/*
+ * This is a timeout to do a DAPM powerdown after a stream is closed().
+ * It can be used to eliminate pops between different playback streams, e.g.
+ * between two audio tracks.
+ */
+static int pmdown_time = 5000;
+module_param(pmdown_time, int, 0);
+MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+
+/*
+ * 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;
+}
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+/* unregister ac97 codec */
+static int soc_ac97_dev_unregister(struct snd_soc_codec *codec)
+{
+ if (codec->ac97->dev.bus)
+ device_unregister(&codec->ac97->dev);
+ return 0;
+}
+
+/* stop no dev release warning */
+static void soc_ac97_device_release(struct device *dev){}
+
+/* register ac97 codec to bus */
+static int soc_ac97_dev_register(struct snd_soc_codec *codec)
+{
+ int err;
+
+ codec->ac97->dev.bus = &ac97_bus_type;
+ codec->ac97->dev.parent = NULL;
+ codec->ac97->dev.release = soc_ac97_device_release;
+
+ dev_set_name(&codec->ac97->dev, "%d-%d:%s",
+ codec->card->number, 0, codec->name);
+ err = device_register(&codec->ac97->dev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Can't register ac97 bus\n");
+ codec->ac97->dev.bus = NULL;
+ return err;
+ }
+ return 0;
+}
+#endif
+
+static inline const char *get_dai_name(int type)
+{
+ switch (type) {
+ case SND_SOC_DAI_AC97_BUS:
+ case SND_SOC_DAI_AC97:
+ return "AC97";
+ case SND_SOC_DAI_I2S:
+ return "I2S";
+ case SND_SOC_DAI_PCM:
+ return "PCM";
+ }
+ return NULL;
+}
+
+/*
+ * Called by ALSA when a PCM substream is opened, the runtime->hw record is
+ * then initialized and any private data can be allocated. This also calls
+ * startup for the cpu DAI, platform, machine and codec DAI.
+ */
+static int soc_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ /* startup the audio subsystem */
+ if (cpu_dai->ops.startup) {
+ ret = cpu_dai->ops.startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open interface %s\n",
+ cpu_dai->name);
+ goto out;
+ }
+ }
+
+ if (platform->pcm_ops->open) {
+ ret = platform->pcm_ops->open(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
+ goto platform_err;
+ }
+ }
+
+ if (codec_dai->ops.startup) {
+ ret = codec_dai->ops.startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open codec %s\n",
+ codec_dai->name);
+ goto codec_dai_err;
+ }
+ }
+
+ if (machine->ops && machine->ops->startup) {
+ ret = machine->ops->startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: %s startup failed\n", machine->name);
+ goto machine_err;
+ }
+ }
+
+ /* Check that the codec and cpu DAI's are compatible */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw.rate_min =
+ max(codec_dai->playback.rate_min,
+ cpu_dai->playback.rate_min);
+ runtime->hw.rate_max =
+ min(codec_dai->playback.rate_max,
+ cpu_dai->playback.rate_max);
+ runtime->hw.channels_min =
+ max(codec_dai->playback.channels_min,
+ cpu_dai->playback.channels_min);
+ runtime->hw.channels_max =
+ min(codec_dai->playback.channels_max,
+ cpu_dai->playback.channels_max);
+ runtime->hw.formats =
+ codec_dai->playback.formats & cpu_dai->playback.formats;
+ runtime->hw.rates =
+ codec_dai->playback.rates & cpu_dai->playback.rates;
+ } else {
+ runtime->hw.rate_min =
+ max(codec_dai->capture.rate_min,
+ cpu_dai->capture.rate_min);
+ runtime->hw.rate_max =
+ min(codec_dai->capture.rate_max,
+ cpu_dai->capture.rate_max);
+ runtime->hw.channels_min =
+ max(codec_dai->capture.channels_min,
+ cpu_dai->capture.channels_min);
+ runtime->hw.channels_max =
+ min(codec_dai->capture.channels_max,
+ cpu_dai->capture.channels_max);
+ runtime->hw.formats =
+ codec_dai->capture.formats & cpu_dai->capture.formats;
+ runtime->hw.rates =
+ codec_dai->capture.rates & cpu_dai->capture.rates;
+ }
+
+ snd_pcm_limit_hw_rates(runtime);
+ if (!runtime->hw.rates) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
+ codec_dai->name, cpu_dai->name);
+ goto machine_err;
+ }
+ if (!runtime->hw.formats) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
+ codec_dai->name, cpu_dai->name);
+ goto machine_err;
+ }
+ if (!runtime->hw.channels_min || !runtime->hw.channels_max) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
+ codec_dai->name, cpu_dai->name);
+ goto machine_err;
+ }
+
+ dbg("asoc: %s <-> %s info:\n", codec_dai->name, cpu_dai->name);
+ dbg("asoc: rate mask 0x%x\n", runtime->hw.rates);
+ dbg("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,
+ runtime->hw.channels_max);
+ dbg("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,
+ runtime->hw.rate_max);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->playback.active = codec_dai->playback.active = 1;
+ else
+ cpu_dai->capture.active = codec_dai->capture.active = 1;
+ cpu_dai->active = codec_dai->active = 1;
+ cpu_dai->runtime = runtime;
+ socdev->codec->active++;
+ mutex_unlock(&pcm_mutex);
+ return 0;
+
+machine_err:
+ if (machine->ops && machine->ops->shutdown)
+ machine->ops->shutdown(substream);
+
+codec_dai_err:
+ if (platform->pcm_ops->close)
+ platform->pcm_ops->close(substream);
+
+platform_err:
+ if (cpu_dai->ops.shutdown)
+ cpu_dai->ops.shutdown(substream);
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Power down the audio subsystem pmdown_time msecs after close is called.
+ * This is to ensure there are no pops or clicks in between any music tracks
+ * due to DAPM power cycling.
+ */
+static void close_delayed_work(struct work_struct *work)
+{
+ struct snd_soc_device *socdev =
+ container_of(work, struct snd_soc_device, delayed_work.work);
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_dai *codec_dai;
+ int i;
+
+ mutex_lock(&pcm_mutex);
+ for (i = 0; i < codec->num_dai; i++) {
+ codec_dai = &codec->dai[i];
+
+ dbg("pop wq checking: %s status: %s waiting: %s\n",
+ codec_dai->playback.stream_name,
+ codec_dai->playback.active ? "active" : "inactive",
+ codec_dai->pop_wait ? "yes" : "no");
+
+ /* are we waiting on this codec DAI stream */
+ if (codec_dai->pop_wait == 1) {
+
+ /* Reduce power if no longer active */
+ if (codec->active == 0) {
+ dbg("pop wq D1 %s %s\n", codec->name,
+ codec_dai->playback.stream_name);
+ snd_soc_dapm_set_bias_level(socdev,
+ SND_SOC_BIAS_PREPARE);
+ }
+
+ codec_dai->pop_wait = 0;
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+
+ /* Fall into standby if no longer active */
+ if (codec->active == 0) {
+ dbg("pop wq D3 %s %s\n", codec->name,
+ codec_dai->playback.stream_name);
+ snd_soc_dapm_set_bias_level(socdev,
+ SND_SOC_BIAS_STANDBY);
+ }
+ }
+ }
+ mutex_unlock(&pcm_mutex);
+}
+
+/*
+ * Called by ALSA when a PCM substream is closed. Private data can be
+ * freed here. The cpu DAI, codec DAI, machine and platform are also
+ * shutdown.
+ */
+static int soc_codec_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ mutex_lock(&pcm_mutex);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->playback.active = codec_dai->playback.active = 0;
+ else
+ cpu_dai->capture.active = codec_dai->capture.active = 0;
+
+ if (codec_dai->playback.active == 0 &&
+ codec_dai->capture.active == 0) {
+ cpu_dai->active = codec_dai->active = 0;
+ }
+ codec->active--;
+
+ /* Muting the DAC suppresses artifacts caused during digital
+ * shutdown, for example from stopping clocks.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_digital_mute(codec_dai, 1);
+
+ if (cpu_dai->ops.shutdown)
+ cpu_dai->ops.shutdown(substream);
+
+ if (codec_dai->ops.shutdown)
+ codec_dai->ops.shutdown(substream);
+
+ if (machine->ops && machine->ops->shutdown)
+ machine->ops->shutdown(substream);
+
+ if (platform->pcm_ops->close)
+ platform->pcm_ops->close(substream);
+ cpu_dai->runtime = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* start delayed pop wq here for playback streams */
+ codec_dai->pop_wait = 1;
+ schedule_delayed_work(&socdev->delayed_work,
+ msecs_to_jiffies(pmdown_time));
+ } else {
+ /* capture streams can be powered down now */
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+
+ if (codec->active == 0 && codec_dai->pop_wait == 0)
+ snd_soc_dapm_set_bias_level(socdev,
+ SND_SOC_BIAS_STANDBY);
+ }
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+/*
+ * Called by ALSA when the PCM substream is prepared, can set format, sample
+ * rate, etc. This function is non atomic and can be called multiple times,
+ * it can refer to the runtime info.
+ */
+static int soc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ if (machine->ops && machine->ops->prepare) {
+ ret = machine->ops->prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: machine prepare error\n");
+ goto out;
+ }
+ }
+
+ if (platform->pcm_ops->prepare) {
+ ret = platform->pcm_ops->prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: platform prepare error\n");
+ goto out;
+ }
+ }
+
+ if (codec_dai->ops.prepare) {
+ ret = codec_dai->ops.prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: codec DAI prepare error\n");
+ goto out;
+ }
+ }
+
+ if (cpu_dai->ops.prepare) {
+ ret = cpu_dai->ops.prepare(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: cpu DAI prepare error\n");
+ goto out;
+ }
+ }
+
+ /* we only want to start a DAPM playback stream if we are not waiting
+ * on an existing one stopping */
+ if (codec_dai->pop_wait) {
+ /* we are waiting for the delayed work to start */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ snd_soc_dapm_stream_event(socdev->codec,
+ codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else {
+ codec_dai->pop_wait = 0;
+ cancel_delayed_work(&socdev->delayed_work);
+ snd_soc_dai_digital_mute(codec_dai, 0);
+ }
+ } else {
+ /* no delayed work - do we need to power up codec */
+ if (codec->bias_level != SND_SOC_BIAS_ON) {
+
+ snd_soc_dapm_set_bias_level(socdev,
+ SND_SOC_BIAS_PREPARE);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+
+ snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON);
+ snd_soc_dai_digital_mute(codec_dai, 0);
+
+ } else {
+ /* codec already powered - power on widgets */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else
+ snd_soc_dapm_stream_event(codec,
+ codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+
+ snd_soc_dai_digital_mute(codec_dai, 0);
+ }
+ }
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Called by ALSA when the hardware params are set by application. This
+ * function can also be called multiple times and can allocate buffers
+ * (using snd_pcm_lib_* ). It's non-atomic.
+ */
+static int soc_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_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ if (machine->ops && machine->ops->hw_params) {
+ ret = machine->ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: machine hw_params failed\n");
+ goto out;
+ }
+ }
+
+ if (codec_dai->ops.hw_params) {
+ ret = codec_dai->ops.hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't set codec %s hw params\n",
+ codec_dai->name);
+ goto codec_err;
+ }
+ }
+
+ if (cpu_dai->ops.hw_params) {
+ ret = cpu_dai->ops.hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: interface %s hw params failed\n",
+ cpu_dai->name);
+ goto interface_err;
+ }
+ }
+
+ if (platform->pcm_ops->hw_params) {
+ ret = platform->pcm_ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: platform %s hw params failed\n",
+ platform->name);
+ goto platform_err;
+ }
+ }
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+
+platform_err:
+ if (cpu_dai->ops.hw_free)
+ cpu_dai->ops.hw_free(substream);
+
+interface_err:
+ if (codec_dai->ops.hw_free)
+ codec_dai->ops.hw_free(substream);
+
+codec_err:
+ if (machine->ops && machine->ops->hw_free)
+ machine->ops->hw_free(substream);
+
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Free's resources allocated by hw_params, can be called multiple times
+ */
+static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ mutex_lock(&pcm_mutex);
+
+ /* apply codec digital mute */
+ if (!codec->active)
+ snd_soc_dai_digital_mute(codec_dai, 1);
+
+ /* free any machine hw params */
+ if (machine->ops && machine->ops->hw_free)
+ machine->ops->hw_free(substream);
+
+ /* free any DMA resources */
+ if (platform->pcm_ops->hw_free)
+ platform->pcm_ops->hw_free(substream);
+
+ /* now free hw params for the DAI's */
+ if (codec_dai->ops.hw_free)
+ codec_dai->ops.hw_free(substream);
+
+ if (cpu_dai->ops.hw_free)
+ cpu_dai->ops.hw_free(substream);
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ int ret;
+
+ if (codec_dai->ops.trigger) {
+ ret = codec_dai->ops.trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (platform->pcm_ops->trigger) {
+ ret = platform->pcm_ops->trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (cpu_dai->ops.trigger) {
+ ret = cpu_dai->ops.trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/* ASoC PCM operations */
+static struct snd_pcm_ops soc_pcm_ops = {
+ .open = soc_pcm_open,
+ .close = soc_codec_close,
+ .hw_params = soc_pcm_hw_params,
+ .hw_free = soc_pcm_hw_free,
+ .prepare = soc_pcm_prepare,
+ .trigger = soc_pcm_trigger,
+};
+
+#ifdef CONFIG_PM
+/* powers down audio subsystem for suspend */
+static int soc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+
+ /* Due to the resume being scheduled into a workqueue we could
+ * suspend before that's finished - wait for it to complete.
+ */
+ snd_power_lock(codec->card);
+ snd_power_wait(codec->card, SNDRV_CTL_POWER_D0);
+ snd_power_unlock(codec->card);
+
+ /* we're going to block userspace touching us until resume completes */
+ snd_power_change_state(codec->card, SNDRV_CTL_POWER_D3hot);
+
+ /* mute any active DAC's */
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *dai = machine->dai_link[i].codec_dai;
+ if (dai->dai_ops.digital_mute && dai->playback.active)
+ dai->dai_ops.digital_mute(dai, 1);
+ }
+
+ /* suspend all pcms */
+ for (i = 0; i < machine->num_links; i++)
+ snd_pcm_suspend_all(machine->dai_link[i].pcm);
+
+ if (machine->suspend_pre)
+ machine->suspend_pre(pdev, state);
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->suspend && cpu_dai->type != SND_SOC_DAI_AC97)
+ cpu_dai->suspend(pdev, cpu_dai);
+ if (platform->suspend)
+ platform->suspend(pdev, cpu_dai);
+ }
+
+ /* close any waiting streams and save state */
+ run_delayed_work(&socdev->delayed_work);
+ codec->suspend_bias_level = codec->bias_level;
+
+ for (i = 0; i < codec->num_dai; i++) {
+ char *stream = codec->dai[i].playback.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+ stream = codec->dai[i].capture.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+ }
+
+ if (codec_dev->suspend)
+ codec_dev->suspend(pdev, state);
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->suspend && cpu_dai->type == SND_SOC_DAI_AC97)
+ cpu_dai->suspend(pdev, cpu_dai);
+ }
+
+ if (machine->suspend_post)
+ machine->suspend_post(pdev, state);
+
+ return 0;
+}
+
+/* deferred resume work, so resume can complete before we finished
+ * setting our codec back up, which can be very slow on I2C
+ */
+static void soc_resume_deferred(struct work_struct *work)
+{
+ struct snd_soc_device *socdev = container_of(work,
+ struct snd_soc_device,
+ deferred_resume_work);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct platform_device *pdev = to_platform_device(socdev->dev);
+ int i;
+
+ /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
+ * so userspace apps are blocked from touching us
+ */
+
+ dev_info(socdev->dev, "starting resume work\n");
+
+ if (machine->resume_pre)
+ machine->resume_pre(pdev);
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->resume && cpu_dai->type == SND_SOC_DAI_AC97)
+ cpu_dai->resume(pdev, cpu_dai);
+ }
+
+ if (codec_dev->resume)
+ codec_dev->resume(pdev);
+
+ for (i = 0; i < codec->num_dai; i++) {
+ char *stream = codec->dai[i].playback.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_RESUME);
+ stream = codec->dai[i].capture.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_RESUME);
+ }
+
+ /* unmute any active DACs */
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *dai = machine->dai_link[i].codec_dai;
+ if (dai->dai_ops.digital_mute && dai->playback.active)
+ dai->dai_ops.digital_mute(dai, 0);
+ }
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->resume && cpu_dai->type != SND_SOC_DAI_AC97)
+ cpu_dai->resume(pdev, cpu_dai);
+ if (platform->resume)
+ platform->resume(pdev, cpu_dai);
+ }
+
+ if (machine->resume_post)
+ machine->resume_post(pdev);
+
+ dev_info(socdev->dev, "resume work completed\n");
+
+ /* userspace can access us now we are back as we were before */
+ snd_power_change_state(codec->card, SNDRV_CTL_POWER_D0);
+}
+
+/* powers up audio subsystem after a suspend */
+static int soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ dev_info(socdev->dev, "scheduling resume work\n");
+
+ if (!schedule_work(&socdev->deferred_resume_work))
+ dev_err(socdev->dev, "work item may be lost\n");
+
+ return 0;
+}
+
+#else
+#define soc_suspend NULL
+#define soc_resume NULL
+#endif
+
+/* probes a new socdev */
+static int soc_probe(struct platform_device *pdev)
+{
+ int ret = 0, i;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+
+ if (machine->probe) {
+ ret = machine->probe(pdev);
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->probe) {
+ ret = cpu_dai->probe(pdev, cpu_dai);
+ if (ret < 0)
+ goto cpu_dai_err;
+ }
+ }
+
+ if (codec_dev->probe) {
+ ret = codec_dev->probe(pdev);
+ if (ret < 0)
+ goto cpu_dai_err;
+ }
+
+ if (platform->probe) {
+ ret = platform->probe(pdev);
+ if (ret < 0)
+ goto platform_err;
+ }
+
+ /* DAPM stream work */
+ INIT_DELAYED_WORK(&socdev->delayed_work, close_delayed_work);
+#ifdef CONFIG_PM
+ /* deferred resume work */
+ INIT_WORK(&socdev->deferred_resume_work, soc_resume_deferred);
+#endif
+
+ return 0;
+
+platform_err:
+ if (codec_dev->remove)
+ codec_dev->remove(pdev);
+
+cpu_dai_err:
+ for (i--; i >= 0; i--) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->remove)
+ cpu_dai->remove(pdev, cpu_dai);
+ }
+
+ if (machine->remove)
+ machine->remove(pdev);
+
+ return ret;
+}
+
+/* removes a socdev */
+static int soc_remove(struct platform_device *pdev)
+{
+ int i;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+
+ run_delayed_work(&socdev->delayed_work);
+
+ if (platform->remove)
+ platform->remove(pdev);
+
+ if (codec_dev->remove)
+ codec_dev->remove(pdev);
+
+ for (i = 0; i < machine->num_links; i++) {
+ struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->remove)
+ cpu_dai->remove(pdev, cpu_dai);
+ }
+
+ if (machine->remove)
+ machine->remove(pdev);
+
+ return 0;
+}
+
+/* ASoC platform driver */
+static struct platform_driver soc_driver = {
+ .driver = {
+ .name = "soc-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = soc_probe,
+ .remove = soc_remove,
+ .suspend = soc_suspend,
+ .resume = soc_resume,
+};
+
+/* create a new pcm */
+static int soc_new_pcm(struct snd_soc_device *socdev,
+ struct snd_soc_dai_link *dai_link, int num)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_dai *codec_dai = dai_link->codec_dai;
+ struct snd_soc_dai *cpu_dai = dai_link->cpu_dai;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_pcm *pcm;
+ char new_name[64];
+ int ret = 0, playback = 0, capture = 0;
+
+ rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL);
+ if (rtd == NULL)
+ return -ENOMEM;
+
+ rtd->dai = dai_link;
+ rtd->socdev = socdev;
+ codec_dai->codec = socdev->codec;
+
+ /* check client and interface hw capabilities */
+ sprintf(new_name, "%s %s-%s-%d", dai_link->stream_name, codec_dai->name,
+ get_dai_name(cpu_dai->type), num);
+
+ if (codec_dai->playback.channels_min)
+ playback = 1;
+ if (codec_dai->capture.channels_min)
+ capture = 1;
+
+ ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,
+ capture, &pcm);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't create pcm for codec %s\n",
+ codec->name);
+ kfree(rtd);
+ return ret;
+ }
+
+ dai_link->pcm = pcm;
+ pcm->private_data = rtd;
+ soc_pcm_ops.mmap = socdev->platform->pcm_ops->mmap;
+ soc_pcm_ops.pointer = socdev->platform->pcm_ops->pointer;
+ soc_pcm_ops.ioctl = socdev->platform->pcm_ops->ioctl;
+ soc_pcm_ops.copy = socdev->platform->pcm_ops->copy;
+ soc_pcm_ops.silence = socdev->platform->pcm_ops->silence;
+ soc_pcm_ops.ack = socdev->platform->pcm_ops->ack;
+ soc_pcm_ops.page = socdev->platform->pcm_ops->page;
+
+ if (playback)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
+
+ if (capture)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
+
+ ret = socdev->platform->pcm_new(codec->card, codec_dai, pcm);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: platform pcm constructor failed\n");
+ kfree(rtd);
+ return ret;
+ }
+
+ pcm->private_free = socdev->platform->pcm_free;
+ printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
+ cpu_dai->name);
+ return ret;
+}
+
+/* codec register dump */
+static ssize_t codec_reg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_device *devdata = dev_get_drvdata(dev);
+ struct snd_soc_codec *codec = devdata->codec;
+ int i, step = 1, count = 0;
+
+ if (!codec->reg_cache_size)
+ return 0;
+
+ if (codec->reg_cache_step)
+ step = codec->reg_cache_step;
+
+ count += sprintf(buf, "%s registers\n", codec->name);
+ for (i = 0; i < codec->reg_cache_size; i += step) {
+ count += sprintf(buf + count, "%2x: ", i);
+ if (count >= PAGE_SIZE - 1)
+ break;
+
+ if (codec->display_register)
+ count += codec->display_register(codec, buf + count,
+ PAGE_SIZE - count, i);
+ else
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%4x", codec->read(codec, i));
+
+ if (count >= PAGE_SIZE - 1)
+ break;
+
+ count += snprintf(buf + count, PAGE_SIZE - count, "\n");
+ if (count >= PAGE_SIZE - 1)
+ break;
+ }
+
+ /* Truncate count; min() would cause a warning */
+ if (count >= PAGE_SIZE)
+ count = PAGE_SIZE - 1;
+
+ return count;
+}
+static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
+
+/**
+ * snd_soc_new_ac97_codec - initailise AC97 device
+ * @codec: audio codec
+ * @ops: AC97 bus operations
+ * @num: AC97 codec number
+ *
+ * Initialises AC97 codec resources for use by ad-hoc devices only.
+ */
+int snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
+ struct snd_ac97_bus_ops *ops, int num)
+{
+ mutex_lock(&codec->mutex);
+
+ codec->ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL);
+ if (codec->ac97 == NULL) {
+ mutex_unlock(&codec->mutex);
+ return -ENOMEM;
+ }
+
+ codec->ac97->bus = kzalloc(sizeof(struct snd_ac97_bus), GFP_KERNEL);
+ if (codec->ac97->bus == NULL) {
+ kfree(codec->ac97);
+ codec->ac97 = NULL;
+ mutex_unlock(&codec->mutex);
+ return -ENOMEM;
+ }
+
+ codec->ac97->bus->ops = ops;
+ codec->ac97->num = num;
+ mutex_unlock(&codec->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec);
+
+/**
+ * snd_soc_free_ac97_codec - free AC97 codec device
+ * @codec: audio codec
+ *
+ * Frees AC97 codec device resources.
+ */
+void snd_soc_free_ac97_codec(struct snd_soc_codec *codec)
+{
+ mutex_lock(&codec->mutex);
+ kfree(codec->ac97->bus);
+ kfree(codec->ac97);
+ codec->ac97 = NULL;
+ mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec);
+
+/**
+ * snd_soc_update_bits - update codec register bits
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Writes new register value.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
+ unsigned short mask, unsigned short value)
+{
+ int change;
+ unsigned short old, new;
+
+ mutex_lock(&io_mutex);
+ old = snd_soc_read(codec, reg);
+ new = (old & ~mask) | value;
+ change = old != new;
+ if (change)
+ snd_soc_write(codec, reg, new);
+
+ mutex_unlock(&io_mutex);
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits);
+
+/**
+ * snd_soc_test_bits - test register for change
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Tests a register with a new value and checks if the new value is
+ * different from the old value.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
+ unsigned short mask, unsigned short value)
+{
+ int change;
+ unsigned short old, new;
+
+ mutex_lock(&io_mutex);
+ old = snd_soc_read(codec, reg);
+ new = (old & ~mask) | value;
+ change = old != new;
+ mutex_unlock(&io_mutex);
+
+ return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_test_bits);
+
+/**
+ * snd_soc_new_pcms - create new sound card and pcms
+ * @socdev: the SoC audio device
+ *
+ * Create a new sound card based upon the codec and interface pcms.
+ *
+ * Returns 0 for success, else error.
+ */
+int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_machine *machine = socdev->machine;
+ int ret = 0, i;
+
+ mutex_lock(&codec->mutex);
+
+ /* register a sound card */
+ codec->card = snd_card_new(idx, xid, codec->owner, 0);
+ if (!codec->card) {
+ printk(KERN_ERR "asoc: can't create sound card for codec %s\n",
+ codec->name);
+ mutex_unlock(&codec->mutex);
+ return -ENODEV;
+ }
+
+ codec->card->dev = socdev->dev;
+ codec->card->private_data = codec;
+ strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver));
+
+ /* create the pcms */
+ for (i = 0; i < machine->num_links; i++) {
+ ret = soc_new_pcm(socdev, &machine->dai_link[i], i);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't create pcm %s\n",
+ machine->dai_link[i].stream_name);
+ mutex_unlock(&codec->mutex);
+ return ret;
+ }
+ }
+
+ mutex_unlock(&codec->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_new_pcms);
+
+/**
+ * snd_soc_register_card - register sound card
+ * @socdev: the SoC audio device
+ *
+ * Register a SoC sound card. Also registers an AC97 device if the
+ * codec is AC97 for ad hoc devices.
+ *
+ * Returns 0 for success, else error.
+ */
+int snd_soc_register_card(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_machine *machine = socdev->machine;
+ int ret = 0, i, ac97 = 0, err = 0;
+
+ for (i = 0; i < machine->num_links; i++) {
+ if (socdev->machine->dai_link[i].init) {
+ err = socdev->machine->dai_link[i].init(codec);
+ if (err < 0) {
+ printk(KERN_ERR "asoc: failed to init %s\n",
+ socdev->machine->dai_link[i].stream_name);
+ continue;
+ }
+ }
+ if (socdev->machine->dai_link[i].codec_dai->type ==
+ SND_SOC_DAI_AC97_BUS)
+ ac97 = 1;
+ }
+ snprintf(codec->card->shortname, sizeof(codec->card->shortname),
+ "%s", machine->name);
+ snprintf(codec->card->longname, sizeof(codec->card->longname),
+ "%s (%s)", machine->name, codec->name);
+
+ ret = snd_card_register(codec->card);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to register soundcard for %s\n",
+ codec->name);
+ goto out;
+ }
+
+ mutex_lock(&codec->mutex);
+#ifdef CONFIG_SND_SOC_AC97_BUS
+ if (ac97) {
+ ret = soc_ac97_dev_register(codec);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: AC97 device register failed\n");
+ snd_card_free(codec->card);
+ mutex_unlock(&codec->mutex);
+ goto out;
+ }
+ }
+#endif
+
+ err = snd_soc_dapm_sys_add(socdev->dev);
+ if (err < 0)
+ printk(KERN_WARNING "asoc: failed to add dapm sysfs entries\n");
+
+ err = device_create_file(socdev->dev, &dev_attr_codec_reg);
+ if (err < 0)
+ printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");
+
+ mutex_unlock(&codec->mutex);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_card);
+
+/**
+ * snd_soc_free_pcms - free sound card and pcms
+ * @socdev: the SoC audio device
+ *
+ * Frees sound card and pcms associated with the socdev.
+ * Also unregister the codec if it is an AC97 device.
+ */
+void snd_soc_free_pcms(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+#ifdef CONFIG_SND_SOC_AC97_BUS
+ struct snd_soc_dai *codec_dai;
+ int i;
+#endif
+
+ mutex_lock(&codec->mutex);
+#ifdef CONFIG_SND_SOC_AC97_BUS
+ for (i = 0; i < codec->num_dai; i++) {
+ codec_dai = &codec->dai[i];
+ if (codec_dai->type == SND_SOC_DAI_AC97_BUS && codec->ac97) {
+ soc_ac97_dev_unregister(codec);
+ goto free_card;
+ }
+ }
+free_card:
+#endif
+
+ if (codec->card)
+ snd_card_free(codec->card);
+ device_remove_file(socdev->dev, &dev_attr_codec_reg);
+ mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_free_pcms);
+
+/**
+ * snd_soc_set_runtime_hwparams - set the runtime hardware parameters
+ * @substream: the pcm substream
+ * @hw: the hardware parameters
+ *
+ * Sets the substream runtime hardware parameters.
+ */
+int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
+ const struct snd_pcm_hardware *hw)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ runtime->hw.info = hw->info;
+ runtime->hw.formats = hw->formats;
+ runtime->hw.period_bytes_min = hw->period_bytes_min;
+ runtime->hw.period_bytes_max = hw->period_bytes_max;
+ runtime->hw.periods_min = hw->periods_min;
+ runtime->hw.periods_max = hw->periods_max;
+ runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
+ runtime->hw.fifo_size = hw->fifo_size;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams);
+
+/**
+ * snd_soc_cnew - create new control
+ * @_template: control template
+ * @data: control private data
+ * @lnng_name: control long name
+ *
+ * Create a new mixer control from a template control.
+ *
+ * Returns 0 for success, else error.
+ */
+struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
+ void *data, char *long_name)
+{
+ struct snd_kcontrol_new template;
+
+ memcpy(&template, _template, sizeof(template));
+ if (long_name)
+ template.name = long_name;
+ template.index = 0;
+
+ return snd_ctl_new1(&template, data);
+}
+EXPORT_SYMBOL_GPL(snd_soc_cnew);
+
+/**
+ * snd_soc_info_enum_double - enumerated double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double enumerated
+ * mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
+ uinfo->value.enumerated.items = e->max;
+
+ if (uinfo->value.enumerated.item > e->max - 1)
+ uinfo->value.enumerated.item = e->max - 1;
+ strcpy(uinfo->value.enumerated.name,
+ e->texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_double);
+
+/**
+ * snd_soc_get_enum_double - enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned short val, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(codec, e->reg);
+ ucontrol->value.enumerated.item[0]
+ = (val >> e->shift_l) & (bitmask - 1);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ (val >> e->shift_r) & (bitmask - 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_enum_double);
+
+/**
+ * snd_soc_put_enum_double - enumerated double mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned short val;
+ unsigned short mask, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ val = ucontrol->value.enumerated.item[0] << e->shift_l;
+ mask = (bitmask - 1) << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= (bitmask - 1) << e->shift_r;
+ }
+
+ return snd_soc_update_bits(codec, e->reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_enum_double);
+
+/**
+ * snd_soc_info_enum_ext - external enumerated single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about an external enumerated
+ * single mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = e->max;
+
+ if (uinfo->value.enumerated.item > e->max - 1)
+ uinfo->value.enumerated.item = e->max - 1;
+ strcpy(uinfo->value.enumerated.name,
+ e->texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext);
+
+/**
+ * snd_soc_info_volsw_ext - external single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single external mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int max = kcontrol->private_value;
+
+ if (max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext);
+
+/**
+ * snd_soc_info_volsw - single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+
+ if (max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = shift == rshift ? 1 : 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
+
+/**
+ * snd_soc_get_volsw - single mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg) >> rshift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
+
+/**
+ * snd_soc_put_volsw - single mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned short val, val2, val_mask;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+ if (invert)
+ val = max - val;
+ val_mask = mask << shift;
+ val = val << shift;
+ if (shift != rshift) {
+ val2 = (ucontrol->value.integer.value[1] & mask);
+ if (invert)
+ val2 = max - val2;
+ val_mask |= mask << rshift;
+ val |= val2 << rshift;
+ }
+ return snd_soc_update_bits(codec, reg, val_mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
+
+/**
+ * snd_soc_info_volsw_2r - double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double mixer control that
+ * spans 2 codec registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+
+ if (max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r);
+
+/**
+ * snd_soc_get_volsw_2r - double mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1<<fls(max))-1;
+ unsigned int invert = mc->invert;
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg2) >> shift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r);
+
+/**
+ * snd_soc_put_volsw_2r - double mixer set callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ int err;
+ unsigned short val, val2, val_mask;
+
+ val_mask = mask << shift;
+ val = (ucontrol->value.integer.value[0] & mask);
+ val2 = (ucontrol->value.integer.value[1] & mask);
+
+ if (invert) {
+ val = max - val;
+ val2 = max - val2;
+ }
+
+ val = val << shift;
+ val2 = val2 << shift;
+
+ err = snd_soc_update_bits(codec, reg, val_mask, val);
+ if (err < 0)
+ return err;
+
+ err = snd_soc_update_bits(codec, reg2, val_mask, val2);
+ return err;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r);
+
+/**
+ * snd_soc_info_volsw_s8 - signed mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ int min = mc->min;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max-min;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8);
+
+/**
+ * snd_soc_get_volsw_s8 - signed mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ int min = mc->min;
+ int val = snd_soc_read(codec, reg);
+
+ ucontrol->value.integer.value[0] =
+ ((signed char)(val & 0xff))-min;
+ ucontrol->value.integer.value[1] =
+ ((signed char)((val >> 8) & 0xff))-min;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8);
+
+/**
+ * snd_soc_put_volsw_sgn - signed mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ int min = mc->min;
+ unsigned short val;
+
+ val = (ucontrol->value.integer.value[0]+min) & 0xff;
+ val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8;
+
+ return snd_soc_update_bits(codec, reg, 0xffff, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8);
+
+/**
+ * snd_soc_dai_set_sysclk - configure DAI system or master clock.
+ * @dai: DAI
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ if (dai->dai_ops.set_sysclk)
+ return dai->dai_ops.set_sysclk(dai, clk_id, freq, dir);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
+
+/**
+ * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
+ * @dai: DAI
+ * @clk_id: DAI specific clock divider ID
+ * @div: new clock divisor.
+ *
+ * Configures the clock dividers. This is used to derive the best DAI bit and
+ * frame clocks from the system or master clock. It's best to set the DAI bit
+ * and frame clocks as low as possible to save system power.
+ */
+int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ if (dai->dai_ops.set_clkdiv)
+ return dai->dai_ops.set_clkdiv(dai, div_id, div);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
+
+/**
+ * snd_soc_dai_set_pll - configure DAI PLL.
+ * @dai: DAI
+ * @pll_id: DAI specific PLL ID
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_dai_set_pll(struct snd_soc_dai *dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ if (dai->dai_ops.set_pll)
+ return dai->dai_ops.set_pll(dai, pll_id, freq_in, freq_out);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
+
+/**
+ * snd_soc_dai_set_fmt - configure DAI hardware audio format.
+ * @dai: DAI
+ * @clk_id: DAI specific clock ID
+ * @fmt: SND_SOC_DAIFMT_ format value.
+ *
+ * Configures the DAI hardware format and clocking.
+ */
+int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ if (dai->dai_ops.set_fmt)
+ return dai->dai_ops.set_fmt(dai, fmt);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
+
+/**
+ * snd_soc_dai_set_tdm_slot - configure DAI TDM.
+ * @dai: DAI
+ * @mask: DAI specific mask representing used slots.
+ * @slots: Number of slots in use.
+ *
+ * Configures a DAI for TDM operation. Both mask and slots are codec and DAI
+ * specific.
+ */
+int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int mask, int slots)
+{
+ if (dai->dai_ops.set_sysclk)
+ return dai->dai_ops.set_tdm_slot(dai, mask, slots);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
+
+/**
+ * snd_soc_dai_set_tristate - configure DAI system or master clock.
+ * @dai: DAI
+ * @tristate: tristate enable
+ *
+ * Tristates the DAI so that others can use it.
+ */
+int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ if (dai->dai_ops.set_sysclk)
+ return dai->dai_ops.set_tristate(dai, tristate);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
+
+/**
+ * snd_soc_dai_digital_mute - configure DAI system or master clock.
+ * @dai: DAI
+ * @mute: mute enable
+ *
+ * Mutes the DAI DAC.
+ */
+int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ if (dai->dai_ops.digital_mute)
+ return dai->dai_ops.digital_mute(dai, mute);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
+
+static int __devinit snd_soc_init(void)
+{
+ printk(KERN_INFO "ASoC version %s\n", SND_SOC_VERSION);
+ return platform_driver_register(&soc_driver);
+}
+
+static void snd_soc_exit(void)
+{
+ platform_driver_unregister(&soc_driver);
+}
+
+module_init(snd_soc_init);
+module_exit(snd_soc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("ALSA SoC Core");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
new file mode 100644
index 0000000..7351db9
--- /dev/null
+++ b/sound/soc/soc-dapm.c
@@ -0,0 +1,1545 @@
+/*
+ * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.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.
+ *
+ * Features:
+ * o Changes power status of internal codec blocks depending on the
+ * dynamic configuration of codec internal audio paths and active
+ * DAC's/ADC's.
+ * o Platform power domain - can support external components i.e. amps and
+ * mic/meadphone insertion events.
+ * o Automatic Mic Bias support
+ * o Jack insertion power event initiation - e.g. hp insertion will enable
+ * sinks, dacs, etc
+ * o Delayed powerdown of audio susbsystem to reduce pops between a quick
+ * device reopen.
+ *
+ * Todo:
+ * o DAPM power change sequencing - allow for configurable per
+ * codec sequences.
+ * o Support for analogue bias optimisation.
+ * o Support for reduced codec oversampling rates.
+ * o Support for reduced codec bias currents.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/debugfs.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+/* debug */
+#ifdef DEBUG
+#define dump_dapm(codec, action) dbg_dump_dapm(codec, action)
+#else
+#define dump_dapm(codec, action)
+#endif
+
+/* dapm power sequences - make this per codec in the future */
+static int dapm_up_seq[] = {
+ snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic,
+ snd_soc_dapm_mux, snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_pga,
+ snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post
+};
+static int dapm_down_seq[] = {
+ snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
+ snd_soc_dapm_pga, snd_soc_dapm_mixer, snd_soc_dapm_dac, snd_soc_dapm_mic,
+ snd_soc_dapm_micbias, snd_soc_dapm_mux, snd_soc_dapm_post
+};
+
+static int dapm_status = 1;
+module_param(dapm_status, int, 0);
+MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries");
+
+static struct dentry *asoc_debugfs;
+
+static u32 pop_time;
+
+static void pop_wait(void)
+{
+ if (pop_time)
+ schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time));
+}
+
+static void pop_dbg(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+
+ if (pop_time) {
+ vprintk(fmt, args);
+ pop_wait();
+ }
+
+ va_end(args);
+}
+
+/* create a new dapm widget */
+static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
+ const struct snd_soc_dapm_widget *_widget)
+{
+ return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
+}
+
+/* set up initial codec paths */
+static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
+ struct snd_soc_dapm_path *p, int i)
+{
+ switch (w->id) {
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer: {
+ int val;
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)
+ w->kcontrols[i].private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ val = snd_soc_read(w->codec, reg);
+ val = (val >> shift) & mask;
+
+ if ((invert && !val) || (!invert && val))
+ p->connect = 1;
+ else
+ p->connect = 0;
+ }
+ break;
+ case snd_soc_dapm_mux: {
+ struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value;
+ int val, item, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(w->codec, e->reg);
+ item = (val >> e->shift_l) & (bitmask - 1);
+
+ p->connect = 0;
+ for (i = 0; i < e->max; i++) {
+ if (!(strcmp(p->name, e->texts[i])) && item == i)
+ p->connect = 1;
+ }
+ }
+ break;
+ /* does not effect routing - always connected */
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_vmid:
+ p->connect = 1;
+ break;
+ /* does effect routing - dynamically connected */
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ p->connect = 0;
+ break;
+ }
+}
+
+/* connect mux widget to it's interconnecting audio paths */
+static int dapm_connect_mux(struct snd_soc_codec *codec,
+ struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+ struct snd_soc_dapm_path *path, const char *control_name,
+ const struct snd_kcontrol_new *kcontrol)
+{
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ int i;
+
+ for (i = 0; i < e->max; i++) {
+ if (!(strcmp(control_name, e->texts[i]))) {
+ list_add(&path->list, &codec->dapm_paths);
+ list_add(&path->list_sink, &dest->sources);
+ list_add(&path->list_source, &src->sinks);
+ path->name = (char*)e->texts[i];
+ dapm_set_path_status(dest, path, 0);
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+/* connect mixer widget to it's interconnecting audio paths */
+static int dapm_connect_mixer(struct snd_soc_codec *codec,
+ struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+ struct snd_soc_dapm_path *path, const char *control_name)
+{
+ int i;
+
+ /* search for mixer kcontrol */
+ for (i = 0; i < dest->num_kcontrols; i++) {
+ if (!strcmp(control_name, dest->kcontrols[i].name)) {
+ list_add(&path->list, &codec->dapm_paths);
+ list_add(&path->list_sink, &dest->sources);
+ list_add(&path->list_source, &src->sinks);
+ path->name = dest->kcontrols[i].name;
+ dapm_set_path_status(dest, path, i);
+ return 0;
+ }
+ }
+ return -ENODEV;
+}
+
+/* update dapm codec register bits */
+static int dapm_update_bits(struct snd_soc_dapm_widget *widget)
+{
+ int change, power;
+ unsigned short old, new;
+ struct snd_soc_codec *codec = widget->codec;
+
+ /* check for valid widgets */
+ if (widget->reg < 0 || widget->id == snd_soc_dapm_input ||
+ widget->id == snd_soc_dapm_output ||
+ widget->id == snd_soc_dapm_hp ||
+ widget->id == snd_soc_dapm_mic ||
+ widget->id == snd_soc_dapm_line ||
+ widget->id == snd_soc_dapm_spk)
+ return 0;
+
+ power = widget->power;
+ if (widget->invert)
+ power = (power ? 0:1);
+
+ old = snd_soc_read(codec, widget->reg);
+ new = (old & ~(0x1 << widget->shift)) | (power << widget->shift);
+
+ change = old != new;
+ if (change) {
+ pop_dbg("pop test %s : %s in %d ms\n", widget->name,
+ widget->power ? "on" : "off", pop_time);
+ snd_soc_write(codec, widget->reg, new);
+ pop_wait();
+ }
+ pr_debug("reg %x old %x new %x change %d\n", widget->reg,
+ old, new, change);
+ return change;
+}
+
+/* ramps the volume up or down to minimise pops before or after a
+ * DAPM power event */
+static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power)
+{
+ const struct snd_kcontrol_new *k = widget->kcontrols;
+
+ if (widget->muted && !power)
+ return 0;
+ if (!widget->muted && power)
+ return 0;
+
+ if (widget->num_kcontrols && k) {
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)k->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+
+ if (power) {
+ int i;
+ /* power up has happended, increase volume to last level */
+ if (invert) {
+ for (i = max; i > widget->saved_value; i--)
+ snd_soc_update_bits(widget->codec, reg, mask, i);
+ } else {
+ for (i = 0; i < widget->saved_value; i++)
+ snd_soc_update_bits(widget->codec, reg, mask, i);
+ }
+ widget->muted = 0;
+ } else {
+ /* power down is about to occur, decrease volume to mute */
+ int val = snd_soc_read(widget->codec, reg);
+ int i = widget->saved_value = (val >> shift) & mask;
+ if (invert) {
+ for (; i < mask; i++)
+ snd_soc_update_bits(widget->codec, reg, mask, i);
+ } else {
+ for (; i > 0; i--)
+ snd_soc_update_bits(widget->codec, reg, mask, i);
+ }
+ widget->muted = 1;
+ }
+ }
+ return 0;
+}
+
+/* create new dapm mixer control */
+static int dapm_new_mixer(struct snd_soc_codec *codec,
+ struct snd_soc_dapm_widget *w)
+{
+ int i, ret = 0;
+ char name[32];
+ struct snd_soc_dapm_path *path;
+
+ /* add kcontrol */
+ for (i = 0; i < w->num_kcontrols; i++) {
+
+ /* match name */
+ list_for_each_entry(path, &w->sources, list_sink) {
+
+ /* mixer/mux paths name must match control name */
+ if (path->name != (char*)w->kcontrols[i].name)
+ continue;
+
+ /* add dapm control with long name */
+ snprintf(name, 32, "%s %s", w->name, w->kcontrols[i].name);
+ path->long_name = kstrdup (name, GFP_KERNEL);
+ if (path->long_name == NULL)
+ return -ENOMEM;
+
+ path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w,
+ path->long_name);
+ ret = snd_ctl_add(codec->card, path->kcontrol);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n",
+ path->long_name);
+ kfree(path->long_name);
+ path->long_name = NULL;
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+/* create new dapm mux control */
+static int dapm_new_mux(struct snd_soc_codec *codec,
+ struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *path = NULL;
+ struct snd_kcontrol *kcontrol;
+ int ret = 0;
+
+ if (!w->num_kcontrols) {
+ printk(KERN_ERR "asoc: mux %s has no controls\n", w->name);
+ return -EINVAL;
+ }
+
+ kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
+ ret = snd_ctl_add(codec->card, kcontrol);
+ if (ret < 0)
+ goto err;
+
+ list_for_each_entry(path, &w->sources, list_sink)
+ path->kcontrol = kcontrol;
+
+ return ret;
+
+err:
+ printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);
+ return ret;
+}
+
+/* create new dapm volume control */
+static int dapm_new_pga(struct snd_soc_codec *codec,
+ struct snd_soc_dapm_widget *w)
+{
+ struct snd_kcontrol *kcontrol;
+ int ret = 0;
+
+ if (!w->num_kcontrols)
+ return -EINVAL;
+
+ kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
+ ret = snd_ctl_add(codec->card, kcontrol);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);
+ return ret;
+ }
+
+ return ret;
+}
+
+/* reset 'walked' bit for each dapm path */
+static inline void dapm_clear_walk(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_path *p;
+
+ list_for_each_entry(p, &codec->dapm_paths, list)
+ p->walked = 0;
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * output widget. Returns number of complete paths.
+ */
+static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_path *path;
+ int con = 0;
+
+ if (widget->id == snd_soc_dapm_adc && widget->active)
+ return 1;
+
+ if (widget->connected) {
+ /* connected pin ? */
+ if (widget->id == snd_soc_dapm_output && !widget->ext)
+ return 1;
+
+ /* connected jack or spk ? */
+ if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
+ widget->id == snd_soc_dapm_line)
+ return 1;
+ }
+
+ list_for_each_entry(path, &widget->sinks, list_source) {
+ if (path->walked)
+ continue;
+
+ if (path->sink && path->connect) {
+ path->walked = 1;
+ con += is_connected_output_ep(path->sink);
+ }
+ }
+
+ return con;
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * input widget. Returns number of complete paths.
+ */
+static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_path *path;
+ int con = 0;
+
+ /* active stream ? */
+ if (widget->id == snd_soc_dapm_dac && widget->active)
+ return 1;
+
+ if (widget->connected) {
+ /* connected pin ? */
+ if (widget->id == snd_soc_dapm_input && !widget->ext)
+ return 1;
+
+ /* connected VMID/Bias for lower pops */
+ if (widget->id == snd_soc_dapm_vmid)
+ return 1;
+
+ /* connected jack ? */
+ if (widget->id == snd_soc_dapm_mic || widget->id == snd_soc_dapm_line)
+ return 1;
+ }
+
+ list_for_each_entry(path, &widget->sources, list_sink) {
+ if (path->walked)
+ continue;
+
+ if (path->source && path->connect) {
+ path->walked = 1;
+ con += is_connected_input_ep(path->source);
+ }
+ }
+
+ return con;
+}
+
+/*
+ * Handler for generic register modifier widget.
+ */
+int dapm_reg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ unsigned int val;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ val = w->on_val;
+ else
+ val = w->off_val;
+
+ snd_soc_update_bits(w->codec, -(w->reg + 1),
+ w->mask << w->shift, val << w->shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dapm_reg_event);
+
+/*
+ * Scan each dapm widget for complete audio path.
+ * A complete path is a route that has valid endpoints i.e.:-
+ *
+ * o DAC to output pin.
+ * o Input Pin to ADC.
+ * o Input pin to Output pin (bypass, sidetone)
+ * o DAC to ADC (loopback).
+ */
+static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
+{
+ struct snd_soc_dapm_widget *w;
+ int in, out, i, c = 1, *seq = NULL, ret = 0, power_change, power;
+
+ /* do we have a sequenced stream event */
+ if (event == SND_SOC_DAPM_STREAM_START) {
+ c = ARRAY_SIZE(dapm_up_seq);
+ seq = dapm_up_seq;
+ } else if (event == SND_SOC_DAPM_STREAM_STOP) {
+ c = ARRAY_SIZE(dapm_down_seq);
+ seq = dapm_down_seq;
+ }
+
+ for(i = 0; i < c; i++) {
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+ /* is widget in stream order */
+ if (seq && seq[i] && w->id != seq[i])
+ continue;
+
+ /* vmid - no action */
+ if (w->id == snd_soc_dapm_vmid)
+ continue;
+
+ /* active ADC */
+ if (w->id == snd_soc_dapm_adc && w->active) {
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->codec);
+ w->power = (in != 0) ? 1 : 0;
+ dapm_update_bits(w);
+ continue;
+ }
+
+ /* active DAC */
+ if (w->id == snd_soc_dapm_dac && w->active) {
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->codec);
+ w->power = (out != 0) ? 1 : 0;
+ dapm_update_bits(w);
+ continue;
+ }
+
+ /* pre and post event widgets */
+ if (w->id == snd_soc_dapm_pre) {
+ if (!w->event)
+ continue;
+
+ if (event == SND_SOC_DAPM_STREAM_START) {
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_PRE_PMU);
+ if (ret < 0)
+ return ret;
+ } else if (event == SND_SOC_DAPM_STREAM_STOP) {
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_PRE_PMD);
+ if (ret < 0)
+ return ret;
+ }
+ continue;
+ }
+ if (w->id == snd_soc_dapm_post) {
+ if (!w->event)
+ continue;
+
+ if (event == SND_SOC_DAPM_STREAM_START) {
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_POST_PMU);
+ if (ret < 0)
+ return ret;
+ } else if (event == SND_SOC_DAPM_STREAM_STOP) {
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_POST_PMD);
+ if (ret < 0)
+ return ret;
+ }
+ continue;
+ }
+
+ /* all other widgets */
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->codec);
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->codec);
+ power = (out != 0 && in != 0) ? 1 : 0;
+ power_change = (w->power == power) ? 0: 1;
+ w->power = power;
+
+ if (!power_change)
+ continue;
+
+ /* call any power change event handlers */
+ if (w->event)
+ pr_debug("power %s event for %s flags %x\n",
+ w->power ? "on" : "off",
+ w->name, w->event_flags);
+
+ /* power up pre event */
+ if (power && w->event &&
+ (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
+ ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* power down pre event */
+ if (!power && w->event &&
+ (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
+ ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Lower PGA volume to reduce pops */
+ if (w->id == snd_soc_dapm_pga && !power)
+ dapm_set_pga(w, power);
+
+ dapm_update_bits(w);
+
+ /* Raise PGA volume to reduce pops */
+ if (w->id == snd_soc_dapm_pga && power)
+ dapm_set_pga(w, power);
+
+ /* power up post event */
+ if (power && w->event &&
+ (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
+ ret = w->event(w,
+ NULL, SND_SOC_DAPM_POST_PMU);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* power down post event */
+ if (!power && w->event &&
+ (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
+ ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+#ifdef DEBUG
+static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action)
+{
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_path *p = NULL;
+ int in, out;
+
+ printk("DAPM %s %s\n", codec->name, action);
+
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+ /* only display widgets that effect routing */
+ switch (w->id) {
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ case snd_soc_dapm_vmid:
+ continue;
+ case snd_soc_dapm_mux:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_mixer:
+ if (w->name) {
+ in = is_connected_input_ep(w);
+ dapm_clear_walk(w->codec);
+ out = is_connected_output_ep(w);
+ dapm_clear_walk(w->codec);
+ printk("%s: %s in %d out %d\n", w->name,
+ w->power ? "On":"Off",in, out);
+
+ list_for_each_entry(p, &w->sources, list_sink) {
+ if (p->connect)
+ printk(" in %s %s\n", p->name ? p->name : "static",
+ p->source->name);
+ }
+ list_for_each_entry(p, &w->sinks, list_source) {
+ if (p->connect)
+ printk(" out %s %s\n", p->name ? p->name : "static",
+ p->sink->name);
+ }
+ }
+ break;
+ }
+ }
+}
+#endif
+
+/* test and update the power status of a mux widget */
+static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kcontrol, int mask,
+ int mux, int val, struct soc_enum *e)
+{
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ if (widget->id != snd_soc_dapm_mux)
+ return -ENODEV;
+
+ if (!snd_soc_test_bits(widget->codec, e->reg, mask, val))
+ return 0;
+
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ if (!path->name || !e->texts[mux])
+ continue;
+
+ found = 1;
+ /* we now need to match the string in the enum to the path */
+ if (!(strcmp(path->name, e->texts[mux])))
+ path->connect = 1; /* new connection */
+ else
+ path->connect = 0; /* old connection must be powered down */
+ }
+
+ if (found) {
+ dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
+ dump_dapm(widget->codec, "mux power update");
+ }
+
+ return 0;
+}
+
+/* test and update the power status of a mixer or switch widget */
+static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kcontrol, int reg,
+ int val_mask, int val, int invert)
+{
+ struct snd_soc_dapm_path *path;
+ int found = 0;
+
+ if (widget->id != snd_soc_dapm_mixer &&
+ widget->id != snd_soc_dapm_switch)
+ return -ENODEV;
+
+ if (!snd_soc_test_bits(widget->codec, reg, val_mask, val))
+ return 0;
+
+ /* find dapm widget path assoc with kcontrol */
+ list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+ if (path->kcontrol != kcontrol)
+ continue;
+
+ /* found, now check type */
+ found = 1;
+ if (val)
+ /* new connection */
+ path->connect = invert ? 0:1;
+ else
+ /* old connection must be powered down */
+ path->connect = invert ? 1:0;
+ break;
+ }
+
+ if (found) {
+ dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
+ dump_dapm(widget->codec, "mixer power update");
+ }
+
+ return 0;
+}
+
+/* show dapm widget status in sys fs */
+static ssize_t dapm_widget_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_soc_device *devdata = dev_get_drvdata(dev);
+ struct snd_soc_codec *codec = devdata->codec;
+ struct snd_soc_dapm_widget *w;
+ int count = 0;
+ char *state = "not set";
+
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+ /* only display widgets that burnm power */
+ switch (w->id) {
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_mixer:
+ if (w->name)
+ count += sprintf(buf + count, "%s: %s\n",
+ w->name, w->power ? "On":"Off");
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (codec->bias_level) {
+ case SND_SOC_BIAS_ON:
+ state = "On";
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ state = "Prepare";
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ state = "Standby";
+ break;
+ case SND_SOC_BIAS_OFF:
+ state = "Off";
+ break;
+ }
+ count += sprintf(buf + count, "PM State: %s\n", state);
+
+ return count;
+}
+
+static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
+
+int snd_soc_dapm_sys_add(struct device *dev)
+{
+ int ret = 0;
+
+ if (!dapm_status)
+ return 0;
+
+ ret = device_create_file(dev, &dev_attr_dapm_widget);
+ if (ret != 0)
+ return ret;
+
+ asoc_debugfs = debugfs_create_dir("asoc", NULL);
+ if (!IS_ERR(asoc_debugfs) && asoc_debugfs)
+ debugfs_create_u32("dapm_pop_time", 0744, asoc_debugfs,
+ &pop_time);
+ else
+ asoc_debugfs = NULL;
+
+ return 0;
+}
+
+static void snd_soc_dapm_sys_remove(struct device *dev)
+{
+ if (dapm_status) {
+ device_remove_file(dev, &dev_attr_dapm_widget);
+ }
+
+ if (asoc_debugfs)
+ debugfs_remove_recursive(asoc_debugfs);
+}
+
+/* free all dapm widgets and resources */
+static void dapm_free_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_widget *w, *next_w;
+ struct snd_soc_dapm_path *p, *next_p;
+
+ list_for_each_entry_safe(w, next_w, &codec->dapm_widgets, list) {
+ list_del(&w->list);
+ kfree(w);
+ }
+
+ list_for_each_entry_safe(p, next_p, &codec->dapm_paths, list) {
+ list_del(&p->list);
+ kfree(p->long_name);
+ kfree(p);
+ }
+}
+
+static int snd_soc_dapm_set_pin(struct snd_soc_codec *codec,
+ char *pin, int status)
+{
+ struct snd_soc_dapm_widget *w;
+
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+ if (!strcmp(w->name, pin)) {
+ pr_debug("dapm: %s: pin %s\n", codec->name, pin);
+ w->connected = status;
+ return 0;
+ }
+ }
+
+ pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin);
+ return -EINVAL;
+}
+
+/**
+ * snd_soc_dapm_sync - scan and power dapm paths
+ * @codec: audio codec
+ *
+ * Walks all dapm audio paths and powers widgets according to their
+ * stream or path usage.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_sync(struct snd_soc_codec *codec)
+{
+ int ret = dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
+ dump_dapm(codec, "sync");
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
+
+static int snd_soc_dapm_add_route(struct snd_soc_codec *codec,
+ const char *sink, const char *control, const char *source)
+{
+ struct snd_soc_dapm_path *path;
+ struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
+ int ret = 0;
+
+ /* find src and dest widgets */
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+ if (!wsink && !(strcmp(w->name, sink))) {
+ wsink = w;
+ continue;
+ }
+ if (!wsource && !(strcmp(w->name, source))) {
+ wsource = w;
+ }
+ }
+
+ if (wsource == NULL || wsink == NULL)
+ return -ENODEV;
+
+ path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ path->source = wsource;
+ path->sink = wsink;
+ INIT_LIST_HEAD(&path->list);
+ INIT_LIST_HEAD(&path->list_source);
+ INIT_LIST_HEAD(&path->list_sink);
+
+ /* check for external widgets */
+ 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_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_input)
+ wsource->ext = 1;
+ }
+
+ /* connect static paths */
+ if (control == NULL) {
+ list_add(&path->list, &codec->dapm_paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 1;
+ return 0;
+ }
+
+ /* connect dynamic paths */
+ switch(wsink->id) {
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_vmid:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ list_add(&path->list, &codec->dapm_paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 1;
+ return 0;
+ case snd_soc_dapm_mux:
+ ret = dapm_connect_mux(codec, wsource, wsink, path, control,
+ &wsink->kcontrols[0]);
+ if (ret != 0)
+ goto err;
+ break;
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer:
+ ret = dapm_connect_mixer(codec, wsource, wsink, path, control);
+ if (ret != 0)
+ goto err;
+ break;
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_spk:
+ list_add(&path->list, &codec->dapm_paths);
+ list_add(&path->list_sink, &wsink->sources);
+ list_add(&path->list_source, &wsource->sinks);
+ path->connect = 0;
+ return 0;
+ }
+ return 0;
+
+err:
+ printk(KERN_WARNING "asoc: no dapm match for %s --> %s --> %s\n", source,
+ control, sink);
+ kfree(path);
+ return ret;
+}
+
+/**
+ * snd_soc_dapm_connect_input - connect dapm widgets
+ * @codec: audio codec
+ * @sink: name of target widget
+ * @control: mixer control name
+ * @source: name of source name
+ *
+ * Connects 2 dapm widgets together via a named audio path. The sink is
+ * the widget receiving the audio signal, whilst the source is the sender
+ * of the audio signal.
+ *
+ * This function has been deprecated in favour of snd_soc_dapm_add_routes().
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_connect_input(struct snd_soc_codec *codec, const char *sink,
+ const char *control, const char *source)
+{
+ return snd_soc_dapm_add_route(codec, sink, control, source);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_connect_input);
+
+/**
+ * snd_soc_dapm_add_routes - Add routes between DAPM widgets
+ * @codec: codec
+ * @route: audio routes
+ * @num: number of routes
+ *
+ * Connects 2 dapm widgets together via a named audio path. The sink is
+ * the widget receiving the audio signal, whilst the source is the sender
+ * of the audio signal.
+ *
+ * Returns 0 for success else error. On error all resources can be freed
+ * with a call to snd_soc_card_free().
+ */
+int snd_soc_dapm_add_routes(struct snd_soc_codec *codec,
+ const struct snd_soc_dapm_route *route, int num)
+{
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ ret = snd_soc_dapm_add_route(codec, route->sink,
+ route->control, route->source);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to add route %s->%s\n",
+ route->source,
+ route->sink);
+ return ret;
+ }
+ route++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
+
+/**
+ * snd_soc_dapm_new_widgets - add new dapm widgets
+ * @codec: audio codec
+ *
+ * Checks the codec for any new dapm widgets and creates them if found.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_widget *w;
+
+ list_for_each_entry(w, &codec->dapm_widgets, list)
+ {
+ if (w->new)
+ continue;
+
+ switch(w->id) {
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mixer:
+ dapm_new_mixer(codec, w);
+ break;
+ case snd_soc_dapm_mux:
+ dapm_new_mux(codec, w);
+ break;
+ case snd_soc_dapm_adc:
+ case snd_soc_dapm_dac:
+ case snd_soc_dapm_pga:
+ dapm_new_pga(codec, w);
+ break;
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_micbias:
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_line:
+ case snd_soc_dapm_vmid:
+ case snd_soc_dapm_pre:
+ case snd_soc_dapm_post:
+ break;
+ }
+ w->new = 1;
+ }
+
+ dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
+
+/**
+ * snd_soc_dapm_get_volsw - dapm mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int invert = mc->invert;
+ unsigned int mask = (1 << fls(max)) - 1;
+
+ /* return the saved value if we are powered down */
+ if (widget->id == snd_soc_dapm_pga && !widget->power) {
+ ucontrol->value.integer.value[0] = widget->saved_value;
+ return 0;
+ }
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(widget->codec, reg) >> shift) & mask;
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(widget->codec, reg) >> rshift) & mask;
+ if (invert) {
+ ucontrol->value.integer.value[0] =
+ max - ucontrol->value.integer.value[0];
+ if (shift != rshift)
+ ucontrol->value.integer.value[1] =
+ max - ucontrol->value.integer.value[1];
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw);
+
+/**
+ * snd_soc_dapm_put_volsw - dapm mixer set callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ unsigned int rshift = mc->rshift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ unsigned int invert = mc->invert;
+ unsigned short val, val2, val_mask;
+ int ret;
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ if (invert)
+ val = max - val;
+ val_mask = mask << shift;
+ val = val << shift;
+ if (shift != rshift) {
+ val2 = (ucontrol->value.integer.value[1] & mask);
+ if (invert)
+ val2 = max - val2;
+ val_mask |= mask << rshift;
+ val |= val2 << rshift;
+ }
+
+ mutex_lock(&widget->codec->mutex);
+ widget->value = val;
+
+ /* save volume value if the widget is powered down */
+ if (widget->id == snd_soc_dapm_pga && !widget->power) {
+ widget->saved_value = val;
+ mutex_unlock(&widget->codec->mutex);
+ return 1;
+ }
+
+ dapm_mixer_update_power(widget, kcontrol, reg, val_mask, val, invert);
+ if (widget->event) {
+ if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
+ ret = widget->event(widget, kcontrol,
+ SND_SOC_DAPM_PRE_REG);
+ if (ret < 0) {
+ ret = 1;
+ goto out;
+ }
+ }
+ ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+ if (widget->event_flags & SND_SOC_DAPM_POST_REG)
+ ret = widget->event(widget, kcontrol,
+ SND_SOC_DAPM_POST_REG);
+ } else
+ ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+out:
+ mutex_unlock(&widget->codec->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
+
+/**
+ * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to get the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned short val, bitmask;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ val = snd_soc_read(widget->codec, e->reg);
+ ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ (val >> e->shift_r) & (bitmask - 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double);
+
+/**
+ * snd_soc_dapm_put_enum_double - dapm enumerated double mixer set callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to set the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned short val, mux;
+ unsigned short mask, bitmask;
+ int ret = 0;
+
+ for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+ ;
+ if (ucontrol->value.enumerated.item[0] > e->max - 1)
+ return -EINVAL;
+ mux = ucontrol->value.enumerated.item[0];
+ val = mux << e->shift_l;
+ mask = (bitmask - 1) << e->shift_l;
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->max - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= (bitmask - 1) << e->shift_r;
+ }
+
+ mutex_lock(&widget->codec->mutex);
+ widget->value = val;
+ dapm_mux_update_power(widget, kcontrol, mask, mux, val, e);
+ if (widget->event) {
+ if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
+ ret = widget->event(widget,
+ kcontrol, SND_SOC_DAPM_PRE_REG);
+ if (ret < 0)
+ goto out;
+ }
+ ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
+ if (widget->event_flags & SND_SOC_DAPM_POST_REG)
+ ret = widget->event(widget,
+ kcontrol, SND_SOC_DAPM_POST_REG);
+ } else
+ ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
+
+out:
+ mutex_unlock(&widget->codec->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
+
+/**
+ * snd_soc_dapm_new_control - create new dapm control
+ * @codec: audio codec
+ * @widget: widget template
+ *
+ * Creates a new dapm control based upon the template.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_control(struct snd_soc_codec *codec,
+ const struct snd_soc_dapm_widget *widget)
+{
+ struct snd_soc_dapm_widget *w;
+
+ if ((w = dapm_cnew_widget(widget)) == NULL)
+ return -ENOMEM;
+
+ w->codec = codec;
+ INIT_LIST_HEAD(&w->sources);
+ INIT_LIST_HEAD(&w->sinks);
+ INIT_LIST_HEAD(&w->list);
+ list_add(&w->list, &codec->dapm_widgets);
+
+ /* machine layer set ups unconnected pins and insertions */
+ w->connected = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
+
+/**
+ * snd_soc_dapm_new_controls - create new dapm controls
+ * @codec: audio codec
+ * @widget: widget array
+ * @num: number of widgets
+ *
+ * Creates new DAPM controls based upon the templates.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_controls(struct snd_soc_codec *codec,
+ const struct snd_soc_dapm_widget *widget,
+ int num)
+{
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ ret = snd_soc_dapm_new_control(codec, widget);
+ if (ret < 0)
+ return ret;
+ widget++;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
+
+
+/**
+ * snd_soc_dapm_stream_event - send a stream event to the dapm core
+ * @codec: audio codec
+ * @stream: stream name
+ * @event: stream event
+ *
+ * Sends a stream event to the dapm core. The core then makes any
+ * necessary widget power changes.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_stream_event(struct snd_soc_codec *codec,
+ char *stream, int event)
+{
+ struct snd_soc_dapm_widget *w;
+
+ if (stream == NULL)
+ return 0;
+
+ mutex_lock(&codec->mutex);
+ list_for_each_entry(w, &codec->dapm_widgets, list)
+ {
+ if (!w->sname)
+ continue;
+ pr_debug("widget %s\n %s stream %s event %d\n",
+ w->name, w->sname, stream, event);
+ if (strstr(w->sname, stream)) {
+ switch(event) {
+ case SND_SOC_DAPM_STREAM_START:
+ w->active = 1;
+ break;
+ case SND_SOC_DAPM_STREAM_STOP:
+ w->active = 0;
+ break;
+ case SND_SOC_DAPM_STREAM_SUSPEND:
+ if (w->active)
+ w->suspend = 1;
+ w->active = 0;
+ break;
+ case SND_SOC_DAPM_STREAM_RESUME:
+ if (w->suspend) {
+ w->active = 1;
+ w->suspend = 0;
+ }
+ break;
+ case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
+ break;
+ case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
+ break;
+ }
+ }
+ }
+ mutex_unlock(&codec->mutex);
+
+ dapm_power_widgets(codec, event);
+ dump_dapm(codec, __func__);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
+
+/**
+ * snd_soc_dapm_set_bias_level - set the bias level for the system
+ * @socdev: audio device
+ * @level: level to configure
+ *
+ * Configure the bias (power) levels for the SoC audio device.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_machine *machine = socdev->machine;
+ int ret = 0;
+
+ if (machine->set_bias_level)
+ ret = machine->set_bias_level(machine, level);
+ if (ret == 0 && codec->set_bias_level)
+ ret = codec->set_bias_level(codec, level);
+
+ return ret;
+}
+
+/**
+ * snd_soc_dapm_enable_pin - enable pin.
+ * @snd_soc_codec: SoC codec
+ * @pin: pin name
+ *
+ * Enables input/output pin and it's parents or children widgets iff there is
+ * a valid audio route and active audio stream.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, char *pin)
+{
+ return snd_soc_dapm_set_pin(codec, pin, 1);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin);
+
+/**
+ * snd_soc_dapm_disable_pin - disable pin.
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Disables input/output pin and it's parents or children widgets.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, char *pin)
+{
+ return snd_soc_dapm_set_pin(codec, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin);
+
+/**
+ * snd_soc_dapm_nc_pin - permanently disable pin.
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Marks the specified pin as being not connected, disabling it along
+ * any parent or child widgets. At present this is identical to
+ * snd_soc_dapm_disable_pin() but in future it will be extended to do
+ * additional things such as disabling controls which only affect
+ * paths through the pin.
+ *
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, char *pin)
+{
+ return snd_soc_dapm_set_pin(codec, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin);
+
+/**
+ * snd_soc_dapm_get_pin_status - get audio pin status
+ * @codec: audio codec
+ * @pin: audio signal pin endpoint (or start point)
+ *
+ * Get audio pin status - connected or disconnected.
+ *
+ * Returns 1 for connected otherwise 0.
+ */
+int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, char *pin)
+{
+ struct snd_soc_dapm_widget *w;
+
+ list_for_each_entry(w, &codec->dapm_widgets, list) {
+ if (!strcmp(w->name, pin))
+ return w->connected;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status);
+
+/**
+ * snd_soc_dapm_free - free dapm resources
+ * @socdev: SoC device
+ *
+ * Free all dapm widgets and resources.
+ */
+void snd_soc_dapm_free(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->codec;
+
+ snd_soc_dapm_sys_remove(socdev->dev);
+ dapm_free_widgets(codec);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_free);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
+MODULE_LICENSE("GPL");
OpenPOWER on IntegriCloud