Merge branch 'for-2.6.33' into for-2.6.34
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile
index 996cbac..6cee38d 100644
--- a/arch/arm/plat-mxc/Makefile
+++ b/arch/arm/plat-mxc/Makefile
@@ -13,3 +13,7 @@
obj-$(CONFIG_MXC_ULPI) += ulpi.o
obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o
obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.o
+ifdef CONFIG_SND_IMX_SOC
+obj-y += ssi-fiq.o
+obj-y += ssi-fiq-ksym.o
+endif
diff --git a/arch/arm/plat-mxc/ssi-fiq-ksym.c b/arch/arm/plat-mxc/ssi-fiq-ksym.c
new file mode 100644
index 0000000..b5fad45
--- /dev/null
+++ b/arch/arm/plat-mxc/ssi-fiq-ksym.c
@@ -0,0 +1,20 @@
+/*
+ * Exported ksyms for the SSI FIQ handler
+ *
+ * Copyright (C) 2009, Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * 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 <mach/ssi.h>
+
+EXPORT_SYMBOL(imx_ssi_fiq_tx_buffer);
+EXPORT_SYMBOL(imx_ssi_fiq_rx_buffer);
+EXPORT_SYMBOL(imx_ssi_fiq_start);
+EXPORT_SYMBOL(imx_ssi_fiq_end);
+EXPORT_SYMBOL(imx_ssi_fiq_base);
+
diff --git a/arch/arm/plat-mxc/ssi-fiq.S b/arch/arm/plat-mxc/ssi-fiq.S
new file mode 100644
index 0000000..4ddce56
--- /dev/null
+++ b/arch/arm/plat-mxc/ssi-fiq.S
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * 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/linkage.h>
+#include <asm/assembler.h>
+
+/*
+ * r8 = bit 0-15: tx offset, bit 16-31: tx buffer size
+ * r9 = bit 0-15: rx offset, bit 16-31: rx buffer size
+ */
+
+#define SSI_STX0 0x00
+#define SSI_SRX0 0x08
+#define SSI_SISR 0x14
+#define SSI_SIER 0x18
+#define SSI_SACNT 0x38
+
+#define SSI_SACNT_AC97EN (1 << 0)
+
+#define SSI_SIER_TFE0_EN (1 << 0)
+#define SSI_SISR_TFE0 (1 << 0)
+#define SSI_SISR_RFF0 (1 << 2)
+#define SSI_SIER_RFF0_EN (1 << 2)
+
+ .text
+ .global imx_ssi_fiq_start
+ .global imx_ssi_fiq_end
+ .global imx_ssi_fiq_base
+ .global imx_ssi_fiq_rx_buffer
+ .global imx_ssi_fiq_tx_buffer
+
+imx_ssi_fiq_start:
+ ldr r12, imx_ssi_fiq_base
+
+ /* TX */
+ ldr r11, imx_ssi_fiq_tx_buffer
+
+ /* shall we send? */
+ ldr r13, [r12, #SSI_SIER]
+ tst r13, #SSI_SIER_TFE0_EN
+ beq 1f
+
+ /* TX FIFO empty? */
+ ldr r13, [r12, #SSI_SISR]
+ tst r13, #SSI_SISR_TFE0
+ beq 1f
+
+ mov r10, #0x10000
+ sub r10, #1
+ and r10, r10, r8 /* r10: current buffer offset */
+
+ add r11, r11, r10
+
+ ldrh r13, [r11]
+ strh r13, [r12, #SSI_STX0]
+
+ ldrh r13, [r11, #2]
+ strh r13, [r12, #SSI_STX0]
+
+ ldrh r13, [r11, #4]
+ strh r13, [r12, #SSI_STX0]
+
+ ldrh r13, [r11, #6]
+ strh r13, [r12, #SSI_STX0]
+
+ add r10, #8
+ lsr r13, r8, #16 /* r13: buffer size */
+ cmp r10, r13
+ lslgt r8, r13, #16
+ addle r8, #8
+1:
+ /* RX */
+
+ /* shall we receive? */
+ ldr r13, [r12, #SSI_SIER]
+ tst r13, #SSI_SIER_RFF0_EN
+ beq 1f
+
+ /* RX FIFO full? */
+ ldr r13, [r12, #SSI_SISR]
+ tst r13, #SSI_SISR_RFF0
+ beq 1f
+
+ ldr r11, imx_ssi_fiq_rx_buffer
+
+ mov r10, #0x10000
+ sub r10, #1
+ and r10, r10, r9 /* r10: current buffer offset */
+
+ add r11, r11, r10
+
+ ldr r13, [r12, #SSI_SACNT]
+ tst r13, #SSI_SACNT_AC97EN
+
+ ldr r13, [r12, #SSI_SRX0]
+ strh r13, [r11]
+
+ ldr r13, [r12, #SSI_SRX0]
+ strh r13, [r11, #2]
+
+ /* dummy read to skip slot 12 */
+ ldrne r13, [r12, #SSI_SRX0]
+
+ ldr r13, [r12, #SSI_SRX0]
+ strh r13, [r11, #4]
+
+ ldr r13, [r12, #SSI_SRX0]
+ strh r13, [r11, #6]
+
+ /* dummy read to skip slot 12 */
+ ldrne r13, [r12, #SSI_SRX0]
+
+ add r10, #8
+ lsr r13, r9, #16 /* r13: buffer size */
+ cmp r10, r13
+ lslgt r9, r13, #16
+ addle r9, #8
+
+1:
+ @ return from FIQ
+ subs pc, lr, #4
+imx_ssi_fiq_base:
+ .word 0x0
+imx_ssi_fiq_rx_buffer:
+ .word 0x0
+imx_ssi_fiq_tx_buffer:
+ .word 0x0
+imx_ssi_fiq_end:
+
diff --git a/arch/sh/include/asm/siu.h b/arch/sh/include/asm/siu.h
new file mode 100644
index 0000000..57565a3
--- /dev/null
+++ b/arch/sh/include/asm/siu.h
@@ -0,0 +1,26 @@
+/*
+ * platform header for the SIU ASoC driver
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * 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 ASM_SIU_H
+#define ASM_SIU_H
+
+#include <asm/dma-sh.h>
+
+struct device;
+
+struct siu_platform {
+ struct device *dma_dev;
+ enum sh_dmae_slave_chan_id dma_slave_tx_a;
+ enum sh_dmae_slave_chan_id dma_slave_rx_a;
+ enum sh_dmae_slave_chan_id dma_slave_tx_b;
+ enum sh_dmae_slave_chan_id dma_slave_rx_b;
+};
+
+#endif /* ASM_SIU_H */
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h
index ca24e7f..061f16d 100644
--- a/include/sound/soc-dai.h
+++ b/include/sound/soc-dai.h
@@ -16,6 +16,8 @@
#include <linux/list.h>
+#include <sound/soc.h>
+
struct snd_pcm_substream;
/*
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
index c5c95e1d..c0922a0 100644
--- a/include/sound/soc-dapm.h
+++ b/include/sound/soc-dapm.h
@@ -95,6 +95,21 @@
.shift = wshift, .invert = winvert, .kcontrols = wcontrols, \
.num_kcontrols = 1}
+/* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */
+#define SOC_PGA_ARRAY(wname, wreg, wshift, winvert,\
+ wcontrols) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)}
+#define SOC_MIXER_ARRAY(wname, wreg, wshift, winvert, \
+ wcontrols)\
+{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)}
+#define SOC_MIXER_NAMED_CTL_ARRAY(wname, wreg, wshift, winvert, \
+ wcontrols)\
+{ .id = snd_soc_dapm_mixer_named_ctl, .name = wname, .reg = wreg, \
+ .shift = wshift, .invert = winvert, .kcontrols = wcontrols, \
+ .num_kcontrols = ARRAY_SIZE(wcontrols)}
+
/* path domain with event - event handler must return 0 for success */
#define SND_SOC_DAPM_PGA_E(wname, wreg, wshift, winvert, wcontrols, \
wncontrols, wevent, wflags) \
@@ -126,6 +141,23 @@
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1, \
.event = wevent, .event_flags = wflags}
+/* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */
+#define SOC_PGA_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \
+ wevent, wflags) \
+{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \
+ .event = wevent, .event_flags = wflags}
+#define SOC_MIXER_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \
+ wevent, wflags) \
+{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \
+ .event = wevent, .event_flags = wflags}
+#define SOC_MIXER_NAMED_CTL_E_ARRAY(wname, wreg, wshift, winvert, \
+ wcontrols, wevent, wflags) \
+{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, \
+ .num_kcontrols = ARRAY_SIZE(wcontrols), .event = wevent, .event_flags = wflags}
+
/* events that are pre and post DAPM */
#define SND_SOC_DAPM_PRE(wname, wevent) \
{ .id = snd_soc_dapm_pre, .name = wname, .kcontrols = NULL, \
diff --git a/include/sound/soc.h b/include/sound/soc.h
index 0d7718f..4bbeb9f 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -169,6 +169,23 @@
.private_value = (unsigned long)&xenum }
/*
+ * Simplified versions of above macros, declaring a struct and calculating
+ * ARRAY_SIZE internally
+ */
+#define SOC_ENUM_DOUBLE_DECL(name, xreg, xshift_l, xshift_r, xtexts) \
+ struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \
+ ARRAY_SIZE(xtexts), xtexts)
+#define SOC_ENUM_SINGLE_DECL(name, xreg, xshift, xtexts) \
+ SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts)
+#define SOC_ENUM_SINGLE_EXT_DECL(name, xtexts) \
+ struct soc_enum name = SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(xtexts), xtexts)
+#define SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift_l, xshift_r, xmask, xtexts, xvalues) \
+ struct soc_enum name = SOC_VALUE_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, \
+ ARRAY_SIZE(xtexts), xtexts, xvalues)
+#define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \
+ SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
+
+/*
* Bias levels
*
* @ON: Bias is fully on for audio playback and capture operations.
@@ -253,6 +270,9 @@
/* codec register bit access */
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
unsigned int mask, unsigned int value);
+int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
+ unsigned short reg, unsigned int mask,
+ unsigned int value);
int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
unsigned int mask, unsigned int value);
@@ -402,6 +422,8 @@
short reg_cache_size;
short reg_cache_step;
+ unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
+
/* dapm */
u32 pop_time;
struct list_head dapm_widgets;
diff --git a/include/sound/tlv320dac33-plat.h b/include/sound/tlv320dac33-plat.h
index 5858d06..ac06652 100644
--- a/include/sound/tlv320dac33-plat.h
+++ b/include/sound/tlv320dac33-plat.h
@@ -15,6 +15,7 @@
struct tlv320dac33_platform_data {
int power_gpio;
+ u8 burst_bclkdiv;
};
#endif /* __TLV320DAC33_PLAT_H */
diff --git a/include/sound/tpa6130a2-plat.h b/include/sound/tpa6130a2-plat.h
index e8c901e..e29fde6 100644
--- a/include/sound/tpa6130a2-plat.h
+++ b/include/sound/tpa6130a2-plat.h
@@ -23,7 +23,13 @@
#ifndef TPA6130A2_PLAT_H
#define TPA6130A2_PLAT_H
+enum tpa_model {
+ TPA6130A2,
+ TPA6140A2,
+};
+
struct tpa6130a2_platform_data {
+ enum tpa_model id;
int power_gpio;
};
diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h
new file mode 100644
index 0000000..d66575a
--- /dev/null
+++ b/include/sound/wm8904.h
@@ -0,0 +1,57 @@
+/*
+ * Platform data for WM8904
+ *
+ * Copyright 2009 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 __MFD_WM8994_PDATA_H__
+#define __MFD_WM8994_PDATA_H__
+
+#define WM8904_DRC_REGS 4
+#define WM8904_EQ_REGS 25
+
+/**
+ * DRC configurations are specified with a label and a set of register
+ * values to write (the enable bits will be ignored). At runtime an
+ * enumerated control will be presented for each DRC block allowing
+ * the user to choose the configration to use.
+ *
+ * Configurations may be generated by hand or by using the DRC control
+ * panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
+ * for details.
+ */
+struct wm8904_drc_cfg {
+ const char *name;
+ u16 regs[WM8904_DRC_REGS];
+};
+
+/**
+ * ReTune Mobile configurations are specified with a label, sample
+ * rate and set of values to write (the enable bits will be ignored).
+ *
+ * Configurations are expected to be generated using the ReTune Mobile
+ * control panel in WISCE - see http://www.wolfsonmicro.com/wisce/
+ */
+struct wm8904_retune_mobile_cfg {
+ const char *name;
+ unsigned int rate;
+ u16 regs[WM8904_EQ_REGS];
+};
+
+struct wm8904_pdata {
+ int num_drc_cfgs;
+ struct wm8904_drc_cfg *drc_cfgs;
+
+ int num_retune_mobile_cfgs;
+ struct wm8904_retune_mobile_cfg *retune_mobile_cfgs;
+};
+
+#endif
diff --git a/include/sound/wm8955.h b/include/sound/wm8955.h
new file mode 100644
index 0000000..5074ef4
--- /dev/null
+++ b/include/sound/wm8955.h
@@ -0,0 +1,26 @@
+/*
+ * Platform data for WM8955
+ *
+ * Copyright 2009 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 __WM8955_PDATA_H__
+#define __WM8955_PDATA_H__
+
+struct wm8955_pdata {
+ /* Configure LOUT2/ROUT2 to drive a speaker */
+ unsigned int out2_speaker:1;
+
+ /* Configure MONOIN+/- in differential mode */
+ unsigned int monoin_diff:1;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 52b005f..0aad72f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -23,6 +23,7 @@
select SND_SOC_AK4671 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_MAX9877 if I2C
+ select SND_SOC_DA7210 if I2C
select SND_SOC_PCM3008
select SND_SOC_SPDIF
select SND_SOC_SSM2602 if I2C
@@ -49,11 +50,14 @@
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8900 if I2C
select SND_SOC_WM8903 if I2C
+ select SND_SOC_WM8904 if I2C
select SND_SOC_WM8940 if I2C
+ select SND_SOC_WM8955 if I2C
select SND_SOC_WM8960 if I2C
select SND_SOC_WM8961 if I2C
select SND_SOC_WM8971 if I2C
select SND_SOC_WM8974 if I2C
+ select SND_SOC_WM8978 if I2C
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8990 if I2C
select SND_SOC_WM8993 if I2C
@@ -112,6 +116,9 @@
config SND_SOC_CS4270
tristate
+config SND_SOC_DA7210
+ tristate
+
# 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
@@ -203,9 +210,15 @@
config SND_SOC_WM8903
tristate
+config SND_SOC_WM8904
+ tristate
+
config SND_SOC_WM8940
tristate
+config SND_SOC_WM8955
+ tristate
+
config SND_SOC_WM8960
tristate
@@ -218,6 +231,9 @@
config SND_SOC_WM8974
tristate
+config SND_SOC_WM8978
+ tristate
+
config SND_SOC_WM8988
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index dbaecb1..fbd290e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -10,6 +10,7 @@
snd-soc-ak4671-objs := ak4671.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-cx20442-objs := cx20442.o
+snd-soc-da7210-objs := da7210.o
snd-soc-l3-objs := l3.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-spdif-objs := spdif_transciever.o
@@ -36,11 +37,14 @@
snd-soc-wm8776-objs := wm8776.o
snd-soc-wm8900-objs := wm8900.o
snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8904-objs := wm8904.o
snd-soc-wm8940-objs := wm8940.o
+snd-soc-wm8955-objs := wm8955.o
snd-soc-wm8960-objs := wm8960.o
snd-soc-wm8961-objs := wm8961.o
snd-soc-wm8971-objs := wm8971.o
snd-soc-wm8974-objs := wm8974.o
+snd-soc-wm8978-objs := wm8978.o
snd-soc-wm8988-objs := wm8988.o
snd-soc-wm8990-objs := wm8990.o
snd-soc-wm8993-objs := wm8993.o
@@ -66,6 +70,7 @@
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
+obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
@@ -92,11 +97,14 @@
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.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_WM8974) += snd-soc-wm8974.o
+obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o
obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
+obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o
obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o
+obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
+obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c
index 2c18e3d..3c80137 100644
--- a/sound/soc/codecs/ad1836.c
+++ b/sound/soc/codecs/ad1836.c
@@ -171,57 +171,35 @@
return 0;
}
-
-/*
- * interface to read/write ad1836 register
- */
-#define AD1836_SPI_REG_SHFT 12
-#define AD1836_SPI_READ (1 << 11)
-#define AD1836_SPI_VAL_MSK 0x3FF
-
-/*
- * write to the ad1836 register space
- */
-
-static int ad1836_write_reg(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
+#ifdef CONFIG_PM
+static int ad1836_soc_suspend(struct platform_device *pdev,
+ pm_message_t state)
{
- u16 *reg_cache = codec->reg_cache;
- int ret = 0;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
- if (value != reg_cache[reg]) {
- unsigned short buf;
- struct spi_transfer t = {
- .tx_buf = &buf,
- .len = 2,
- };
- struct spi_message m;
+ /* reset clock control mode */
+ u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+ adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
- buf = (reg << AD1836_SPI_REG_SHFT) |
- (value & AD1836_SPI_VAL_MSK);
- spi_message_init(&m);
- spi_message_add_tail(&t, &m);
- ret = spi_sync(codec->control_data, &m);
- if (ret == 0)
- reg_cache[reg] = value;
- }
-
- return ret;
+ return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
}
-/*
- * read from the ad1836 register space cache
- */
-static unsigned int ad1836_read_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg)
+static int ad1836_soc_resume(struct platform_device *pdev)
{
- u16 *reg_cache = codec->reg_cache;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
- if (reg >= codec->reg_cache_size)
- return -EINVAL;
+ /* restore clock control mode */
+ u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+ adc_ctrl2 |= AD1836_ADC_AUX;
- return reg_cache[reg];
+ return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
}
+#else
+#define ad1836_soc_suspend NULL
+#define ad1836_soc_resume NULL
+#endif
static int __devinit ad1836_spi_probe(struct spi_device *spi)
{
@@ -306,32 +284,38 @@
codec->owner = THIS_MODULE;
codec->dai = &ad1836_dai;
codec->num_dai = 1;
- codec->write = ad1836_write_reg;
- codec->read = ad1836_read_reg_cache;
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
ad1836_dai.dev = codec->dev;
ad1836_codec = codec;
+ ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O: %d\n",
+ ret);
+ kfree(ad1836);
+ return ret;
+ }
+
/* default setting for ad1836 */
/* de-emphasis: 48kHz, power-on dac */
- codec->write(codec, AD1836_DAC_CTRL1, 0x300);
+ snd_soc_write(codec, AD1836_DAC_CTRL1, 0x300);
/* unmute dac channels */
- codec->write(codec, AD1836_DAC_CTRL2, 0x0);
+ snd_soc_write(codec, AD1836_DAC_CTRL2, 0x0);
/* high-pass filter enable, power-on adc */
- codec->write(codec, AD1836_ADC_CTRL1, 0x100);
+ snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
/* unmute adc channles, adc aux mode */
- codec->write(codec, AD1836_ADC_CTRL2, 0x180);
+ snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
/* left/right diff:PGA/MUX */
- codec->write(codec, AD1836_ADC_CTRL3, 0x3A);
+ snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
/* volume */
- codec->write(codec, AD1836_DAC_L1_VOL, 0x3FF);
- codec->write(codec, AD1836_DAC_R1_VOL, 0x3FF);
- codec->write(codec, AD1836_DAC_L2_VOL, 0x3FF);
- codec->write(codec, AD1836_DAC_R2_VOL, 0x3FF);
- codec->write(codec, AD1836_DAC_L3_VOL, 0x3FF);
- codec->write(codec, AD1836_DAC_R3_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
+ snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
ret = snd_soc_register_codec(codec);
if (ret != 0) {
@@ -404,6 +388,8 @@
struct snd_soc_codec_device soc_codec_dev_ad1836 = {
.probe = ad1836_probe,
.remove = ad1836_remove,
+ .suspend = ad1836_soc_suspend,
+ .resume = ad1836_soc_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1836);
diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h
index 7660ee6..e9d90d3 100644
--- a/sound/soc/codecs/ad1836.h
+++ b/sound/soc/codecs/ad1836.h
@@ -54,6 +54,7 @@
#define AD1836_ADC_SERFMT_MASK (7 << 6)
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
+#define AD1836_ADC_AUX (0x6 << 6)
#define AD1836_ADC_CTRL3 14
diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c
index 5d48918..c233810 100644
--- a/sound/soc/codecs/ad1938.c
+++ b/sound/soc/codecs/ad1938.c
@@ -46,6 +46,11 @@
u8 reg_cache[AD1938_NUM_REGS];
};
+/* ad1938 register cache & default register settings */
+static const u8 ad1938_reg[AD1938_NUM_REGS] = {
+ 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0,
+};
+
static struct snd_soc_codec *ad1938_codec;
struct snd_soc_codec_device soc_codec_dev_ad1938;
static int ad1938_register(struct ad1938_priv *ad1938);
@@ -97,6 +102,7 @@
static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1),
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_SUPPLY("PLL_PWR", AD1938_PLL_CLK_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0),
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
@@ -107,6 +113,8 @@
};
static const struct snd_soc_dapm_route audio_paths[] = {
+ { "DAC", NULL, "PLL_PWR" },
+ { "ADC", NULL, "PLL_PWR" },
{ "DAC", NULL, "ADC_PWR" },
{ "ADC", NULL, "ADC_PWR" },
{ "DAC1OUT", "DAC1 Switch", "DAC" },
@@ -126,30 +134,20 @@
struct snd_soc_codec *codec = dai->codec;
int reg;
- reg = codec->read(codec, AD1938_DAC_CTRL2);
+ reg = snd_soc_read(codec, AD1938_DAC_CTRL2);
reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg &
(~AD1938_DAC_MASTER_MUTE);
- codec->write(codec, AD1938_DAC_CTRL2, reg);
-
- return 0;
-}
-
-static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd)
-{
- int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0);
- reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg |
- AD1938_PLL_POWERDOWN;
- codec->write(codec, AD1938_PLL_CLK_CTRL0, reg);
+ snd_soc_write(codec, AD1938_DAC_CTRL2, reg);
return 0;
}
static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
- unsigned int mask, int slots, int width)
+ unsigned int rx_mask, int slots, int width)
{
struct snd_soc_codec *codec = dai->codec;
- int dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
- int adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
+ int dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1);
+ int adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2);
dac_reg &= ~AD1938_DAC_CHAN_MASK;
adc_reg &= ~AD1938_ADC_CHAN_MASK;
@@ -175,8 +173,8 @@
return -EINVAL;
}
- codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
- codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
+ snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg);
+ snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg);
return 0;
}
@@ -187,8 +185,8 @@
struct snd_soc_codec *codec = codec_dai->codec;
int adc_reg, dac_reg;
- adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
- dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
+ adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2);
+ dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1);
/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
* with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
@@ -265,8 +263,8 @@
return -EINVAL;
}
- codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
- codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
+ snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg);
+ snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg);
return 0;
}
@@ -295,134 +293,13 @@
break;
}
- reg = codec->read(codec, AD1938_DAC_CTRL2);
+ reg = snd_soc_read(codec, AD1938_DAC_CTRL2);
reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len;
- codec->write(codec, AD1938_DAC_CTRL2, reg);
+ snd_soc_write(codec, AD1938_DAC_CTRL2, reg);
- reg = codec->read(codec, AD1938_ADC_CTRL1);
+ reg = snd_soc_read(codec, AD1938_ADC_CTRL1);
reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len;
- codec->write(codec, AD1938_ADC_CTRL1, reg);
-
- return 0;
-}
-
-static int ad1938_set_bias_level(struct snd_soc_codec *codec,
- enum snd_soc_bias_level level)
-{
- switch (level) {
- case SND_SOC_BIAS_ON:
- ad1938_pll_powerctrl(codec, 1);
- break;
- case SND_SOC_BIAS_PREPARE:
- break;
- case SND_SOC_BIAS_STANDBY:
- case SND_SOC_BIAS_OFF:
- ad1938_pll_powerctrl(codec, 0);
- break;
- }
- codec->bias_level = level;
- return 0;
-}
-
-/*
- * interface to read/write ad1938 register
- */
-
-#define AD1938_SPI_ADDR 0x4
-#define AD1938_SPI_READ 0x1
-#define AD1938_SPI_BUFLEN 3
-
-/*
- * write to the ad1938 register space
- */
-
-static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg,
- unsigned int value)
-{
- u8 *reg_cache = codec->reg_cache;
- int ret = 0;
-
- if (value != reg_cache[reg]) {
- uint8_t buf[AD1938_SPI_BUFLEN];
- struct spi_transfer t = {
- .tx_buf = buf,
- .len = AD1938_SPI_BUFLEN,
- };
- struct spi_message m;
-
- buf[0] = AD1938_SPI_ADDR << 1;
- buf[1] = reg;
- buf[2] = value;
- spi_message_init(&m);
- spi_message_add_tail(&t, &m);
- ret = spi_sync(codec->control_data, &m);
- if (ret == 0)
- reg_cache[reg] = value;
- }
-
- return ret;
-}
-
-/*
- * read from the ad1938 register space cache
- */
-
-static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec,
- unsigned int reg)
-{
- u8 *reg_cache = codec->reg_cache;
-
- if (reg >= codec->reg_cache_size)
- return -EINVAL;
-
- return reg_cache[reg];
-}
-
-/*
- * read from the ad1938 register space
- */
-
-static unsigned int ad1938_read_reg(struct snd_soc_codec *codec,
- unsigned int reg)
-{
- char w_buf[AD1938_SPI_BUFLEN];
- char r_buf[AD1938_SPI_BUFLEN];
- int ret;
-
- struct spi_transfer t = {
- .tx_buf = w_buf,
- .rx_buf = r_buf,
- .len = AD1938_SPI_BUFLEN,
- };
- struct spi_message m;
-
- w_buf[0] = (AD1938_SPI_ADDR << 1) | AD1938_SPI_READ;
- w_buf[1] = reg;
- w_buf[2] = 0;
-
- spi_message_init(&m);
- spi_message_add_tail(&t, &m);
- ret = spi_sync(codec->control_data, &m);
- if (ret == 0)
- return r_buf[2];
- else
- return -EIO;
-}
-
-static int ad1938_fill_cache(struct snd_soc_codec *codec)
-{
- int i;
- u8 *reg_cache = codec->reg_cache;
- struct spi_device *spi = codec->control_data;
-
- for (i = 0; i < codec->reg_cache_size; i++) {
- int ret = ad1938_read_reg(codec, i);
- if (ret == -EIO) {
- dev_err(&spi->dev, "AD1938 SPI read failure\n");
- return ret;
- }
- reg_cache[i] = ret;
- }
+ snd_soc_write(codec, AD1938_ADC_CTRL1, reg);
return 0;
}
@@ -512,32 +389,37 @@
codec->owner = THIS_MODULE;
codec->dai = &ad1938_dai;
codec->num_dai = 1;
- codec->write = ad1938_write_reg;
- codec->read = ad1938_read_reg_cache;
- codec->set_bias_level = ad1938_set_bias_level;
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
ad1938_dai.dev = codec->dev;
ad1938_codec = codec;
+ memcpy(codec->reg_cache, ad1938_reg, AD1938_NUM_REGS);
+
+ ret = snd_soc_codec_set_cache_io(codec, 16, 8, SND_SOC_SPI);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to set cache I/O: %d\n",
+ ret);
+ kfree(ad1938);
+ return ret;
+ }
+
/* default setting for ad1938 */
/* unmute dac channels */
- codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0);
+ snd_soc_write(codec, AD1938_DAC_CHNL_MUTE, 0x0);
/* de-emphasis: 48kHz, powedown dac */
- codec->write(codec, AD1938_DAC_CTRL2, 0x1A);
+ snd_soc_write(codec, AD1938_DAC_CTRL2, 0x1A);
/* powerdown dac, dac in tdm mode */
- codec->write(codec, AD1938_DAC_CTRL0, 0x41);
+ snd_soc_write(codec, AD1938_DAC_CTRL0, 0x41);
/* high-pass filter enable */
- codec->write(codec, AD1938_ADC_CTRL0, 0x3);
+ snd_soc_write(codec, AD1938_ADC_CTRL0, 0x3);
/* sata delay=1, adc aux mode */
- codec->write(codec, AD1938_ADC_CTRL1, 0x43);
+ snd_soc_write(codec, AD1938_ADC_CTRL1, 0x43);
/* pll input: mclki/xi */
- codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D);
- codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04);
-
- ad1938_fill_cache(codec);
+ snd_soc_write(codec, AD1938_PLL_CLK_CTRL0, 0x9D);
+ snd_soc_write(codec, AD1938_PLL_CLK_CTRL1, 0x04);
ret = snd_soc_register_codec(codec);
if (ret != 0) {
@@ -559,7 +441,6 @@
static void ad1938_unregister(struct ad1938_priv *ad1938)
{
- ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF);
snd_soc_unregister_dai(&ad1938_dai);
snd_soc_unregister_codec(&ad1938->codec);
kfree(ad1938);
@@ -593,7 +474,6 @@
ARRAY_SIZE(ad1938_dapm_widgets));
snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
- ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
pcm_err:
return ret;
@@ -610,37 +490,9 @@
return 0;
}
-#ifdef CONFIG_PM
-static int ad1938_suspend(struct platform_device *pdev,
- pm_message_t state)
-{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->card->codec;
-
- ad1938_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
-}
-
-static int ad1938_resume(struct platform_device *pdev)
-{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->card->codec;
-
- if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
- ad1938_set_bias_level(codec, SND_SOC_BIAS_ON);
-
- return 0;
-}
-#else
-#define ad1938_suspend NULL
-#define ad1938_resume NULL
-#endif
-
struct snd_soc_codec_device soc_codec_dev_ad1938 = {
.probe = ad1938_probe,
.remove = ad1938_remove,
- .suspend = ad1938_suspend,
- .resume = ad1938_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938);
diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c
index 3a14c6f..b9ef7e4 100644
--- a/sound/soc/codecs/ak4104.c
+++ b/sound/soc/codecs/ak4104.c
@@ -185,9 +185,7 @@
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
- .rates = SNDRV_PCM_RATE_44100 |
- SNDRV_PCM_RATE_48000 |
- SNDRV_PCM_RATE_32000,
+ .rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_S24_LE
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
index ffe122d..593bfc7 100644
--- a/sound/soc/codecs/cs4270.c
+++ b/sound/soc/codecs/cs4270.c
@@ -28,6 +28,7 @@
#include <sound/initval.h>
#include <linux/i2c.h>
#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
#include "cs4270.h"
@@ -106,6 +107,10 @@
#define CS4270_MUTE_DAC_A 0x01
#define CS4270_MUTE_DAC_B 0x02
+static const char *supply_names[] = {
+ "va", "vd", "vlc"
+};
+
/* Private data for the CS4270 */
struct cs4270_private {
struct snd_soc_codec codec;
@@ -114,6 +119,9 @@
unsigned int mode; /* The mode (I2S or left-justified) */
unsigned int slave_mode;
unsigned int manual_mute;
+
+ /* power domain regulators */
+ struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
};
/**
@@ -192,6 +200,11 @@
* 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.
+ *
+ * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
+ * theoretically possible sample rates to be enabled. Call it again with a
+ * proper value set one the external clock is set (most probably you would do
+ * that from a machine's driver 'hw_param' hook.
*/
static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
@@ -205,20 +218,27 @@
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 (cs4270->mclk) {
+ 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) {
- dev_err(codec->dev, "could not find a valid sample rate\n");
- return -EINVAL;
+ if (!rates) {
+ dev_err(codec->dev, "could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+ } else {
+ /* enable all possible rates */
+ rates = SNDRV_PCM_RATE_8000_192000;
+ rate_min = 8000;
+ rate_max = 192000;
}
codec_dai->playback.rates = rates;
@@ -579,7 +599,8 @@
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = cs4270_codec;
- int ret;
+ struct cs4270_private *cs4270 = codec->private_data;
+ int i, ret;
/* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
socdev->card->codec = codec;
@@ -599,6 +620,15 @@
goto error_free_pcms;
}
+ /* get the power supply regulators */
+ for (i = 0; i < ARRAY_SIZE(supply_names); i++)
+ cs4270->supplies[i].supply = supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+ if (ret < 0)
+ goto error_free_pcms;
+
return 0;
error_free_pcms:
@@ -616,8 +646,11 @@
static int cs4270_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = cs4270_codec;
+ struct cs4270_private *cs4270 = codec->private_data;
snd_soc_free_pcms(socdev);
+ regulator_bulk_free(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
return 0;
};
@@ -799,17 +832,33 @@
static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg)
{
struct snd_soc_codec *codec = cs4270_codec;
- int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+ struct cs4270_private *cs4270 = codec->private_data;
+ int reg, ret;
- return snd_soc_write(codec, CS4270_PWRCTL, reg);
+ reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+ if (reg < 0)
+ return reg;
+
+ ret = snd_soc_write(codec, CS4270_PWRCTL, reg);
+ if (ret < 0)
+ return ret;
+
+ regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+
+ return 0;
}
static int cs4270_soc_resume(struct platform_device *pdev)
{
struct snd_soc_codec *codec = cs4270_codec;
+ struct cs4270_private *cs4270 = codec->private_data;
struct i2c_client *i2c_client = codec->control_data;
int reg;
+ regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
+ cs4270->supplies);
+
/* In case the device was put to hard reset during sleep, we need to
* wait 500ns here before any I2C communication. */
ndelay(500);
diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c
new file mode 100644
index 0000000..cf2975a
--- /dev/null
+++ b/sound/soc/codecs/da7210.c
@@ -0,0 +1,589 @@
+/*
+ * DA7210 ALSA Soc codec driver
+ *
+ * Copyright (c) 2009 Dialog Semiconductor
+ * Written by David Chen <Dajun.chen@diasemi.com>
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S
+ *
+ * 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/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "da7210.h"
+
+/* DA7210 register space */
+#define DA7210_STATUS 0x02
+#define DA7210_STARTUP1 0x03
+#define DA7210_MIC_L 0x07
+#define DA7210_MIC_R 0x08
+#define DA7210_INMIX_L 0x0D
+#define DA7210_INMIX_R 0x0E
+#define DA7210_ADC_HPF 0x0F
+#define DA7210_ADC 0x10
+#define DA7210_DAC_HPF 0x14
+#define DA7210_DAC_L 0x15
+#define DA7210_DAC_R 0x16
+#define DA7210_DAC_SEL 0x17
+#define DA7210_OUTMIX_L 0x1C
+#define DA7210_OUTMIX_R 0x1D
+#define DA7210_HP_L_VOL 0x21
+#define DA7210_HP_R_VOL 0x22
+#define DA7210_HP_CFG 0x23
+#define DA7210_DAI_SRC_SEL 0x25
+#define DA7210_DAI_CFG1 0x26
+#define DA7210_DAI_CFG3 0x28
+#define DA7210_PLL_DIV3 0x2B
+#define DA7210_PLL 0x2C
+
+/* STARTUP1 bit fields */
+#define DA7210_SC_MST_EN (1 << 0)
+
+/* MIC_L bit fields */
+#define DA7210_MICBIAS_EN (1 << 6)
+#define DA7210_MIC_L_EN (1 << 7)
+
+/* MIC_R bit fields */
+#define DA7210_MIC_R_EN (1 << 7)
+
+/* INMIX_L bit fields */
+#define DA7210_IN_L_EN (1 << 7)
+
+/* INMIX_R bit fields */
+#define DA7210_IN_R_EN (1 << 7)
+
+/* ADC_HPF bit fields */
+#define DA7210_ADC_VOICE_EN (1 << 7)
+
+/* ADC bit fields */
+#define DA7210_ADC_L_EN (1 << 3)
+#define DA7210_ADC_R_EN (1 << 7)
+
+/* DAC_HPF fields */
+#define DA7210_DAC_VOICE_EN (1 << 7)
+
+/* DAC_SEL bit fields */
+#define DA7210_DAC_L_SRC_DAI_L (4 << 0)
+#define DA7210_DAC_L_EN (1 << 3)
+#define DA7210_DAC_R_SRC_DAI_R (5 << 4)
+#define DA7210_DAC_R_EN (1 << 7)
+
+/* OUTMIX_L bit fields */
+#define DA7210_OUT_L_EN (1 << 7)
+
+/* OUTMIX_R bit fields */
+#define DA7210_OUT_R_EN (1 << 7)
+
+/* HP_CFG bit fields */
+#define DA7210_HP_2CAP_MODE (1 << 1)
+#define DA7210_HP_SENSE_EN (1 << 2)
+#define DA7210_HP_L_EN (1 << 3)
+#define DA7210_HP_MODE (1 << 6)
+#define DA7210_HP_R_EN (1 << 7)
+
+/* DAI_SRC_SEL bit fields */
+#define DA7210_DAI_OUT_L_SRC (6 << 0)
+#define DA7210_DAI_OUT_R_SRC (7 << 4)
+
+/* DAI_CFG1 bit fields */
+#define DA7210_DAI_WORD_S16_LE (0 << 0)
+#define DA7210_DAI_WORD_S24_LE (2 << 0)
+#define DA7210_DAI_FLEN_64BIT (1 << 2)
+#define DA7210_DAI_MODE_MASTER (1 << 7)
+
+/* DAI_CFG3 bit fields */
+#define DA7210_DAI_FORMAT_I2SMODE (0 << 0)
+#define DA7210_DAI_OE (1 << 3)
+#define DA7210_DAI_EN (1 << 7)
+
+/*PLL_DIV3 bit fields */
+#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4)
+#define DA7210_PLL_BYP (1 << 6)
+
+/* PLL bit fields */
+#define DA7210_PLL_FS_48000 (11 << 0)
+
+#define DA7210_VERSION "0.0.1"
+
+/* Codec private data */
+struct da7210_priv {
+ struct snd_soc_codec codec;
+};
+
+static struct snd_soc_codec *da7210_codec;
+
+/*
+ * Register cache
+ */
+static const u8 da7210_reg[] = {
+ 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R0 - R7 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, /* R8 - RF */
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54, /* R10 - R17 */
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R18 - R1F */
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, /* R20 - R27 */
+ 0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00, /* R28 - R2F */
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, /* R30 - R37 */
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, /* R38 - R3F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R40 - R4F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R48 - R4F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R50 - R57 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R58 - R5F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R60 - R67 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R68 - R6F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R70 - R77 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00, /* R78 - R7F */
+ 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, /* R80 - R87 */
+ 0x00, /* R88 */
+};
+
+/*
+ * Read da7210 register cache
+ */
+static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg)
+{
+ u8 *cache = codec->reg_cache;
+ BUG_ON(reg > ARRAY_SIZE(da7210_reg));
+ return cache[reg];
+}
+
+/*
+ * Write to the da7210 register space
+ */
+static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value)
+{
+ u8 *cache = codec->reg_cache;
+ u8 data[2];
+
+ BUG_ON(codec->volatile_register);
+
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ if (reg >= codec->reg_cache_size)
+ return -EIO;
+
+ if (2 != codec->hw_write(codec->control_data, data, 2))
+ return -EIO;
+
+ cache[reg] = value;
+ return 0;
+}
+
+/*
+ * Read from the da7210 register space.
+ */
+static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg)
+{
+ if (DA7210_STATUS == reg)
+ return i2c_smbus_read_byte_data(codec->control_data, reg);
+
+ return da7210_read_reg_cache(codec, reg);
+}
+
+static int da7210_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (is_play) {
+ /* PlayBack Volume 40 */
+ snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40);
+ snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40);
+
+ /* Enable Out */
+ snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10);
+ snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10);
+
+ } else {
+ /* Volume 7 */
+ snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7);
+ snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7);
+
+ /* Enable Mic */
+ snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1);
+ snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1);
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI word length.
+ */
+static int da7210_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ u32 dai_cfg1;
+ u32 reg, mask;
+
+ /* set DAI source to Left and Right ADC */
+ da7210_write(codec, DA7210_DAI_SRC_SEL,
+ DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC);
+
+ /* Enable DAI */
+ da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN);
+
+ dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ dai_cfg1 |= DA7210_DAI_WORD_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dai_cfg1 |= DA7210_DAI_WORD_S24_LE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+
+ /* FIXME
+ *
+ * It support 48K only now
+ */
+ switch (params_rate(params)) {
+ case 48000:
+ if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+ reg = DA7210_DAC_HPF;
+ mask = DA7210_DAC_VOICE_EN;
+ } else {
+ reg = DA7210_ADC_HPF;
+ mask = DA7210_ADC_VOICE_EN;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, reg, mask, 0);
+
+ return 0;
+}
+
+/*
+ * Set DAI mode and Format
+ */
+static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u32 dai_cfg1;
+ u32 dai_cfg3;
+
+ dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1);
+ dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ dai_cfg1 |= DA7210_DAI_MODE_MASTER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* FIXME
+ *
+ * It support I2S only now
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* FIXME
+ *
+ * It support 64bit data transmission only now
+ */
+ dai_cfg1 |= DA7210_DAI_FLEN_64BIT;
+
+ da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+ da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3);
+
+ return 0;
+}
+
+#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+/* DAI operations */
+static struct snd_soc_dai_ops da7210_dai_ops = {
+ .startup = da7210_startup,
+ .hw_params = da7210_hw_params,
+ .set_fmt = da7210_set_dai_fmt,
+};
+
+struct snd_soc_dai da7210_dai = {
+ .name = "DA7210 IIS",
+ .id = 0,
+ /* playback capabilities */
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = DA7210_FORMATS,
+ },
+ /* capture capabilities */
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = DA7210_FORMATS,
+ },
+ .ops = &da7210_dai_ops,
+};
+EXPORT_SYMBOL_GPL(da7210_dai);
+
+/*
+ * Initialize the DA7210 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int da7210_init(struct da7210_priv *da7210)
+{
+ struct snd_soc_codec *codec = &da7210->codec;
+ int ret = 0;
+
+ if (da7210_codec) {
+ dev_err(codec->dev, "Another da7210 is registered\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = da7210;
+ codec->name = "DA7210";
+ codec->owner = THIS_MODULE;
+ codec->read = da7210_read;
+ codec->write = da7210_write;
+ codec->dai = &da7210_dai;
+ codec->num_dai = 1;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+ codec->reg_cache_size = ARRAY_SIZE(da7210_reg);
+ codec->reg_cache = kmemdup(da7210_reg,
+ sizeof(da7210_reg), GFP_KERNEL);
+
+ if (!codec->reg_cache)
+ return -ENOMEM;
+
+ da7210_dai.dev = codec->dev;
+ da7210_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(codec->dev, "Failed to register CODEC: %d\n", ret);
+ goto init_err;
+ }
+
+ ret = snd_soc_register_dai(&da7210_dai);
+ if (ret) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ goto init_err;
+ }
+
+ /* FIXME
+ *
+ * This driver use fixed value here
+ */
+
+ /*
+ * ADC settings
+ */
+
+ /* Enable Left & Right MIC PGA and Mic Bias */
+ da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN);
+ da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN);
+
+ /* Enable Left and Right input PGA */
+ da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN);
+ da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN);
+
+ /* Enable Left and Right ADC */
+ da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN);
+
+ /*
+ * DAC settings
+ */
+
+ /* Enable Left and Right DAC */
+ da7210_write(codec, DA7210_DAC_SEL,
+ DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN |
+ DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN);
+
+ /* Enable Left and Right out PGA */
+ da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN);
+ da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN);
+
+ /* Enable Left and Right HeadPhone PGA */
+ da7210_write(codec, DA7210_HP_CFG,
+ DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN |
+ DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN);
+
+ /* Diable PLL and bypass it */
+ da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000);
+
+ /* Bypass PLL and set MCLK freq rang to 10-20MHz */
+ da7210_write(codec, DA7210_PLL_DIV3,
+ DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
+
+ /* Activate all enabled subsystem */
+ da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN);
+
+ return ret;
+
+init_err:
+ kfree(codec->reg_cache);
+ codec->reg_cache = NULL;
+
+ return ret;
+
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int __devinit da7210_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct da7210_priv *da7210;
+ struct snd_soc_codec *codec;
+ int ret;
+
+ da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL);
+ if (!da7210)
+ return -ENOMEM;
+
+ codec = &da7210->codec;
+ codec->dev = &i2c->dev;
+
+ i2c_set_clientdata(i2c, da7210);
+ codec->control_data = i2c;
+
+ ret = da7210_init(da7210);
+ if (ret < 0)
+ pr_err("Failed to initialise da7210 audio codec\n");
+
+ return ret;
+}
+
+static int __devexit da7210_i2c_remove(struct i2c_client *client)
+{
+ struct da7210_priv *da7210 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_dai(&da7210_dai);
+ kfree(da7210->codec.reg_cache);
+ kfree(da7210);
+ da7210_codec = NULL;
+
+ return 0;
+}
+
+static const struct i2c_device_id da7210_i2c_id[] = {
+ { "da7210", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da7210_i2c_id);
+
+/* I2C codec control layer */
+static struct i2c_driver da7210_i2c_driver = {
+ .driver = {
+ .name = "DA7210 I2C Codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = da7210_i2c_probe,
+ .remove = __devexit_p(da7210_i2c_remove),
+ .id_table = da7210_i2c_id,
+};
+#endif
+
+static int da7210_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret;
+
+ if (!da7210_codec) {
+ dev_err(&pdev->dev, "Codec device not registered\n");
+ return -ENODEV;
+ }
+
+ socdev->card->codec = da7210_codec;
+ codec = da7210_codec;
+
+ /* Register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0)
+ goto pcm_err;
+
+ dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
+
+pcm_err:
+ return ret;
+}
+
+static int da7210_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_da7210 = {
+ .probe = da7210_probe,
+ .remove = da7210_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_da7210);
+
+static int __init da7210_modinit(void)
+{
+ int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&da7210_i2c_driver);
+#endif
+ return ret;
+}
+module_init(da7210_modinit);
+
+static void __exit da7210_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&da7210_i2c_driver);
+#endif
+}
+module_exit(da7210_exit);
+
+MODULE_DESCRIPTION("ASoC DA7210 driver");
+MODULE_AUTHOR("David Chen, Kuninori Morimoto");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/da7210.h b/sound/soc/codecs/da7210.h
new file mode 100644
index 0000000..390d621
--- /dev/null
+++ b/sound/soc/codecs/da7210.h
@@ -0,0 +1,24 @@
+/*
+ * da7210.h -- audio driver for da7210
+ *
+ * Copyright (c) 2009 Dialog Semiconductor
+ * Written by David Chen <Dajun.chen@diasemi.com>
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.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 _DA7210_H
+#define _DA7210_H
+
+extern struct snd_soc_dai da7210_dai;
+extern struct snd_soc_codec_device soc_codec_dev_da7210;
+
+#endif
+
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
index 2b4dc2b..e4b946a 100644
--- a/sound/soc/codecs/tlv320aic3x.c
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -765,9 +765,10 @@
struct snd_soc_codec *codec = socdev->card->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;
+ u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
+ u16 d, pll_d = 1;
u8 reg;
+ int clk;
/* select data word length */
data =
@@ -833,48 +834,70 @@
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.
+ /* Use PLL, compute apropriate setup for j, d, r and p, the closest
+ * one wins the game. Try with d==0 first, next with d!=0.
+ * Constraints for j are according to the datasheet.
* 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;
+ for (j = 4; j <= 55; j++) {
+ /* This is actually 1000*((j+(d/10000))*r)/p
+ * The term had to be converted to get
+ * rid of the division by 10000; d = 0 here
+ */
+ int tmp_clk = (1000 * j * r) / p;
- if (j > 63)
- continue;
+ /* Check whether this values get closer than
+ * the best ones we had before
+ */
+ if (abs(codec_clk - tmp_clk) <
+ abs(codec_clk - last_clk)) {
+ pll_j = j; pll_d = 0;
+ pll_r = r; pll_p = p;
+ last_clk = tmp_clk;
+ }
- 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 (tmp_clk == codec_clk)
+ goto found;
}
-
- /* Early exit for exact matches */
- if (clk == codec_clk)
- break;
}
+ /* try with d != 0 */
+ for (p = 1; p <= 8; p++) {
+ j = codec_clk * p / 1000;
+
+ if (j < 4 || j > 11)
+ continue;
+
+ /* do not use codec_clk here since we'd loose precision */
+ d = ((2048 * p * fsref) - j * aic3x->sysclk)
+ * 100 / (aic3x->sysclk/100);
+
+ clk = (10000 * j + d) / (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 = 1; pll_p = p;
+ last_clk = clk;
+ }
+
+ /* Early exit for exact matches */
+ if (clk == codec_clk)
+ goto found;
+ }
+
if (last_clk == 0) {
printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
return -EINVAL;
}
+found:
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);
diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c
index 9c8903d..1b35d0c 100644
--- a/sound/soc/codecs/tlv320dac33.c
+++ b/sound/soc/codecs/tlv320dac33.c
@@ -30,6 +30,7 @@
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -58,11 +59,26 @@
DAC33_FLUSH,
};
+enum dac33_fifo_modes {
+ DAC33_FIFO_BYPASS = 0,
+ DAC33_FIFO_MODE1,
+ DAC33_FIFO_MODE7,
+ DAC33_FIFO_LAST_MODE,
+};
+
+#define DAC33_NUM_SUPPLIES 3
+static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = {
+ "AVDD",
+ "DVDD",
+ "IOVDD",
+};
+
struct tlv320dac33_priv {
struct mutex mutex;
struct workqueue_struct *dac33_wq;
struct work_struct work;
struct snd_soc_codec codec;
+ struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES];
int power_gpio;
int chip_power;
int irq;
@@ -73,8 +89,9 @@
* this */
unsigned int nsample_max; /* nsample should not be higher than
* this */
- unsigned int nsample_switch; /* Use FIFO or bypass FIFO switch */
+ enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */
unsigned int nsample; /* burst read amount from host */
+ u8 burst_bclkdiv; /* BCLK divider value in burst mode */
enum dac33_state state;
};
@@ -297,28 +314,49 @@
dac33_write(codec, DAC33_PWR_CTRL, reg);
}
-static void dac33_hard_power(struct snd_soc_codec *codec, int power)
+static int dac33_hard_power(struct snd_soc_codec *codec, int power)
{
struct tlv320dac33_priv *dac33 = codec->private_data;
+ int ret;
mutex_lock(&dac33->mutex);
if (power) {
- if (dac33->power_gpio >= 0) {
- gpio_set_value(dac33->power_gpio, 1);
- dac33->chip_power = 1;
- /* Restore registers */
- dac33_restore_regs(codec);
+ ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n", ret);
+ goto exit;
}
+
+ if (dac33->power_gpio >= 0)
+ gpio_set_value(dac33->power_gpio, 1);
+
+ dac33->chip_power = 1;
+
+ /* Restore registers */
+ dac33_restore_regs(codec);
+
dac33_soft_power(codec, 1);
} else {
dac33_soft_power(codec, 0);
- if (dac33->power_gpio >= 0) {
+ if (dac33->power_gpio >= 0)
gpio_set_value(dac33->power_gpio, 0);
- dac33->chip_power = 0;
- }
- }
- mutex_unlock(&dac33->mutex);
+ ret = regulator_bulk_disable(ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to disable supplies: %d\n", ret);
+ goto exit;
+ }
+
+ dac33->chip_power = 0;
+ }
+
+exit:
+ mutex_unlock(&dac33->mutex);
+ return ret;
}
static int dac33_get_nsample(struct snd_kcontrol *kcontrol,
@@ -351,39 +389,48 @@
return ret;
}
-static int dac33_get_nsample_switch(struct snd_kcontrol *kcontrol,
+static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct tlv320dac33_priv *dac33 = codec->private_data;
- ucontrol->value.integer.value[0] = dac33->nsample_switch;
+ ucontrol->value.integer.value[0] = dac33->fifo_mode;
return 0;
}
-static int dac33_set_nsample_switch(struct snd_kcontrol *kcontrol,
+static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct tlv320dac33_priv *dac33 = codec->private_data;
int ret = 0;
- if (dac33->nsample_switch == ucontrol->value.integer.value[0])
+ if (dac33->fifo_mode == ucontrol->value.integer.value[0])
return 0;
/* Do not allow changes while stream is running*/
if (codec->active)
return -EPERM;
if (ucontrol->value.integer.value[0] < 0 ||
- ucontrol->value.integer.value[0] > 1)
+ ucontrol->value.integer.value[0] >= DAC33_FIFO_LAST_MODE)
ret = -EINVAL;
else
- dac33->nsample_switch = ucontrol->value.integer.value[0];
+ dac33->fifo_mode = ucontrol->value.integer.value[0];
return ret;
}
+/* Codec operation modes */
+static const char *dac33_fifo_mode_texts[] = {
+ "Bypass", "Mode 1", "Mode 7"
+};
+
+static const struct soc_enum dac33_fifo_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(dac33_fifo_mode_texts),
+ dac33_fifo_mode_texts);
+
/*
* DACL/R digital volume control:
* from 0 dB to -63.5 in 0.5 dB steps
@@ -406,8 +453,8 @@
static const struct snd_kcontrol_new dac33_nsample_snd_controls[] = {
SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0,
dac33_get_nsample, dac33_set_nsample),
- SOC_SINGLE_EXT("nSample Switch", 0, 0, 1, 0,
- dac33_get_nsample_switch, dac33_set_nsample_switch),
+ SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum,
+ dac33_get_fifo_mode, dac33_set_fifo_mode),
};
/* Analog bypass */
@@ -469,6 +516,8 @@
static int dac33_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
+ int ret;
+
switch (level) {
case SND_SOC_BIAS_ON:
dac33_soft_power(codec, 1);
@@ -476,12 +525,19 @@
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
- if (codec->bias_level == SND_SOC_BIAS_OFF)
- dac33_hard_power(codec, 1);
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = dac33_hard_power(codec, 1);
+ if (ret != 0)
+ return ret;
+ }
+
dac33_soft_power(codec, 0);
break;
case SND_SOC_BIAS_OFF:
- dac33_hard_power(codec, 0);
+ ret = dac33_hard_power(codec, 0);
+ if (ret != 0)
+ return ret;
+
break;
}
codec->bias_level = level;
@@ -489,6 +545,51 @@
return 0;
}
+static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33)
+{
+ struct snd_soc_codec *codec;
+
+ codec = &dac33->codec;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write16(codec, DAC33_NSAMPLE_MSB,
+ DAC33_THRREG(dac33->nsample));
+ dac33_write16(codec, DAC33_PREFILL_MSB,
+ DAC33_THRREG(dac33->alarm_threshold));
+ break;
+ case DAC33_FIFO_MODE7:
+ dac33_write16(codec, DAC33_PREFILL_MSB,
+ DAC33_THRREG(10));
+ break;
+ default:
+ dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+ dac33->fifo_mode);
+ break;
+ }
+}
+
+static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33)
+{
+ struct snd_soc_codec *codec;
+
+ codec = &dac33->codec;
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write16(codec, DAC33_NSAMPLE_MSB,
+ DAC33_THRREG(dac33->nsample));
+ break;
+ case DAC33_FIFO_MODE7:
+ /* At the moment we are not using interrupts in mode7 */
+ break;
+ default:
+ dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+ dac33->fifo_mode);
+ break;
+ }
+}
+
static void dac33_work(struct work_struct *work)
{
struct snd_soc_codec *codec;
@@ -502,14 +603,10 @@
switch (dac33->state) {
case DAC33_PREFILL:
dac33->state = DAC33_PLAYBACK;
- dac33_write16(codec, DAC33_NSAMPLE_MSB,
- DAC33_THRREG(dac33->nsample));
- dac33_write16(codec, DAC33_PREFILL_MSB,
- DAC33_THRREG(dac33->alarm_threshold));
+ dac33_prefill_handler(dac33);
break;
case DAC33_PLAYBACK:
- dac33_write16(codec, DAC33_NSAMPLE_MSB,
- DAC33_THRREG(dac33->nsample));
+ dac33_playback_handler(dac33);
break;
case DAC33_IDLE:
break;
@@ -547,7 +644,7 @@
unsigned int pwr_ctrl;
/* Stop pending workqueue */
- if (dac33->nsample_switch)
+ if (dac33->fifo_mode)
cancel_work_sync(&dac33->work);
mutex_lock(&dac33->mutex);
@@ -619,7 +716,7 @@
struct snd_soc_codec *codec = socdev->card->codec;
struct tlv320dac33_priv *dac33 = codec->private_data;
unsigned int oscset, ratioset, pwr_ctrl, reg_tmp;
- u8 aictrl_a, fifoctrl_a;
+ u8 aictrl_a, aictrl_b, fifoctrl_a;
switch (substream->runtime->rate) {
case 44100:
@@ -675,7 +772,8 @@
dac33_oscwait(codec);
- if (dac33->nsample_switch) {
+ if (dac33->fifo_mode) {
+ /* Generic for all FIFO modes */
/* 50-51 : ASRC Control registers */
dac33_write(codec, DAC33_ASRC_CTRL_A, (1 << 4)); /* div=2 */
dac33_write(codec, DAC33_ASRC_CTRL_B, 1); /* ??? */
@@ -685,38 +783,101 @@
/* Set interrupts to high active */
dac33_write(codec, DAC33_INTP_CTRL_A, DAC33_INTPM_AHIGH);
-
- dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
- DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
- dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
} else {
+ /* FIFO bypass mode */
/* 50-51 : ASRC Control registers */
dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCBYP);
dac33_write(codec, DAC33_ASRC_CTRL_B, 0); /* ??? */
}
- if (dac33->nsample_switch)
+ /* Interrupt behaviour configuration */
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
+ DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
+ dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
+ break;
+ case DAC33_FIFO_MODE7:
+ /* Disable all interrupts */
+ dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0);
+ break;
+ default:
+ /* in FIFO bypass mode, the interrupts are not used */
+ break;
+ }
+
+ aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
+ /*
+ * For mode1:
+ * Disable the FIFO bypass (Enable the use of FIFO)
+ * Select nSample mode
+ * BCLK is only running when data is needed by DAC33
+ */
fifoctrl_a &= ~DAC33_FBYPAS;
- else
+ fifoctrl_a &= ~DAC33_FAUTO;
+ aictrl_b &= ~DAC33_BCLKON;
+ break;
+ case DAC33_FIFO_MODE7:
+ /*
+ * For mode1:
+ * Disable the FIFO bypass (Enable the use of FIFO)
+ * Select Threshold mode
+ * BCLK is only running when data is needed by DAC33
+ */
+ fifoctrl_a &= ~DAC33_FBYPAS;
+ fifoctrl_a |= DAC33_FAUTO;
+ aictrl_b &= ~DAC33_BCLKON;
+ break;
+ default:
+ /*
+ * For FIFO bypass mode:
+ * Enable the FIFO bypass (Disable the FIFO use)
+ * Set the BCLK as continous
+ */
fifoctrl_a |= DAC33_FBYPAS;
+ aictrl_b |= DAC33_BCLKON;
+ break;
+ }
+
dac33_write(codec, DAC33_FIFO_CTRL_A, fifoctrl_a);
-
dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
- reg_tmp = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
- if (dac33->nsample_switch)
- reg_tmp &= ~DAC33_BCLKON;
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
+
+ /*
+ * BCLK divide ratio
+ * 0: 1.5
+ * 1: 1
+ * 2: 2
+ * ...
+ * 254: 254
+ * 255: 255
+ */
+ if (dac33->fifo_mode)
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C,
+ dac33->burst_bclkdiv);
else
- reg_tmp |= DAC33_BCLKON;
- dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, reg_tmp);
+ dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
- if (dac33->nsample_switch) {
- /* 20: BCLK divide ratio */
- dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 3);
-
+ switch (dac33->fifo_mode) {
+ case DAC33_FIFO_MODE1:
dac33_write16(codec, DAC33_ATHR_MSB,
DAC33_THRREG(dac33->alarm_threshold));
- } else {
- dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
+ break;
+ case DAC33_FIFO_MODE7:
+ /*
+ * Configure the threshold levels, and leave 10 sample space
+ * at the bottom, and also at the top of the FIFO
+ */
+ dac33_write16(codec, DAC33_UTHR_MSB,
+ DAC33_THRREG(DAC33_BUFFER_SIZE_SAMPLES - 10));
+ dac33_write16(codec, DAC33_LTHR_MSB,
+ DAC33_THRREG(10));
+ break;
+ default:
+ break;
}
mutex_unlock(&dac33->mutex);
@@ -789,7 +950,7 @@
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (dac33->nsample_switch) {
+ if (dac33->fifo_mode) {
dac33->state = DAC33_PREFILL;
queue_work(dac33->dac33_wq, &dac33->work);
}
@@ -797,7 +958,7 @@
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (dac33->nsample_switch) {
+ if (dac33->fifo_mode) {
dac33->state = DAC33_FLUSH;
queue_work(dac33->dac33_wq, &dac33->work);
}
@@ -843,6 +1004,7 @@
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
+ struct tlv320dac33_priv *dac33 = codec->private_data;
u8 aictrl_a, aictrl_b;
aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
@@ -855,7 +1017,11 @@
break;
case SND_SOC_DAIFMT_CBS_CFS:
/* Codec Slave */
- aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
+ if (dac33->fifo_mode) {
+ dev_err(codec->dev, "FIFO mode requires master mode\n");
+ return -EINVAL;
+ } else
+ aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
break;
default:
return -EINVAL;
@@ -959,6 +1125,9 @@
/* power on device */
dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ /* Bias level configuration has enabled regulator an extra time */
+ regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+
return 0;
pcm_err:
@@ -1033,13 +1202,13 @@
};
EXPORT_SYMBOL_GPL(dac33_dai);
-static int dac33_i2c_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int __devinit dac33_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
{
struct tlv320dac33_platform_data *pdata;
struct tlv320dac33_priv *dac33;
struct snd_soc_codec *codec;
- int ret = 0;
+ int ret, i;
if (client->dev.platform_data == NULL) {
dev_err(&client->dev, "Platform data not set\n");
@@ -1080,10 +1249,11 @@
i2c_set_clientdata(client, dac33);
dac33->power_gpio = pdata->power_gpio;
+ dac33->burst_bclkdiv = pdata->burst_bclkdiv;
dac33->irq = client->irq;
dac33->nsample = NSAMPLE_MAX;
/* Disable FIFO use by default */
- dac33->nsample_switch = 0;
+ dac33->fifo_mode = DAC33_FIFO_BYPASS;
tlv320dac33_codec = codec;
@@ -1130,6 +1300,24 @@
}
}
+ for (i = 0; i < ARRAY_SIZE(dac33->supplies); i++)
+ dac33->supplies[i].supply = dac33_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
+ dac33->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_enable;
+ }
+
ret = snd_soc_register_codec(codec);
if (ret != 0) {
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
@@ -1149,6 +1337,10 @@
return ret;
error_codec:
+ regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+err_enable:
+ regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+err_get:
if (dac33->irq >= 0) {
free_irq(dac33->irq, &dac33->codec);
destroy_workqueue(dac33->dac33_wq);
@@ -1165,7 +1357,7 @@
return ret;
}
-static int dac33_i2c_remove(struct i2c_client *client)
+static int __devexit dac33_i2c_remove(struct i2c_client *client)
{
struct tlv320dac33_priv *dac33;
@@ -1177,6 +1369,8 @@
if (dac33->irq >= 0)
free_irq(dac33->irq, &dac33->codec);
+ regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+
destroy_workqueue(dac33->dac33_wq);
snd_soc_unregister_dai(&dac33_dai);
snd_soc_unregister_codec(&dac33->codec);
diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c
index 6b650c1..958d49c 100644
--- a/sound/soc/codecs/tpa6130a2.c
+++ b/sound/soc/codecs/tpa6130a2.c
@@ -25,6 +25,7 @@
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
#include <sound/tpa6130a2-plat.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
@@ -34,10 +35,22 @@
static struct i2c_client *tpa6130a2_client;
+#define TPA6130A2_NUM_SUPPLIES 2
+static const char *tpa6130a2_supply_names[TPA6130A2_NUM_SUPPLIES] = {
+ "CPVSS",
+ "Vdd",
+};
+
+static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = {
+ "HPVdd",
+ "AVdd",
+};
+
/* This struct is used to save the context */
struct tpa6130a2_data {
struct mutex mutex;
unsigned char regs[TPA6130A2_CACHEREGNUM];
+ struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES];
int power_gpio;
unsigned char power_state;
};
@@ -106,10 +119,11 @@
tpa6130a2_i2c_write(i, data->regs[i]);
}
-static void tpa6130a2_power(int power)
+static int tpa6130a2_power(int power)
{
struct tpa6130a2_data *data;
u8 val;
+ int ret;
BUG_ON(tpa6130a2_client == NULL);
data = i2c_get_clientdata(tpa6130a2_client);
@@ -117,11 +131,20 @@
mutex_lock(&data->mutex);
if (power) {
/* Power on */
- if (data->power_gpio >= 0) {
+ if (data->power_gpio >= 0)
gpio_set_value(data->power_gpio, 1);
- data->power_state = 1;
- tpa6130a2_initialize();
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (ret != 0) {
+ dev_err(&tpa6130a2_client->dev,
+ "Failed to enable supplies: %d\n", ret);
+ goto exit;
}
+
+ data->power_state = 1;
+ tpa6130a2_initialize();
+
/* Clear SWS */
val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
val &= ~TPA6130A2_SWS;
@@ -131,13 +154,25 @@
val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
val |= TPA6130A2_SWS;
tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+
/* Power off */
- if (data->power_gpio >= 0) {
+ if (data->power_gpio >= 0)
gpio_set_value(data->power_gpio, 0);
- data->power_state = 0;
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (ret != 0) {
+ dev_err(&tpa6130a2_client->dev,
+ "Failed to disable supplies: %d\n", ret);
+ goto exit;
}
+
+ data->power_state = 0;
}
+
+exit:
mutex_unlock(&data->mutex);
+ return ret;
}
static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol,
@@ -237,12 +272,8 @@
*/
static void tpa6130a2_channel_enable(u8 channel, int enable)
{
- struct tpa6130a2_data *data;
u8 val;
- BUG_ON(tpa6130a2_client == NULL);
- data = i2c_get_clientdata(tpa6130a2_client);
-
if (enable) {
/* Enable channel */
/* Enable amplifier */
@@ -299,15 +330,17 @@
static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
+ int ret = 0;
+
switch (event) {
case SND_SOC_DAPM_POST_PMU:
- tpa6130a2_power(1);
+ ret = tpa6130a2_power(1);
break;
case SND_SOC_DAPM_POST_PMD:
- tpa6130a2_power(0);
+ ret = tpa6130a2_power(0);
break;
}
- return 0;
+ return ret;
}
static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = {
@@ -346,13 +379,13 @@
}
EXPORT_SYMBOL_GPL(tpa6130a2_add_controls);
-static int tpa6130a2_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int __devinit tpa6130a2_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
{
struct device *dev;
struct tpa6130a2_data *data;
struct tpa6130a2_platform_data *pdata;
- int ret;
+ int i, ret;
dev = &client->dev;
@@ -387,15 +420,38 @@
if (ret < 0) {
dev_err(dev, "Failed to request power GPIO (%d)\n",
data->power_gpio);
- goto fail;
+ goto err_gpio;
}
gpio_direction_output(data->power_gpio, 0);
- } else {
- data->power_state = 1;
- tpa6130a2_initialize();
}
- tpa6130a2_power(1);
+ switch (pdata->id) {
+ case TPA6130A2:
+ for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
+ data->supplies[i].supply = tpa6130a2_supply_names[i];
+ break;
+ case TPA6140A2:
+ for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
+ data->supplies[i].supply = tpa6140a2_supply_names[i];;
+ break;
+ default:
+ dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n",
+ pdata->id);
+ for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
+ data->supplies[i].supply = tpa6130a2_supply_names[i];
+ }
+
+ ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to request supplies: %d\n", ret);
+ goto err_regulator;
+ }
+
+ ret = tpa6130a2_power(1);
+ if (ret != 0)
+ goto err_power;
+
/* Read version */
ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
@@ -404,10 +460,18 @@
dev_warn(dev, "UNTESTED version detected (%d)\n", ret);
/* Disable the chip */
- tpa6130a2_power(0);
+ ret = tpa6130a2_power(0);
+ if (ret != 0)
+ goto err_power;
return 0;
-fail:
+
+err_power:
+ regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
+err_regulator:
+ if (data->power_gpio >= 0)
+ gpio_free(data->power_gpio);
+err_gpio:
kfree(data);
i2c_set_clientdata(tpa6130a2_client, NULL);
tpa6130a2_client = NULL;
@@ -415,7 +479,7 @@
return ret;
}
-static int tpa6130a2_remove(struct i2c_client *client)
+static int __devexit tpa6130a2_remove(struct i2c_client *client)
{
struct tpa6130a2_data *data = i2c_get_clientdata(client);
@@ -423,6 +487,9 @@
if (data->power_gpio >= 0)
gpio_free(data->power_gpio);
+
+ regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies);
+
kfree(data);
tpa6130a2_client = NULL;
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 2a27f7b..e0106a5 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -64,12 +64,12 @@
0x00, /* REG_VRXPGA (0x14) */
0x00, /* REG_VSTPGA (0x15) */
0x00, /* REG_VRX2ARXPGA (0x16) */
- 0x0c, /* REG_AVDAC_CTL (0x17) */
+ 0x00, /* REG_AVDAC_CTL (0x17) */
0x00, /* REG_ARX2VTXPGA (0x18) */
0x00, /* REG_ARXL1_APGA_CTL (0x19) */
0x00, /* REG_ARXR1_APGA_CTL (0x1A) */
- 0x4b, /* REG_ARXL2_APGA_CTL (0x1B) */
- 0x4b, /* REG_ARXR2_APGA_CTL (0x1C) */
+ 0x4a, /* REG_ARXL2_APGA_CTL (0x1B) */
+ 0x4a, /* REG_ARXR2_APGA_CTL (0x1C) */
0x00, /* REG_ATX2ARXPGA (0x1D) */
0x00, /* REG_BT_IF (0x1E) */
0x00, /* REG_BTPGA (0x1F) */
@@ -99,7 +99,7 @@
0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */
0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */
0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */
- 0x16, /* REG_APLL_CTL (0x3A) */
+ 0x06, /* REG_APLL_CTL (0x3A) */
0x00, /* REG_DTMF_CTL (0x3B) */
0x00, /* REG_DTMF_PGA_CTL2 (0x3C) */
0x00, /* REG_DTMF_PGA_CTL1 (0x3D) */
@@ -2192,7 +2192,7 @@
codec->write = twl4030_write;
codec->set_bias_level = twl4030_set_bias_level;
codec->dai = twl4030_dai;
- codec->num_dai = ARRAY_SIZE(twl4030_dai),
+ codec->num_dai = ARRAY_SIZE(twl4030_dai);
codec->reg_cache_size = sizeof(twl4030_reg);
codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg),
GFP_KERNEL);
diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h
index dd6396e..f206d24 100644
--- a/sound/soc/codecs/twl4030.h
+++ b/sound/soc/codecs/twl4030.h
@@ -25,7 +25,7 @@
/* Register descriptions are here */
#include <linux/mfd/twl4030-codec.h>
-/* Sgadow register used by the audio driver */
+/* Shadow register used by the audio driver */
#define TWL4030_REG_SW_SHADOW 0x4A
#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1)
diff --git a/sound/soc/codecs/wm8727.c b/sound/soc/codecs/wm8727.c
index d8ffbd6..63a254e 100644
--- a/sound/soc/codecs/wm8727.c
+++ b/sound/soc/codecs/wm8727.c
@@ -44,23 +44,16 @@
};
EXPORT_SYMBOL_GPL(wm8727_dai);
+static struct snd_soc_codec *wm8727_codec;
+
static int wm8727_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 = "WM8727";
- codec->owner = THIS_MODULE;
- codec->dai = &wm8727_dai;
- codec->num_dai = 1;
- socdev->card->codec = codec;
- INIT_LIST_HEAD(&codec->dapm_widgets);
- INIT_LIST_HEAD(&codec->dapm_paths);
+ BUG_ON(!wm8727_codec);
+
+ socdev->card->codec = wm8727_codec;
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
@@ -80,12 +73,9 @@
static int wm8727_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->card->codec;
- if (codec == NULL)
- return 0;
snd_soc_free_pcms(socdev);
- kfree(codec);
+
return 0;
}
@@ -98,13 +88,55 @@
static __devinit int wm8727_platform_probe(struct platform_device *pdev)
{
+ struct snd_soc_codec *codec;
+ int ret;
+
+ if (wm8727_codec) {
+ dev_err(&pdev->dev, "Another WM8727 is registered\n");
+ return -EBUSY;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+ wm8727_codec = codec;
+
+ platform_set_drvdata(pdev, codec);
+
+ mutex_init(&codec->mutex);
+ codec->dev = &pdev->dev;
+ codec->name = "WM8727";
+ codec->owner = THIS_MODULE;
+ codec->dai = &wm8727_dai;
+ codec->num_dai = 1;
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
wm8727_dai.dev = &pdev->dev;
- return snd_soc_register_dai(&wm8727_dai);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to register CODEC: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_register_dai(&wm8727_dai);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to register DAI: %d\n", ret);
+ goto err_codec;
+ }
+
+err_codec:
+ snd_soc_unregister_codec(codec);
+err:
+ kfree(codec);
+ return ret;
}
static int __devexit wm8727_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_dai(&wm8727_dai);
+ snd_soc_unregister_codec(platform_get_drvdata(pdev));
return 0;
}
diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c
index 3a49781..5a2619d 100644
--- a/sound/soc/codecs/wm8731.c
+++ b/sound/soc/codecs/wm8731.c
@@ -456,6 +456,9 @@
/* Sync reg_cache with the hardware */
for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
+ if (cache[i] == wm8731_reg[i])
+ continue;
+
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
data[1] = cache[i] & 0x00ff;
codec->hw_write(codec->control_data, data, 2);
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
index d6850da..c2444e7 100644
--- a/sound/soc/codecs/wm8753.c
+++ b/sound/soc/codecs/wm8753.c
@@ -1507,10 +1507,6 @@
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->card->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;
}
@@ -1523,10 +1519,6 @@
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)
diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c
index ab2c0da..44e7d9d 100644
--- a/sound/soc/codecs/wm8776.c
+++ b/sound/soc/codecs/wm8776.c
@@ -406,6 +406,8 @@
/* Sync reg_cache with the hardware */
for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) {
+ if (cache[i] == wm8776_reg[i])
+ continue;
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
data[1] = cache[i] & 0x00ff;
codec->hw_write(codec->control_data, data, 2);
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
new file mode 100644
index 0000000..992a7f2
--- /dev/null
+++ b/sound/soc/codecs/wm8904.c
@@ -0,0 +1,2556 @@
+/*
+ * wm8904.c -- WM8904 ALSA SoC Audio driver
+ *
+ * Copyright 2009 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.
+ */
+
+#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/regulator/consumer.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 <sound/wm8904.h>
+
+#include "wm8904.h"
+
+static struct snd_soc_codec *wm8904_codec;
+struct snd_soc_codec_device soc_codec_dev_wm8904;
+
+#define WM8904_NUM_DCS_CHANNELS 4
+
+#define WM8904_NUM_SUPPLIES 5
+static const char *wm8904_supply_names[WM8904_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "AVDD",
+ "CPVDD",
+ "MICVDD",
+};
+
+/* codec private data */
+struct wm8904_priv {
+ struct snd_soc_codec codec;
+ u16 reg_cache[WM8904_MAX_REGISTER + 1];
+
+ struct regulator_bulk_data supplies[WM8904_NUM_SUPPLIES];
+
+ struct wm8904_pdata *pdata;
+
+ int deemph;
+
+ /* Platform provided DRC configuration */
+ const char **drc_texts;
+ int drc_cfg;
+ struct soc_enum drc_enum;
+
+ /* Platform provided ReTune mobile configuration */
+ int num_retune_mobile_texts;
+ const char **retune_mobile_texts;
+ int retune_mobile_cfg;
+ struct soc_enum retune_mobile_enum;
+
+ /* FLL setup */
+ int fll_src;
+ int fll_fref;
+ int fll_fout;
+
+ /* Clocking configuration */
+ unsigned int mclk_rate;
+ int sysclk_src;
+ unsigned int sysclk_rate;
+
+ int tdm_width;
+ int tdm_slots;
+ int bclk;
+ int fs;
+
+ /* DC servo configuration - cached offset values */
+ int dcs_state[WM8904_NUM_DCS_CHANNELS];
+};
+
+static const u16 wm8904_reg[WM8904_MAX_REGISTER + 1] = {
+ 0x8904, /* R0 - SW Reset and ID */
+ 0x0000, /* R1 - Revision */
+ 0x0000, /* R2 */
+ 0x0000, /* R3 */
+ 0x0018, /* R4 - Bias Control 0 */
+ 0x0000, /* R5 - VMID Control 0 */
+ 0x0000, /* R6 - Mic Bias Control 0 */
+ 0x0000, /* R7 - Mic Bias Control 1 */
+ 0x0001, /* R8 - Analogue DAC 0 */
+ 0x9696, /* R9 - mic Filter Control */
+ 0x0001, /* R10 - Analogue ADC 0 */
+ 0x0000, /* R11 */
+ 0x0000, /* R12 - Power Management 0 */
+ 0x0000, /* R13 */
+ 0x0000, /* R14 - Power Management 2 */
+ 0x0000, /* R15 - Power Management 3 */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - Power Management 6 */
+ 0x0000, /* R19 */
+ 0x945E, /* R20 - Clock Rates 0 */
+ 0x0C05, /* R21 - Clock Rates 1 */
+ 0x0006, /* R22 - Clock Rates 2 */
+ 0x0000, /* R23 */
+ 0x0050, /* R24 - Audio Interface 0 */
+ 0x000A, /* R25 - Audio Interface 1 */
+ 0x00E4, /* R26 - Audio Interface 2 */
+ 0x0040, /* 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 */
+ 0x0008, /* R33 - DAC Digital 1 */
+ 0x0000, /* R34 */
+ 0x0000, /* R35 */
+ 0x00C0, /* R36 - ADC Digital Volume Left */
+ 0x00C0, /* R37 - ADC Digital Volume Right */
+ 0x0010, /* R38 - ADC Digital 0 */
+ 0x0000, /* R39 - Digital Microphone 0 */
+ 0x01AF, /* R40 - DRC 0 */
+ 0x3248, /* R41 - DRC 1 */
+ 0x0000, /* 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 */
+ 0x0000, /* R50 */
+ 0x0000, /* R51 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 */
+ 0x0000, /* R55 */
+ 0x0000, /* R56 */
+ 0x002D, /* R57 - Analogue OUT1 Left */
+ 0x002D, /* R58 - Analogue OUT1 Right */
+ 0x0039, /* R59 - Analogue OUT2 Left */
+ 0x0039, /* R60 - Analogue OUT2 Right */
+ 0x0000, /* R61 - Analogue OUT12 ZC */
+ 0x0000, /* R62 */
+ 0x0000, /* R63 */
+ 0x0000, /* R64 */
+ 0x0000, /* R65 */
+ 0x0000, /* R66 */
+ 0x0000, /* R67 - DC Servo 0 */
+ 0x0000, /* R68 - DC Servo 1 */
+ 0xAAAA, /* R69 - DC Servo 2 */
+ 0x0000, /* R70 */
+ 0xAAAA, /* R71 - DC Servo 4 */
+ 0xAAAA, /* R72 - DC Servo 5 */
+ 0x0000, /* R73 - DC Servo 6 */
+ 0x0000, /* R74 - DC Servo 7 */
+ 0x0000, /* R75 - DC Servo 8 */
+ 0x0000, /* R76 - DC Servo 9 */
+ 0x0000, /* R77 - DC Servo Readback 0 */
+ 0x0000, /* R78 */
+ 0x0000, /* R79 */
+ 0x0000, /* R80 */
+ 0x0000, /* R81 */
+ 0x0000, /* R82 */
+ 0x0000, /* R83 */
+ 0x0000, /* R84 */
+ 0x0000, /* R85 */
+ 0x0000, /* R86 */
+ 0x0000, /* R87 */
+ 0x0000, /* R88 */
+ 0x0000, /* R89 */
+ 0x0000, /* R90 - Analogue HP 0 */
+ 0x0000, /* R91 */
+ 0x0000, /* R92 */
+ 0x0000, /* R93 */
+ 0x0000, /* R94 - Analogue Lineout 0 */
+ 0x0000, /* R95 */
+ 0x0000, /* R96 */
+ 0x0000, /* R97 */
+ 0x0000, /* R98 - Charge Pump 0 */
+ 0x0000, /* R99 */
+ 0x0000, /* R100 */
+ 0x0000, /* R101 */
+ 0x0000, /* R102 */
+ 0x0000, /* R103 */
+ 0x0004, /* R104 - Class W 0 */
+ 0x0000, /* R105 */
+ 0x0000, /* 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 */
+ 0x0000, /* R115 */
+ 0x0000, /* R116 - FLL Control 1 */
+ 0x0007, /* R117 - FLL Control 2 */
+ 0x0000, /* R118 - FLL Control 3 */
+ 0x2EE0, /* R119 - FLL Control 4 */
+ 0x0004, /* R120 - FLL Control 5 */
+ 0x0014, /* R121 - GPIO Control 1 */
+ 0x0010, /* R122 - GPIO Control 2 */
+ 0x0010, /* R123 - GPIO Control 3 */
+ 0x0000, /* R124 - GPIO Control 4 */
+ 0x0000, /* R125 */
+ 0x0000, /* R126 - Digital Pulls */
+ 0x0000, /* R127 - Interrupt Status */
+ 0xFFFF, /* R128 - Interrupt Status Mask */
+ 0x0000, /* R129 - Interrupt Polarity */
+ 0x0000, /* R130 - Interrupt Debounce */
+ 0x0000, /* R131 */
+ 0x0000, /* R132 */
+ 0x0000, /* R133 */
+ 0x0000, /* R134 - EQ1 */
+ 0x000C, /* R135 - EQ2 */
+ 0x000C, /* R136 - EQ3 */
+ 0x000C, /* R137 - EQ4 */
+ 0x000C, /* R138 - EQ5 */
+ 0x000C, /* R139 - EQ6 */
+ 0x0FCA, /* R140 - EQ7 */
+ 0x0400, /* R141 - EQ8 */
+ 0x00D8, /* R142 - EQ9 */
+ 0x1EB5, /* R143 - EQ10 */
+ 0xF145, /* R144 - EQ11 */
+ 0x0B75, /* R145 - EQ12 */
+ 0x01C5, /* R146 - EQ13 */
+ 0x1C58, /* R147 - EQ14 */
+ 0xF373, /* R148 - EQ15 */
+ 0x0A54, /* R149 - EQ16 */
+ 0x0558, /* R150 - EQ17 */
+ 0x168E, /* R151 - EQ18 */
+ 0xF829, /* R152 - EQ19 */
+ 0x07AD, /* R153 - EQ20 */
+ 0x1103, /* R154 - EQ21 */
+ 0x0564, /* R155 - EQ22 */
+ 0x0559, /* R156 - EQ23 */
+ 0x4000, /* R157 - EQ24 */
+ 0x0000, /* R158 */
+ 0x0000, /* R159 */
+ 0x0000, /* R160 */
+ 0x0000, /* R161 - Control Interface Test 1 */
+ 0x0000, /* R162 */
+ 0x0000, /* R163 */
+ 0x0000, /* R164 */
+ 0x0000, /* R165 */
+ 0x0000, /* R166 */
+ 0x0000, /* R167 */
+ 0x0000, /* R168 */
+ 0x0000, /* R169 */
+ 0x0000, /* R170 */
+ 0x0000, /* R171 */
+ 0x0000, /* R172 */
+ 0x0000, /* R173 */
+ 0x0000, /* R174 */
+ 0x0000, /* R175 */
+ 0x0000, /* R176 */
+ 0x0000, /* R177 */
+ 0x0000, /* R178 */
+ 0x0000, /* R179 */
+ 0x0000, /* R180 */
+ 0x0000, /* R181 */
+ 0x0000, /* R182 */
+ 0x0000, /* R183 */
+ 0x0000, /* R184 */
+ 0x0000, /* R185 */
+ 0x0000, /* R186 */
+ 0x0000, /* R187 */
+ 0x0000, /* R188 */
+ 0x0000, /* R189 */
+ 0x0000, /* R190 */
+ 0x0000, /* R191 */
+ 0x0000, /* R192 */
+ 0x0000, /* R193 */
+ 0x0000, /* R194 */
+ 0x0000, /* R195 */
+ 0x0000, /* R196 */
+ 0x0000, /* R197 */
+ 0x0000, /* R198 */
+ 0x0000, /* R199 */
+ 0x0000, /* R200 */
+ 0x0000, /* R201 */
+ 0x0000, /* R202 */
+ 0x0000, /* R203 */
+ 0x0000, /* R204 - Analogue Output Bias 0 */
+ 0x0000, /* R205 */
+ 0x0000, /* R206 */
+ 0x0000, /* R207 */
+ 0x0000, /* R208 */
+ 0x0000, /* R209 */
+ 0x0000, /* R210 */
+ 0x0000, /* R211 */
+ 0x0000, /* R212 */
+ 0x0000, /* R213 */
+ 0x0000, /* R214 */
+ 0x0000, /* R215 */
+ 0x0000, /* R216 */
+ 0x0000, /* R217 */
+ 0x0000, /* R218 */
+ 0x0000, /* R219 */
+ 0x0000, /* R220 */
+ 0x0000, /* R221 */
+ 0x0000, /* R222 */
+ 0x0000, /* R223 */
+ 0x0000, /* R224 */
+ 0x0000, /* R225 */
+ 0x0000, /* R226 */
+ 0x0000, /* R227 */
+ 0x0000, /* R228 */
+ 0x0000, /* R229 */
+ 0x0000, /* R230 */
+ 0x0000, /* R231 */
+ 0x0000, /* R232 */
+ 0x0000, /* R233 */
+ 0x0000, /* R234 */
+ 0x0000, /* R235 */
+ 0x0000, /* R236 */
+ 0x0000, /* R237 */
+ 0x0000, /* R238 */
+ 0x0000, /* R239 */
+ 0x0000, /* R240 */
+ 0x0000, /* R241 */
+ 0x0000, /* R242 */
+ 0x0000, /* R243 */
+ 0x0000, /* R244 */
+ 0x0000, /* R245 */
+ 0x0000, /* R246 */
+ 0x0000, /* R247 - FLL NCO Test 0 */
+ 0x0019, /* R248 - FLL NCO Test 1 */
+};
+
+static struct {
+ int readable;
+ int writable;
+ int vol;
+} wm8904_access[] = {
+ { 0xFFFF, 0xFFFF, 1 }, /* R0 - SW Reset and ID */
+ { 0x0000, 0x0000, 0 }, /* R1 - Revision */
+ { 0x0000, 0x0000, 0 }, /* R2 */
+ { 0x0000, 0x0000, 0 }, /* R3 */
+ { 0x001F, 0x001F, 0 }, /* R4 - Bias Control 0 */
+ { 0x0047, 0x0047, 0 }, /* R5 - VMID Control 0 */
+ { 0x007F, 0x007F, 0 }, /* R6 - Mic Bias Control 0 */
+ { 0xC007, 0xC007, 0 }, /* R7 - Mic Bias Control 1 */
+ { 0x001E, 0x001E, 0 }, /* R8 - Analogue DAC 0 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R9 - mic Filter Control */
+ { 0x0001, 0x0001, 0 }, /* R10 - Analogue ADC 0 */
+ { 0x0000, 0x0000, 0 }, /* R11 */
+ { 0x0003, 0x0003, 0 }, /* R12 - Power Management 0 */
+ { 0x0000, 0x0000, 0 }, /* R13 */
+ { 0x0003, 0x0003, 0 }, /* R14 - Power Management 2 */
+ { 0x0003, 0x0003, 0 }, /* R15 - Power Management 3 */
+ { 0x0000, 0x0000, 0 }, /* R16 */
+ { 0x0000, 0x0000, 0 }, /* R17 */
+ { 0x000F, 0x000F, 0 }, /* R18 - Power Management 6 */
+ { 0x0000, 0x0000, 0 }, /* R19 */
+ { 0x7001, 0x7001, 0 }, /* R20 - Clock Rates 0 */
+ { 0x3C07, 0x3C07, 0 }, /* R21 - Clock Rates 1 */
+ { 0xD00F, 0xD00F, 0 }, /* R22 - Clock Rates 2 */
+ { 0x0000, 0x0000, 0 }, /* R23 */
+ { 0x1FFF, 0x1FFF, 0 }, /* R24 - Audio Interface 0 */
+ { 0x3DDF, 0x3DDF, 0 }, /* R25 - Audio Interface 1 */
+ { 0x0F1F, 0x0F1F, 0 }, /* R26 - Audio Interface 2 */
+ { 0x0FFF, 0x0FFF, 0 }, /* R27 - Audio Interface 3 */
+ { 0x0000, 0x0000, 0 }, /* R28 */
+ { 0x0000, 0x0000, 0 }, /* R29 */
+ { 0x00FF, 0x01FF, 0 }, /* R30 - DAC Digital Volume Left */
+ { 0x00FF, 0x01FF, 0 }, /* R31 - DAC Digital Volume Right */
+ { 0x0FFF, 0x0FFF, 0 }, /* R32 - DAC Digital 0 */
+ { 0x1E4E, 0x1E4E, 0 }, /* R33 - DAC Digital 1 */
+ { 0x0000, 0x0000, 0 }, /* R34 */
+ { 0x0000, 0x0000, 0 }, /* R35 */
+ { 0x00FF, 0x01FF, 0 }, /* R36 - ADC Digital Volume Left */
+ { 0x00FF, 0x01FF, 0 }, /* R37 - ADC Digital Volume Right */
+ { 0x0073, 0x0073, 0 }, /* R38 - ADC Digital 0 */
+ { 0x1800, 0x1800, 0 }, /* R39 - Digital Microphone 0 */
+ { 0xDFEF, 0xDFEF, 0 }, /* R40 - DRC 0 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R41 - DRC 1 */
+ { 0x003F, 0x003F, 0 }, /* R42 - DRC 2 */
+ { 0x07FF, 0x07FF, 0 }, /* R43 - DRC 3 */
+ { 0x009F, 0x009F, 0 }, /* R44 - Analogue Left Input 0 */
+ { 0x009F, 0x009F, 0 }, /* R45 - Analogue Right Input 0 */
+ { 0x007F, 0x007F, 0 }, /* R46 - Analogue Left Input 1 */
+ { 0x007F, 0x007F, 0 }, /* R47 - Analogue Right Input 1 */
+ { 0x0000, 0x0000, 0 }, /* R48 */
+ { 0x0000, 0x0000, 0 }, /* R49 */
+ { 0x0000, 0x0000, 0 }, /* R50 */
+ { 0x0000, 0x0000, 0 }, /* R51 */
+ { 0x0000, 0x0000, 0 }, /* R52 */
+ { 0x0000, 0x0000, 0 }, /* R53 */
+ { 0x0000, 0x0000, 0 }, /* R54 */
+ { 0x0000, 0x0000, 0 }, /* R55 */
+ { 0x0000, 0x0000, 0 }, /* R56 */
+ { 0x017F, 0x01FF, 0 }, /* R57 - Analogue OUT1 Left */
+ { 0x017F, 0x01FF, 0 }, /* R58 - Analogue OUT1 Right */
+ { 0x017F, 0x01FF, 0 }, /* R59 - Analogue OUT2 Left */
+ { 0x017F, 0x01FF, 0 }, /* R60 - Analogue OUT2 Right */
+ { 0x000F, 0x000F, 0 }, /* R61 - Analogue OUT12 ZC */
+ { 0x0000, 0x0000, 0 }, /* R62 */
+ { 0x0000, 0x0000, 0 }, /* R63 */
+ { 0x0000, 0x0000, 0 }, /* R64 */
+ { 0x0000, 0x0000, 0 }, /* R65 */
+ { 0x0000, 0x0000, 0 }, /* R66 */
+ { 0x000F, 0x000F, 0 }, /* R67 - DC Servo 0 */
+ { 0xFFFF, 0xFFFF, 1 }, /* R68 - DC Servo 1 */
+ { 0x0F0F, 0x0F0F, 0 }, /* R69 - DC Servo 2 */
+ { 0x0000, 0x0000, 0 }, /* R70 */
+ { 0x007F, 0x007F, 0 }, /* R71 - DC Servo 4 */
+ { 0x007F, 0x007F, 0 }, /* R72 - DC Servo 5 */
+ { 0x00FF, 0x00FF, 1 }, /* R73 - DC Servo 6 */
+ { 0x00FF, 0x00FF, 1 }, /* R74 - DC Servo 7 */
+ { 0x00FF, 0x00FF, 1 }, /* R75 - DC Servo 8 */
+ { 0x00FF, 0x00FF, 1 }, /* R76 - DC Servo 9 */
+ { 0x0FFF, 0x0000, 1 }, /* R77 - DC Servo Readback 0 */
+ { 0x0000, 0x0000, 0 }, /* R78 */
+ { 0x0000, 0x0000, 0 }, /* R79 */
+ { 0x0000, 0x0000, 0 }, /* R80 */
+ { 0x0000, 0x0000, 0 }, /* R81 */
+ { 0x0000, 0x0000, 0 }, /* R82 */
+ { 0x0000, 0x0000, 0 }, /* R83 */
+ { 0x0000, 0x0000, 0 }, /* R84 */
+ { 0x0000, 0x0000, 0 }, /* R85 */
+ { 0x0000, 0x0000, 0 }, /* R86 */
+ { 0x0000, 0x0000, 0 }, /* R87 */
+ { 0x0000, 0x0000, 0 }, /* R88 */
+ { 0x0000, 0x0000, 0 }, /* R89 */
+ { 0x00FF, 0x00FF, 0 }, /* R90 - Analogue HP 0 */
+ { 0x0000, 0x0000, 0 }, /* R91 */
+ { 0x0000, 0x0000, 0 }, /* R92 */
+ { 0x0000, 0x0000, 0 }, /* R93 */
+ { 0x00FF, 0x00FF, 0 }, /* R94 - Analogue Lineout 0 */
+ { 0x0000, 0x0000, 0 }, /* R95 */
+ { 0x0000, 0x0000, 0 }, /* R96 */
+ { 0x0000, 0x0000, 0 }, /* R97 */
+ { 0x0001, 0x0001, 0 }, /* R98 - Charge Pump 0 */
+ { 0x0000, 0x0000, 0 }, /* R99 */
+ { 0x0000, 0x0000, 0 }, /* R100 */
+ { 0x0000, 0x0000, 0 }, /* R101 */
+ { 0x0000, 0x0000, 0 }, /* R102 */
+ { 0x0000, 0x0000, 0 }, /* R103 */
+ { 0x0001, 0x0001, 0 }, /* R104 - Class W 0 */
+ { 0x0000, 0x0000, 0 }, /* R105 */
+ { 0x0000, 0x0000, 0 }, /* R106 */
+ { 0x0000, 0x0000, 0 }, /* R107 */
+ { 0x011F, 0x011F, 0 }, /* R108 - Write Sequencer 0 */
+ { 0x7FFF, 0x7FFF, 0 }, /* R109 - Write Sequencer 1 */
+ { 0x4FFF, 0x4FFF, 0 }, /* R110 - Write Sequencer 2 */
+ { 0x003F, 0x033F, 0 }, /* R111 - Write Sequencer 3 */
+ { 0x03F1, 0x0000, 0 }, /* R112 - Write Sequencer 4 */
+ { 0x0000, 0x0000, 0 }, /* R113 */
+ { 0x0000, 0x0000, 0 }, /* R114 */
+ { 0x0000, 0x0000, 0 }, /* R115 */
+ { 0x0007, 0x0007, 0 }, /* R116 - FLL Control 1 */
+ { 0x3F77, 0x3F77, 0 }, /* R117 - FLL Control 2 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R118 - FLL Control 3 */
+ { 0x7FEF, 0x7FEF, 0 }, /* R119 - FLL Control 4 */
+ { 0x001B, 0x001B, 0 }, /* R120 - FLL Control 5 */
+ { 0x003F, 0x003F, 0 }, /* R121 - GPIO Control 1 */
+ { 0x003F, 0x003F, 0 }, /* R122 - GPIO Control 2 */
+ { 0x003F, 0x003F, 0 }, /* R123 - GPIO Control 3 */
+ { 0x038F, 0x038F, 0 }, /* R124 - GPIO Control 4 */
+ { 0x0000, 0x0000, 0 }, /* R125 */
+ { 0x00FF, 0x00FF, 0 }, /* R126 - Digital Pulls */
+ { 0x07FF, 0x03FF, 1 }, /* R127 - Interrupt Status */
+ { 0x03FF, 0x03FF, 0 }, /* R128 - Interrupt Status Mask */
+ { 0x03FF, 0x03FF, 0 }, /* R129 - Interrupt Polarity */
+ { 0x03FF, 0x03FF, 0 }, /* R130 - Interrupt Debounce */
+ { 0x0000, 0x0000, 0 }, /* R131 */
+ { 0x0000, 0x0000, 0 }, /* R132 */
+ { 0x0000, 0x0000, 0 }, /* R133 */
+ { 0x0001, 0x0001, 0 }, /* R134 - EQ1 */
+ { 0x001F, 0x001F, 0 }, /* R135 - EQ2 */
+ { 0x001F, 0x001F, 0 }, /* R136 - EQ3 */
+ { 0x001F, 0x001F, 0 }, /* R137 - EQ4 */
+ { 0x001F, 0x001F, 0 }, /* R138 - EQ5 */
+ { 0x001F, 0x001F, 0 }, /* R139 - EQ6 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R140 - EQ7 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R141 - EQ8 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R142 - EQ9 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R143 - EQ10 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R144 - EQ11 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R145 - EQ12 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R146 - EQ13 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R147 - EQ14 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R148 - EQ15 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R149 - EQ16 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R150 - EQ17 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R151wm8523_dai - EQ18 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R152 - EQ19 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R153 - EQ20 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R154 - EQ21 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R155 - EQ22 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R156 - EQ23 */
+ { 0xFFFF, 0xFFFF, 0 }, /* R157 - EQ24 */
+ { 0x0000, 0x0000, 0 }, /* R158 */
+ { 0x0000, 0x0000, 0 }, /* R159 */
+ { 0x0000, 0x0000, 0 }, /* R160 */
+ { 0x0002, 0x0002, 0 }, /* R161 - Control Interface Test 1 */
+ { 0x0000, 0x0000, 0 }, /* R162 */
+ { 0x0000, 0x0000, 0 }, /* R163 */
+ { 0x0000, 0x0000, 0 }, /* R164 */
+ { 0x0000, 0x0000, 0 }, /* R165 */
+ { 0x0000, 0x0000, 0 }, /* R166 */
+ { 0x0000, 0x0000, 0 }, /* R167 */
+ { 0x0000, 0x0000, 0 }, /* R168 */
+ { 0x0000, 0x0000, 0 }, /* R169 */
+ { 0x0000, 0x0000, 0 }, /* R170 */
+ { 0x0000, 0x0000, 0 }, /* R171 */
+ { 0x0000, 0x0000, 0 }, /* R172 */
+ { 0x0000, 0x0000, 0 }, /* R173 */
+ { 0x0000, 0x0000, 0 }, /* R174 */
+ { 0x0000, 0x0000, 0 }, /* R175 */
+ { 0x0000, 0x0000, 0 }, /* R176 */
+ { 0x0000, 0x0000, 0 }, /* R177 */
+ { 0x0000, 0x0000, 0 }, /* R178 */
+ { 0x0000, 0x0000, 0 }, /* R179 */
+ { 0x0000, 0x0000, 0 }, /* R180 */
+ { 0x0000, 0x0000, 0 }, /* R181 */
+ { 0x0000, 0x0000, 0 }, /* R182 */
+ { 0x0000, 0x0000, 0 }, /* R183 */
+ { 0x0000, 0x0000, 0 }, /* R184 */
+ { 0x0000, 0x0000, 0 }, /* R185 */
+ { 0x0000, 0x0000, 0 }, /* R186 */
+ { 0x0000, 0x0000, 0 }, /* R187 */
+ { 0x0000, 0x0000, 0 }, /* R188 */
+ { 0x0000, 0x0000, 0 }, /* R189 */
+ { 0x0000, 0x0000, 0 }, /* R190 */
+ { 0x0000, 0x0000, 0 }, /* R191 */
+ { 0x0000, 0x0000, 0 }, /* R192 */
+ { 0x0000, 0x0000, 0 }, /* R193 */
+ { 0x0000, 0x0000, 0 }, /* R194 */
+ { 0x0000, 0x0000, 0 }, /* R195 */
+ { 0x0000, 0x0000, 0 }, /* R196 */
+ { 0x0000, 0x0000, 0 }, /* R197 */
+ { 0x0000, 0x0000, 0 }, /* R198 */
+ { 0x0000, 0x0000, 0 }, /* R199 */
+ { 0x0000, 0x0000, 0 }, /* R200 */
+ { 0x0000, 0x0000, 0 }, /* R201 */
+ { 0x0000, 0x0000, 0 }, /* R202 */
+ { 0x0000, 0x0000, 0 }, /* R203 */
+ { 0x0070, 0x0070, 0 }, /* R204 - Analogue Output Bias 0 */
+ { 0x0000, 0x0000, 0 }, /* R205 */
+ { 0x0000, 0x0000, 0 }, /* R206 */
+ { 0x0000, 0x0000, 0 }, /* R207 */
+ { 0x0000, 0x0000, 0 }, /* R208 */
+ { 0x0000, 0x0000, 0 }, /* R209 */
+ { 0x0000, 0x0000, 0 }, /* R210 */
+ { 0x0000, 0x0000, 0 }, /* R211 */
+ { 0x0000, 0x0000, 0 }, /* R212 */
+ { 0x0000, 0x0000, 0 }, /* R213 */
+ { 0x0000, 0x0000, 0 }, /* R214 */
+ { 0x0000, 0x0000, 0 }, /* R215 */
+ { 0x0000, 0x0000, 0 }, /* R216 */
+ { 0x0000, 0x0000, 0 }, /* R217 */
+ { 0x0000, 0x0000, 0 }, /* R218 */
+ { 0x0000, 0x0000, 0 }, /* R219 */
+ { 0x0000, 0x0000, 0 }, /* R220 */
+ { 0x0000, 0x0000, 0 }, /* R221 */
+ { 0x0000, 0x0000, 0 }, /* R222 */
+ { 0x0000, 0x0000, 0 }, /* R223 */
+ { 0x0000, 0x0000, 0 }, /* R224 */
+ { 0x0000, 0x0000, 0 }, /* R225 */
+ { 0x0000, 0x0000, 0 }, /* R226 */
+ { 0x0000, 0x0000, 0 }, /* R227 */
+ { 0x0000, 0x0000, 0 }, /* R228 */
+ { 0x0000, 0x0000, 0 }, /* R229 */
+ { 0x0000, 0x0000, 0 }, /* R230 */
+ { 0x0000, 0x0000, 0 }, /* R231 */
+ { 0x0000, 0x0000, 0 }, /* R232 */
+ { 0x0000, 0x0000, 0 }, /* R233 */
+ { 0x0000, 0x0000, 0 }, /* R234 */
+ { 0x0000, 0x0000, 0 }, /* R235 */
+ { 0x0000, 0x0000, 0 }, /* R236 */
+ { 0x0000, 0x0000, 0 }, /* R237 */
+ { 0x0000, 0x0000, 0 }, /* R238 */
+ { 0x0000, 0x0000, 0 }, /* R239 */
+ { 0x0000, 0x0000, 0 }, /* R240 */
+ { 0x0000, 0x0000, 0 }, /* R241 */
+ { 0x0000, 0x0000, 0 }, /* R242 */
+ { 0x0000, 0x0000, 0 }, /* R243 */
+ { 0x0000, 0x0000, 0 }, /* R244 */
+ { 0x0000, 0x0000, 0 }, /* R245 */
+ { 0x0000, 0x0000, 0 }, /* R246 */
+ { 0x0001, 0x0001, 0 }, /* R247 - FLL NCO Test 0 */
+ { 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */
+};
+
+static int wm8904_volatile_register(unsigned int reg)
+{
+ return wm8904_access[reg].vol;
+}
+
+static int wm8904_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8904_SW_RESET_AND_ID, 0);
+}
+
+static int wm8904_configure_clocking(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = codec->private_data;
+ unsigned int clock0, clock2, rate;
+
+ /* Gate the clock while we're updating to avoid misclocking */
+ clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_SYSCLK_SRC, 0);
+
+ /* This should be done on init() for bypass paths */
+ switch (wm8904->sysclk_src) {
+ case WM8904_CLK_MCLK:
+ dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8904->mclk_rate);
+
+ clock2 &= ~WM8904_SYSCLK_SRC;
+ rate = wm8904->mclk_rate;
+
+ /* Ensure the FLL is stopped */
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+ break;
+
+ case WM8904_CLK_FLL:
+ dev_dbg(codec->dev, "Using %dHz FLL clock\n",
+ wm8904->fll_fout);
+
+ clock2 |= WM8904_SYSCLK_SRC;
+ rate = wm8904->fll_fout;
+ break;
+
+ default:
+ dev_err(codec->dev, "System clock not configured\n");
+ return -EINVAL;
+ }
+
+ /* SYSCLK shouldn't be over 13.5MHz */
+ if (rate > 13500000) {
+ clock0 = WM8904_MCLK_DIV;
+ wm8904->sysclk_rate = rate / 2;
+ } else {
+ clock0 = 0;
+ wm8904->sysclk_rate = rate;
+ }
+
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV,
+ clock0);
+
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2);
+
+ dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate);
+
+ return 0;
+}
+
+static void wm8904_set_drc(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = codec->private_data;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int save, i;
+
+ /* Save any enables; the configuration should clear them. */
+ save = snd_soc_read(codec, WM8904_DRC_0);
+
+ for (i = 0; i < WM8904_DRC_REGS; i++)
+ snd_soc_update_bits(codec, WM8904_DRC_0 + i, 0xffff,
+ pdata->drc_cfgs[wm8904->drc_cfg].regs[i]);
+
+ /* Reenable the DRC */
+ snd_soc_update_bits(codec, WM8904_DRC_0,
+ WM8904_DRC_ENA | WM8904_DRC_DAC_PATH, save);
+}
+
+static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int value = ucontrol->value.integer.value[0];
+
+ if (value >= pdata->num_drc_cfgs)
+ return -EINVAL;
+
+ wm8904->drc_cfg = value;
+
+ wm8904_set_drc(codec);
+
+ return 0;
+}
+
+static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+
+ ucontrol->value.enumerated.item[0] = wm8904->drc_cfg;
+
+ return 0;
+}
+
+static void wm8904_set_retune_mobile(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = codec->private_data;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int best, best_val, save, i, cfg;
+
+ if (!pdata || !wm8904->num_retune_mobile_texts)
+ return;
+
+ /* Find the version of the currently selected configuration
+ * with the nearest sample rate. */
+ cfg = wm8904->retune_mobile_cfg;
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8904->retune_mobile_texts[cfg]) == 0 &&
+ abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8904->fs) < best_val) {
+ best = i;
+ best_val = abs(pdata->retune_mobile_cfgs[i].rate
+ - wm8904->fs);
+ }
+ }
+
+ dev_dbg(codec->dev, "ReTune Mobile %s/%dHz for %dHz sample rate\n",
+ pdata->retune_mobile_cfgs[best].name,
+ pdata->retune_mobile_cfgs[best].rate,
+ wm8904->fs);
+
+ /* The EQ will be disabled while reconfiguring it, remember the
+ * current configuration.
+ */
+ save = snd_soc_read(codec, WM8904_EQ1);
+
+ for (i = 0; i < WM8904_EQ_REGS; i++)
+ snd_soc_update_bits(codec, WM8904_EQ1 + i, 0xffff,
+ pdata->retune_mobile_cfgs[best].regs[i]);
+
+ snd_soc_update_bits(codec, WM8904_EQ1, WM8904_EQ_ENA, save);
+}
+
+static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int value = ucontrol->value.integer.value[0];
+
+ if (value >= pdata->num_retune_mobile_cfgs)
+ return -EINVAL;
+
+ wm8904->retune_mobile_cfg = value;
+
+ wm8904_set_retune_mobile(codec);
+
+ return 0;
+}
+
+static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+
+ ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg;
+
+ return 0;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8904_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8904->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8904->fs) <
+ abs(deemph_settings[best] - wm8904->fs))
+ best = i;
+ }
+
+ val = best << WM8904_DEEMPH_SHIFT;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+ WM8904_DEEMPH_MASK, val);
+}
+
+static int wm8904_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+
+ return wm8904->deemph;
+}
+
+static int wm8904_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8904->deemph = deemph;
+
+ return wm8904_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+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(sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const char *input_mode_text[] = {
+ "Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum lin_mode =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rin_mode =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *hpf_mode_text[] = {
+ "Hi-fi", "Voice 1", "Voice 2", "Voice 3"
+};
+
+static const struct soc_enum hpf_mode =
+ SOC_ENUM_SINGLE(WM8904_ADC_DIGITAL_0, 5, 4, hpf_mode_text);
+
+static const struct snd_kcontrol_new wm8904_adc_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8904_ADC_DIGITAL_VOLUME_LEFT,
+ WM8904_ADC_DIGITAL_VOLUME_RIGHT, 1, 119, 0, digital_tlv),
+
+SOC_ENUM("Left Caputure Mode", lin_mode),
+SOC_ENUM("Right Capture Mode", rin_mode),
+
+/* No TLV since it depends on mode */
+SOC_DOUBLE_R("Capture Volume", WM8904_ANALOGUE_LEFT_INPUT_0,
+ WM8904_ANALOGUE_RIGHT_INPUT_0, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", WM8904_ANALOGUE_LEFT_INPUT_0,
+ WM8904_ANALOGUE_RIGHT_INPUT_0, 7, 1, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8904_ADC_DIGITAL_0, 4, 1, 0),
+SOC_ENUM("High Pass Filter Mode", hpf_mode),
+
+SOC_SINGLE("ADC 128x OSR Switch", WM8904_ANALOGUE_ADC_0, 0, 1, 0),
+};
+
+static const char *drc_path_text[] = {
+ "ADC", "DAC"
+};
+
+static const struct soc_enum drc_path =
+ SOC_ENUM_SINGLE(WM8904_DRC_0, 14, 2, drc_path_text);
+
+static const struct snd_kcontrol_new wm8904_dac_snd_controls[] = {
+SOC_SINGLE_TLV("Digital Playback Boost Volume",
+ WM8904_AUDIO_INTERFACE_0, 9, 3, 0, dac_boost_tlv),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8904_DAC_DIGITAL_VOLUME_LEFT,
+ WM8904_DAC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Switch", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_ANALOGUE_OUT1_RIGHT, 6, 1, 0),
+
+SOC_DOUBLE_R_TLV("Line Output Volume", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Line Output Switch", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Line Output ZC Switch", WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_ANALOGUE_OUT2_RIGHT, 6, 1, 0),
+
+SOC_SINGLE("EQ Switch", WM8904_EQ1, 0, 1, 0),
+SOC_SINGLE("DRC Switch", WM8904_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Path", drc_path),
+SOC_SINGLE("DAC OSRx2 Switch", WM8904_DAC_DIGITAL_1, 6, 1, 0),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8904_get_deemph, wm8904_put_deemph),
+};
+
+static const struct snd_kcontrol_new wm8904_snd_controls[] = {
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8904_DAC_DIGITAL_0, 4, 8, 15, 0,
+ sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new wm8904_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM8904_EQ2, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM8904_EQ3, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM8904_EQ4, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM8904_EQ5, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM8904_EQ6, 0, 24, 0, eq_tlv),
+};
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ BUG_ON(event != SND_SOC_DAPM_POST_PMU);
+
+ /* Maximum startup time */
+ udelay(500);
+
+ return 0;
+}
+
+static int sysclk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8904_priv *wm8904 = codec->private_data;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* If we're using the FLL then we only start it when
+ * required; we assume that the configuration has been
+ * done previously and all we need to do is kick it
+ * off.
+ */
+ switch (wm8904->sysclk_src) {
+ case WM8904_CLK_FLL:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA,
+ WM8904_FLL_OSC_ENA);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA,
+ WM8904_FLL_ENA);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int out_pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int reg, val;
+ int dcs_mask;
+ int dcs_l, dcs_r;
+ int dcs_l_reg, dcs_r_reg;
+ int timeout;
+
+ /* This code is shared between HP and LINEOUT; we do all our
+ * power management in stereo pairs to avoid latency issues so
+ * we reuse shift to identify which rather than strcmp() the
+ * name. */
+ reg = w->shift;
+
+ switch (reg) {
+ case WM8904_ANALOGUE_HP_0:
+ dcs_mask = WM8904_DCS_ENA_CHAN_0 | WM8904_DCS_ENA_CHAN_1;
+ dcs_r_reg = WM8904_DC_SERVO_8;
+ dcs_l_reg = WM8904_DC_SERVO_9;
+ dcs_l = 0;
+ dcs_r = 1;
+ break;
+ case WM8904_ANALOGUE_LINEOUT_0:
+ dcs_mask = WM8904_DCS_ENA_CHAN_2 | WM8904_DCS_ENA_CHAN_3;
+ dcs_r_reg = WM8904_DC_SERVO_6;
+ dcs_l_reg = WM8904_DC_SERVO_7;
+ dcs_l = 2;
+ dcs_r = 3;
+ break;
+ default:
+ BUG();
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* Power on the amplifier */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA | WM8904_HPR_ENA,
+ WM8904_HPL_ENA | WM8904_HPR_ENA);
+
+ /* Enable the first stage */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY,
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY);
+
+ /* Power up the DC servo */
+ snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+ dcs_mask, dcs_mask);
+
+ /* Either calibrate the DC servo or restore cached state
+ * if we have that.
+ */
+ if (wm8904->dcs_state[dcs_l] || wm8904->dcs_state[dcs_r]) {
+ dev_dbg(codec->dev, "Restoring DC servo state\n");
+
+ snd_soc_write(codec, dcs_l_reg,
+ wm8904->dcs_state[dcs_l]);
+ snd_soc_write(codec, dcs_r_reg,
+ wm8904->dcs_state[dcs_r]);
+
+ snd_soc_write(codec, WM8904_DC_SERVO_1, dcs_mask);
+
+ timeout = 20;
+ } else {
+ dev_dbg(codec->dev, "Calibrating DC servo\n");
+
+ snd_soc_write(codec, WM8904_DC_SERVO_1,
+ dcs_mask << WM8904_DCS_TRIG_STARTUP_0_SHIFT);
+
+ timeout = 500;
+ }
+
+ /* Wait for DC servo to complete */
+ dcs_mask <<= WM8904_DCS_CAL_COMPLETE_SHIFT;
+ do {
+ val = snd_soc_read(codec, WM8904_DC_SERVO_READBACK_0);
+ if ((val & dcs_mask) == dcs_mask)
+ break;
+
+ msleep(1);
+ } while (--timeout);
+
+ if ((val & dcs_mask) != dcs_mask)
+ dev_warn(codec->dev, "DC servo timed out\n");
+ else
+ dev_dbg(codec->dev, "DC servo ready\n");
+
+ /* Enable the output stage */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP);
+
+ /* Unshort the output itself */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT);
+
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Short the output */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_RMV_SHORT |
+ WM8904_HPR_RMV_SHORT, 0);
+
+ /* Cache the DC servo configuration; this will be
+ * invalidated if we change the configuration. */
+ wm8904->dcs_state[dcs_l] = snd_soc_read(codec, dcs_l_reg);
+ wm8904->dcs_state[dcs_r] = snd_soc_read(codec, dcs_r_reg);
+
+ snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+ dcs_mask, 0);
+
+ /* Disable the amplifier input and output stages */
+ snd_soc_update_bits(codec, reg,
+ WM8904_HPL_ENA | WM8904_HPR_ENA |
+ WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY |
+ WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+ 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const char *lin_text[] = {
+ "IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum lin_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 2, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_mux =
+ SOC_DAPM_ENUM("Left Capture Mux", lin_enum);
+
+static const struct soc_enum lin_inv_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 4, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_inv_mux =
+ SOC_DAPM_ENUM("Left Capture Inveting Mux", lin_inv_enum);
+
+static const char *rin_text[] = {
+ "IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rin_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 2, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_mux =
+ SOC_DAPM_ENUM("Right Capture Mux", rin_enum);
+
+static const struct soc_enum rin_inv_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 4, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_inv_mux =
+ SOC_DAPM_ENUM("Right Capture Inveting Mux", rin_inv_enum);
+
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum aifoutl_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutl_mux =
+ SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum);
+
+static const struct soc_enum aifoutr_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutr_mux =
+ SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum);
+
+static const struct soc_enum aifinl_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinl_mux =
+ SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
+
+static const struct soc_enum aifinr_enum =
+ SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinr_mux =
+ SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);
+
+static const struct snd_soc_dapm_widget wm8904_core_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8904_CLOCK_RATES_2, 2, 0, sysclk_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8904_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8904_CLOCK_RATES_2, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_adc_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_MICBIAS("MICBIAS", WM8904_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lin_mux),
+SND_SOC_DAPM_MUX("Left Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &lin_inv_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rin_mux),
+SND_SOC_DAPM_MUX("Right Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+ &rin_inv_mux),
+
+SND_SOC_DAPM_PGA("Left Capture PGA", WM8904_POWER_MANAGEMENT_0, 1, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture PGA", WM8904_POWER_MANAGEMENT_0, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8904_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8904_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux),
+SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_dac_dapm_widgets[] = {
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
+SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8904_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8904_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_SUPPLY("Charge pump", WM8904_CHARGE_PUMP_0, 0, 0, cp_event,
+ SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA("HPL PGA", WM8904_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("HPR PGA", WM8904_POWER_MANAGEMENT_2, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("LINEL PGA", WM8904_POWER_MANAGEMENT_3, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINER PGA", WM8904_POWER_MANAGEMENT_3, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, WM8904_ANALOGUE_HP_0,
+ 0, NULL, 0, out_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Line Output", SND_SOC_NOPM, WM8904_ANALOGUE_LINEOUT_0,
+ 0, NULL, 0, out_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+};
+
+static const char *out_mux_text[] = {
+ "DAC", "Bypass"
+};
+
+static const struct soc_enum hpl_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 3, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+ SOC_DAPM_ENUM("HPL Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 2, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+ SOC_DAPM_ENUM("HPR Mux", hpr_enum);
+
+static const struct soc_enum linel_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 1, 2, out_mux_text);
+
+static const struct snd_kcontrol_new linel_mux =
+ SOC_DAPM_ENUM("LINEL Mux", linel_enum);
+
+static const struct soc_enum liner_enum =
+ SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 0, 2, out_mux_text);
+
+static const struct snd_kcontrol_new liner_mux =
+ SOC_DAPM_ENUM("LINEL Mux", liner_enum);
+
+static const char *sidetone_text[] = {
+ "None", "Left", "Right"
+};
+
+static const struct soc_enum dacl_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacl_sidetone_mux =
+ SOC_DAPM_ENUM("Left Sidetone Mux", dacl_sidetone_enum);
+
+static const struct soc_enum dacr_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacr_sidetone_mux =
+ SOC_DAPM_ENUM("Right Sidetone Mux", dacr_sidetone_enum);
+
+static const struct snd_soc_dapm_widget wm8904_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("Class G", WM8904_CLASS_W_0, 0, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &dacl_sidetone_mux),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &dacr_sidetone_mux),
+
+SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+SND_SOC_DAPM_MUX("LINEL Mux", SND_SOC_NOPM, 0, 0, &linel_mux),
+SND_SOC_DAPM_MUX("LINER Mux", SND_SOC_NOPM, 0, 0, &liner_mux),
+};
+
+static const struct snd_soc_dapm_route core_intercon[] = {
+ { "CLK_DSP", NULL, "SYSCLK" },
+ { "TOCLK", NULL, "SYSCLK" },
+};
+
+static const struct snd_soc_dapm_route adc_intercon[] = {
+ { "Left Capture Mux", "IN1L", "IN1L" },
+ { "Left Capture Mux", "IN2L", "IN2L" },
+ { "Left Capture Mux", "IN3L", "IN3L" },
+
+ { "Left Capture Inverting Mux", "IN1L", "IN1L" },
+ { "Left Capture Inverting Mux", "IN2L", "IN2L" },
+ { "Left Capture Inverting Mux", "IN3L", "IN3L" },
+
+ { "Right Capture Mux", "IN1R", "IN1R" },
+ { "Right Capture Mux", "IN2R", "IN2R" },
+ { "Right Capture Mux", "IN3R", "IN3R" },
+
+ { "Right Capture Inverting Mux", "IN1R", "IN1R" },
+ { "Right Capture Inverting Mux", "IN2R", "IN2R" },
+ { "Right Capture Inverting Mux", "IN3R", "IN3R" },
+
+ { "Left Capture PGA", NULL, "Left Capture Mux" },
+ { "Left Capture PGA", NULL, "Left Capture Inverting Mux" },
+
+ { "Right Capture PGA", NULL, "Right Capture Mux" },
+ { "Right Capture PGA", NULL, "Right Capture Inverting Mux" },
+
+ { "AIFOUTL", "Left", "ADCL" },
+ { "AIFOUTL", "Right", "ADCR" },
+ { "AIFOUTR", "Left", "ADCL" },
+ { "AIFOUTR", "Right", "ADCR" },
+
+ { "ADCL", NULL, "CLK_DSP" },
+ { "ADCL", NULL, "Left Capture PGA" },
+
+ { "ADCR", NULL, "CLK_DSP" },
+ { "ADCR", NULL, "Right Capture PGA" },
+};
+
+static const struct snd_soc_dapm_route dac_intercon[] = {
+ { "DACL", "Right", "AIFINR" },
+ { "DACL", "Left", "AIFINL" },
+ { "DACL", NULL, "CLK_DSP" },
+
+ { "DACR", "Right", "AIFINR" },
+ { "DACR", "Left", "AIFINL" },
+ { "DACR", NULL, "CLK_DSP" },
+
+ { "Charge pump", NULL, "SYSCLK" },
+
+ { "Headphone Output", NULL, "HPL PGA" },
+ { "Headphone Output", NULL, "HPR PGA" },
+ { "Headphone Output", NULL, "Charge pump" },
+ { "Headphone Output", NULL, "TOCLK" },
+
+ { "Line Output", NULL, "LINEL PGA" },
+ { "Line Output", NULL, "LINER PGA" },
+ { "Line Output", NULL, "Charge pump" },
+ { "Line Output", NULL, "TOCLK" },
+
+ { "HPOUTL", NULL, "Headphone Output" },
+ { "HPOUTR", NULL, "Headphone Output" },
+
+ { "LINEOUTL", NULL, "Line Output" },
+ { "LINEOUTR", NULL, "Line Output" },
+};
+
+static const struct snd_soc_dapm_route wm8904_intercon[] = {
+ { "Left Sidetone", "Left", "ADCL" },
+ { "Left Sidetone", "Right", "ADCR" },
+ { "DACL", NULL, "Left Sidetone" },
+
+ { "Right Sidetone", "Left", "ADCL" },
+ { "Right Sidetone", "Right", "ADCR" },
+ { "DACR", NULL, "Right Sidetone" },
+
+ { "Left Bypass", NULL, "Class G" },
+ { "Left Bypass", NULL, "Left Capture PGA" },
+
+ { "Right Bypass", NULL, "Class G" },
+ { "Right Bypass", NULL, "Right Capture PGA" },
+
+ { "HPL Mux", "DAC", "DACL" },
+ { "HPL Mux", "Bypass", "Left Bypass" },
+
+ { "HPR Mux", "DAC", "DACR" },
+ { "HPR Mux", "Bypass", "Right Bypass" },
+
+ { "LINEL Mux", "DAC", "DACL" },
+ { "LINEL Mux", "Bypass", "Left Bypass" },
+
+ { "LINER Mux", "DAC", "DACR" },
+ { "LINER Mux", "Bypass", "Right Bypass" },
+
+ { "HPL PGA", NULL, "HPL Mux" },
+ { "HPR PGA", NULL, "HPR Mux" },
+
+ { "LINEL PGA", NULL, "LINEL Mux" },
+ { "LINER PGA", NULL, "LINER Mux" },
+};
+
+static int wm8904_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_add_controls(codec, wm8904_adc_snd_controls,
+ ARRAY_SIZE(wm8904_adc_snd_controls));
+ snd_soc_add_controls(codec, wm8904_dac_snd_controls,
+ ARRAY_SIZE(wm8904_dac_snd_controls));
+ snd_soc_add_controls(codec, wm8904_snd_controls,
+ ARRAY_SIZE(wm8904_snd_controls));
+
+ snd_soc_dapm_new_controls(codec, wm8904_core_dapm_widgets,
+ ARRAY_SIZE(wm8904_core_dapm_widgets));
+ snd_soc_dapm_new_controls(codec, wm8904_adc_dapm_widgets,
+ ARRAY_SIZE(wm8904_adc_dapm_widgets));
+ snd_soc_dapm_new_controls(codec, wm8904_dac_dapm_widgets,
+ ARRAY_SIZE(wm8904_dac_dapm_widgets));
+ snd_soc_dapm_new_controls(codec, wm8904_dapm_widgets,
+ ARRAY_SIZE(wm8904_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, core_intercon,
+ ARRAY_SIZE(core_intercon));
+ snd_soc_dapm_add_routes(codec, adc_intercon, ARRAY_SIZE(adc_intercon));
+ snd_soc_dapm_add_routes(codec, dac_intercon, ARRAY_SIZE(dac_intercon));
+ snd_soc_dapm_add_routes(codec, wm8904_intercon,
+ ARRAY_SIZE(wm8904_intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static struct {
+ int ratio;
+ unsigned int clk_sys_rate;
+} clk_sys_rates[] = {
+ { 64, 0 },
+ { 128, 1 },
+ { 192, 2 },
+ { 256, 3 },
+ { 384, 4 },
+ { 512, 5 },
+ { 786, 6 },
+ { 1024, 7 },
+ { 1408, 8 },
+ { 1536, 9 },
+};
+
+static struct {
+ int rate;
+ int sample_rate;
+} sample_rates[] = {
+ { 8000, 0 },
+ { 11025, 1 },
+ { 12000, 1 },
+ { 16000, 2 },
+ { 22050, 3 },
+ { 24000, 3 },
+ { 32000, 4 },
+ { 44100, 5 },
+ { 48000, 5 },
+};
+
+static struct {
+ int div; /* *10 due to .5s */
+ int bclk_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, 16 },
+ { 200, 17 },
+ { 320, 18 },
+ { 440, 19 },
+ { 480, 20 },
+};
+
+
+static int wm8904_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int ret, i, best, best_val, cur_val;
+ unsigned int aif1 = 0;
+ unsigned int aif2 = 0;
+ unsigned int aif3 = 0;
+ unsigned int clock1 = 0;
+ unsigned int dac_digital1 = 0;
+
+ /* What BCLK do we need? */
+ wm8904->fs = params_rate(params);
+ if (wm8904->tdm_slots) {
+ dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n",
+ wm8904->tdm_slots, wm8904->tdm_width);
+ wm8904->bclk = snd_soc_calc_bclk(wm8904->fs,
+ wm8904->tdm_width, 2,
+ wm8904->tdm_slots);
+ } else {
+ wm8904->bclk = snd_soc_params_to_bclk(params);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ aif1 |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ aif1 |= 0x80;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ aif1 |= 0xc0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8904->bclk);
+
+ ret = wm8904_configure_clocking(codec);
+ if (ret != 0)
+ return ret;
+
+ /* Select nearest CLK_SYS_RATE */
+ best = 0;
+ best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio)
+ - wm8904->fs);
+ for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+ cur_val = abs((wm8904->sysclk_rate /
+ clk_sys_rates[i].ratio) - wm8904->fs);;
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+ clk_sys_rates[best].ratio);
+ clock1 |= (clk_sys_rates[best].clk_sys_rate
+ << WM8904_CLK_SYS_RATE_SHIFT);
+
+ /* SAMPLE_RATE */
+ best = 0;
+ best_val = abs(wm8904->fs - sample_rates[0].rate);
+ for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+ /* Closest match */
+ cur_val = abs(wm8904->fs - sample_rates[i].rate);
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+ sample_rates[best].rate);
+ clock1 |= (sample_rates[best].sample_rate
+ << WM8904_SAMPLE_RATE_SHIFT);
+
+ /* Enable sloping stopband filter for low sample rates */
+ if (wm8904->fs <= 24000)
+ dac_digital1 |= WM8904_DAC_SB_FILT;
+
+ /* BCLK_DIV */
+ best = 0;
+ best_val = INT_MAX;
+ for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+ cur_val = ((wm8904->sysclk_rate * 10) / bclk_divs[i].div)
+ - wm8904->bclk;
+ if (cur_val < 0) /* Table is sorted */
+ break;
+ if (cur_val < best_val) {
+ best = i;
+ best_val = cur_val;
+ }
+ }
+ wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div;
+ dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+ bclk_divs[best].div, wm8904->bclk);
+ aif2 |= bclk_divs[best].bclk_div;
+
+ /* LRCLK is a simple fraction of BCLK */
+ dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8904->bclk / wm8904->fs);
+ aif3 |= wm8904->bclk / wm8904->fs;
+
+ /* Apply the settings */
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+ WM8904_DAC_SB_FILT, dac_digital1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIF_WL_MASK, aif1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_2,
+ WM8904_BCLK_DIV_MASK, aif2);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+ WM8904_LRCLK_RATE_MASK, aif3);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_1,
+ WM8904_SAMPLE_RATE_MASK |
+ WM8904_CLK_SYS_RATE_MASK, clock1);
+
+ /* Update filters for the new settings */
+ wm8904_set_retune_mobile(codec);
+ wm8904_set_deemph(codec);
+
+ return 0;
+}
+
+
+static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *priv = codec->private_data;
+
+ switch (clk_id) {
+ case WM8904_CLK_MCLK:
+ priv->sysclk_src = clk_id;
+ priv->mclk_rate = freq;
+ break;
+
+ case WM8904_CLK_FLL:
+ priv->sysclk_src = clk_id;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ wm8904_configure_clocking(codec);
+
+ return 0;
+}
+
+static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned int aif1 = 0;
+ unsigned int aif3 = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ aif3 |= WM8904_LRCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ aif1 |= WM8904_BCLK_DIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif1 |= WM8904_BCLK_DIR;
+ aif3 |= WM8904_LRCLK_DIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif1 |= WM8904_AIF_LRCLK_INV;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif1 |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif1 |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif1 |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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 |= WM8904_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 |= WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif1 |= WM8904_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif1 |= WM8904_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV |
+ WM8904_AIF_FMT_MASK | WM8904_BCLK_DIR, aif1);
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+ WM8904_LRCLK_DIR, aif3);
+
+ return 0;
+}
+
+
+static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int aif1 = 0;
+
+ /* Don't need to validate anything if we're turning off TDM */
+ if (slots == 0)
+ goto out;
+
+ /* Note that we allow configurations we can't handle ourselves -
+ * for example, we can generate clocks for slots 2 and up even if
+ * we can't use those slots ourselves.
+ */
+ aif1 |= WM8904_AIFADC_TDM | WM8904_AIFDAC_TDM;
+
+ switch (rx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif1 |= WM8904_AIFADC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ switch (tx_mask) {
+ case 3:
+ break;
+ case 0xc:
+ aif1 |= WM8904_AIFDAC_TDM_CHAN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+out:
+ wm8904->tdm_width = slot_width;
+ wm8904->tdm_slots = slots / 2;
+
+ snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+ WM8904_AIFADC_TDM | WM8904_AIFADC_TDM_CHAN |
+ WM8904_AIFDAC_TDM | WM8904_AIFDAC_TDM_CHAN, aif1);
+
+ return 0;
+}
+
+struct _fll_div {
+ u16 fll_fratio;
+ u16 fll_outdiv;
+ u16 fll_clk_ref_div;
+ 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 struct {
+ unsigned int min;
+ unsigned int max;
+ u16 fll_fratio;
+ int ratio;
+} fll_fratios[] = {
+ { 0, 64000, 4, 16 },
+ { 64000, 128000, 3, 8 },
+ { 128000, 256000, 2, 4 },
+ { 256000, 1000000, 1, 2 },
+ { 1000000, 13500000, 0, 1 },
+};
+
+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;
+ int i;
+
+ /* Fref must be <=13.5MHz */
+ div = 1;
+ fll_div->fll_clk_ref_div = 0;
+ while ((Fref / div) > 13500000) {
+ div *= 2;
+ fll_div->fll_clk_ref_div++;
+
+ if (div > 8) {
+ pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+ Fref);
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* Apply the division for our remaining calculations */
+ Fref /= div;
+
+ /* Fvco should be 90-100MHz; don't check the upper bound */
+ div = 4;
+ while (Fout * div < 90000000) {
+ div++;
+ if (div > 64) {
+ pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * div;
+ fll_div->fll_outdiv = div - 1;
+
+ pr_debug("Fvco=%dHz\n", target);
+
+ /* Find an appropraite FLL_FRATIO and factor it out of the target */
+ for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+ if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+ fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+ target /= fll_fratios[i].ratio;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(fll_fratios)) {
+ pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+ return -EINVAL;
+ }
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ fll_div->n = Ndiv;
+ Nmod = target % Fref;
+ pr_debug("Nmod=%d\n", Nmod);
+
+ /* 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;
+
+ pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+ fll_div->n, fll_div->k,
+ fll_div->fll_fratio, fll_div->fll_outdiv,
+ fll_div->fll_clk_ref_div);
+
+ return 0;
+}
+
+static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+ unsigned int Fref, unsigned int Fout)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8904_priv *wm8904 = codec->private_data;
+ struct _fll_div fll_div;
+ int ret, val;
+ int clock2, fll1;
+
+ /* Any change? */
+ if (source == wm8904->fll_src && Fref == wm8904->fll_fref &&
+ Fout == wm8904->fll_fout)
+ return 0;
+
+ clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+
+ if (Fout == 0) {
+ dev_dbg(codec->dev, "FLL disabled\n");
+
+ wm8904->fll_fref = 0;
+ wm8904->fll_fout = 0;
+
+ /* Gate SYSCLK to avoid glitches */
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+ goto out;
+ }
+
+ /* Validate the FLL ID */
+ switch (source) {
+ case WM8904_FLL_MCLK:
+ case WM8904_FLL_LRCLK:
+ case WM8904_FLL_BCLK:
+ ret = fll_factors(&fll_div, Fref, Fout);
+ if (ret != 0)
+ return ret;
+ break;
+
+ case WM8904_FLL_FREE_RUNNING:
+ dev_dbg(codec->dev, "Using free running FLL\n");
+ /* Force 12MHz and output/4 for now */
+ Fout = 12000000;
+ Fref = 12000000;
+
+ memset(&fll_div, 0, sizeof(fll_div));
+ fll_div.fll_outdiv = 3;
+ break;
+
+ default:
+ dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+ return -EINVAL;
+ }
+
+ /* Save current state then disable the FLL and SYSCLK to avoid
+ * misclocking */
+ fll1 = snd_soc_read(codec, WM8904_FLL_CONTROL_1);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, 0);
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+ /* Unlock forced oscilator control to switch it on/off */
+ snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+ WM8904_USER_KEY, WM8904_USER_KEY);
+
+ if (fll_id == WM8904_FLL_FREE_RUNNING) {
+ val = WM8904_FLL_FRC_NCO;
+ } else {
+ val = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO,
+ val);
+ snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+ WM8904_USER_KEY, 0);
+
+ switch (fll_id) {
+ case WM8904_FLL_MCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 0);
+ break;
+
+ case WM8904_FLL_LRCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 1);
+ break;
+
+ case WM8904_FLL_BCLK:
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, 2);
+ break;
+ }
+
+ if (fll_div.k)
+ val = WM8904_FLL_FRACN_ENA;
+ else
+ val = 0;
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_FRACN_ENA, val);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_2,
+ WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK,
+ (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) |
+ (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT));
+
+ snd_soc_write(codec, WM8904_FLL_CONTROL_3, fll_div.k);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK,
+ fll_div.n << WM8904_FLL_N_SHIFT);
+
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_DIV_MASK,
+ fll_div.fll_clk_ref_div
+ << WM8904_FLL_CLK_REF_DIV_SHIFT);
+
+ dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+ wm8904->fll_fref = Fref;
+ wm8904->fll_fout = Fout;
+ wm8904->fll_src = source;
+
+ /* Enable the FLL if it was previously active */
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA, fll1);
+ snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA, fll1);
+
+out:
+ /* Reenable SYSCLK if it was previously active */
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, clock2);
+
+ return 0;
+}
+
+static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val;
+
+ if (mute)
+ val = WM8904_DAC_MUTE;
+ else
+ val = 0;
+
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1, WM8904_DAC_MUTE, val);
+
+ return 0;
+}
+
+static int wm8904_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8904_priv *wm8904 = codec->private_data;
+ int ret, i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID resistance 2*50k */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK,
+ 0x1 << WM8904_VMID_RES_SHIFT);
+
+ /* Normal bias current */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_ISEL_MASK, 2 << WM8904_ISEL_SHIFT);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Sync back cached values if they're
+ * different from the hardware default.
+ */
+ for (i = 1; i < ARRAY_SIZE(wm8904->reg_cache); i++) {
+ if (!wm8904_access[i].writable)
+ continue;
+
+ if (wm8904->reg_cache[i] == wm8904_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, wm8904->reg_cache[i]);
+ }
+
+ /* Enable bias */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_BIAS_ENA, WM8904_BIAS_ENA);
+
+ /* Enable VMID, VMID buffering, 2*5k resistance */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_ENA |
+ WM8904_VMID_RES_MASK,
+ WM8904_VMID_ENA |
+ 0x3 << WM8904_VMID_RES_SHIFT);
+
+ /* Let VMID ramp */
+ msleep(1);
+ }
+
+ /* Maintain VMID with 2*250k */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK,
+ 0x2 << WM8904_VMID_RES_SHIFT);
+
+ /* Bias current *0.5 */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_ISEL_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Turn off VMID */
+ snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+ WM8904_VMID_RES_MASK | WM8904_VMID_ENA, 0);
+
+ /* Stop bias generation */
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_BIAS_ENA, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8904_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8904_dai_ops = {
+ .set_sysclk = wm8904_set_sysclk,
+ .set_fmt = wm8904_set_fmt,
+ .set_tdm_slot = wm8904_set_tdm_slot,
+ .set_pll = wm8904_set_fll,
+ .hw_params = wm8904_hw_params,
+ .digital_mute = wm8904_digital_mute,
+};
+
+struct snd_soc_dai wm8904_dai = {
+ .name = "WM8904",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8904_RATES,
+ .formats = WM8904_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8904_RATES,
+ .formats = WM8904_FORMATS,
+ },
+ .ops = &wm8904_dai_ops,
+ .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(wm8904_dai);
+
+#ifdef CONFIG_PM
+static int wm8904_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8904_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8904_suspend NULL
+#define wm8904_resume NULL
+#endif
+
+static void wm8904_handle_retune_mobile_pdata(struct wm8904_priv *wm8904)
+{
+ struct snd_soc_codec *codec = &wm8904->codec;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ struct snd_kcontrol_new control =
+ SOC_ENUM_EXT("EQ Mode",
+ wm8904->retune_mobile_enum,
+ wm8904_get_retune_mobile_enum,
+ wm8904_put_retune_mobile_enum);
+ int ret, i, j;
+ const char **t;
+
+ /* We need an array of texts for the enum API but the number
+ * of texts is likely to be less than the number of
+ * configurations due to the sample rate dependency of the
+ * configurations. */
+ wm8904->num_retune_mobile_texts = 0;
+ wm8904->retune_mobile_texts = NULL;
+ for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+ for (j = 0; j < wm8904->num_retune_mobile_texts; j++) {
+ if (strcmp(pdata->retune_mobile_cfgs[i].name,
+ wm8904->retune_mobile_texts[j]) == 0)
+ break;
+ }
+
+ if (j != wm8904->num_retune_mobile_texts)
+ continue;
+
+ /* Expand the array... */
+ t = krealloc(wm8904->retune_mobile_texts,
+ sizeof(char *) *
+ (wm8904->num_retune_mobile_texts + 1),
+ GFP_KERNEL);
+ if (t == NULL)
+ continue;
+
+ /* ...store the new entry... */
+ t[wm8904->num_retune_mobile_texts] =
+ pdata->retune_mobile_cfgs[i].name;
+
+ /* ...and remember the new version. */
+ wm8904->num_retune_mobile_texts++;
+ wm8904->retune_mobile_texts = t;
+ }
+
+ dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+ wm8904->num_retune_mobile_texts);
+
+ wm8904->retune_mobile_enum.max = wm8904->num_retune_mobile_texts;
+ wm8904->retune_mobile_enum.texts = wm8904->retune_mobile_texts;
+
+ ret = snd_soc_add_controls(&wm8904->codec, &control, 1);
+ if (ret != 0)
+ dev_err(wm8904->codec.dev,
+ "Failed to add ReTune Mobile control: %d\n", ret);
+}
+
+static void wm8904_handle_pdata(struct wm8904_priv *wm8904)
+{
+ struct snd_soc_codec *codec = &wm8904->codec;
+ struct wm8904_pdata *pdata = wm8904->pdata;
+ int ret, i;
+
+ if (!pdata) {
+ snd_soc_add_controls(&wm8904->codec, wm8904_eq_controls,
+ ARRAY_SIZE(wm8904_eq_controls));
+ return;
+ }
+
+ dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
+
+ if (pdata->num_drc_cfgs) {
+ struct snd_kcontrol_new control =
+ SOC_ENUM_EXT("DRC Mode", wm8904->drc_enum,
+ wm8904_get_drc_enum, wm8904_put_drc_enum);
+
+ /* We need an array of texts for the enum API */
+ wm8904->drc_texts = kmalloc(sizeof(char *)
+ * pdata->num_drc_cfgs, GFP_KERNEL);
+ if (!wm8904->drc_texts) {
+ dev_err(wm8904->codec.dev,
+ "Failed to allocate %d DRC config texts\n",
+ pdata->num_drc_cfgs);
+ return;
+ }
+
+ for (i = 0; i < pdata->num_drc_cfgs; i++)
+ wm8904->drc_texts[i] = pdata->drc_cfgs[i].name;
+
+ wm8904->drc_enum.max = pdata->num_drc_cfgs;
+ wm8904->drc_enum.texts = wm8904->drc_texts;
+
+ ret = snd_soc_add_controls(&wm8904->codec, &control, 1);
+ if (ret != 0)
+ dev_err(wm8904->codec.dev,
+ "Failed to add DRC mode control: %d\n", ret);
+
+ wm8904_set_drc(codec);
+ }
+
+ dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
+ pdata->num_retune_mobile_cfgs);
+
+ if (pdata->num_retune_mobile_cfgs)
+ wm8904_handle_retune_mobile_pdata(wm8904);
+ else
+ snd_soc_add_controls(&wm8904->codec, wm8904_eq_controls,
+ ARRAY_SIZE(wm8904_eq_controls));
+}
+
+static int wm8904_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ if (wm8904_codec == NULL) {
+ dev_err(&pdev->dev, "Codec device not registered\n");
+ return -ENODEV;
+ }
+
+ socdev->card->codec = wm8904_codec;
+ codec = wm8904_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+ goto pcm_err;
+ }
+
+ wm8904_handle_pdata(codec->private_data);
+
+ wm8904_add_widgets(codec);
+
+ return ret;
+
+pcm_err:
+ return ret;
+}
+
+static int wm8904_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8904 = {
+ .probe = wm8904_probe,
+ .remove = wm8904_remove,
+ .suspend = wm8904_suspend,
+ .resume = wm8904_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8904);
+
+static int wm8904_register(struct wm8904_priv *wm8904,
+ enum snd_soc_control_type control)
+{
+ int ret;
+ struct snd_soc_codec *codec = &wm8904->codec;
+ int i;
+
+ if (wm8904_codec) {
+ dev_err(codec->dev, "Another WM8904 is registered\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = wm8904;
+ codec->name = "WM8904";
+ codec->owner = THIS_MODULE;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = wm8904_set_bias_level;
+ codec->dai = &wm8904_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = WM8904_MAX_REGISTER;
+ codec->reg_cache = &wm8904->reg_cache;
+ codec->volatile_register = wm8904_volatile_register;
+
+ memcpy(codec->reg_cache, wm8904_reg, sizeof(wm8904_reg));
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, control);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8904->supplies); i++)
+ wm8904->supplies[i].supply = wm8904_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+ wm8904->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = snd_soc_read(codec, WM8904_SW_RESET_AND_ID);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read ID register\n");
+ goto err_enable;
+ }
+ if (ret != wm8904_reg[WM8904_SW_RESET_AND_ID]) {
+ dev_err(codec->dev, "Device is not a WM8904, ID is %x\n", ret);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ ret = snd_soc_read(codec, WM8904_REVISION);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read device revision: %d\n",
+ ret);
+ goto err_enable;
+ }
+ dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+ ret = wm8904_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err_enable;
+ }
+
+ wm8904_dai.dev = codec->dev;
+
+ /* Change some default settings - latch VU and enable ZC */
+ wm8904->reg_cache[WM8904_ADC_DIGITAL_VOLUME_LEFT] |= WM8904_ADC_VU;
+ wm8904->reg_cache[WM8904_ADC_DIGITAL_VOLUME_RIGHT] |= WM8904_ADC_VU;
+ wm8904->reg_cache[WM8904_DAC_DIGITAL_VOLUME_LEFT] |= WM8904_DAC_VU;
+ wm8904->reg_cache[WM8904_DAC_DIGITAL_VOLUME_RIGHT] |= WM8904_DAC_VU;
+ wm8904->reg_cache[WM8904_ANALOGUE_OUT1_LEFT] |= WM8904_HPOUT_VU |
+ WM8904_HPOUTLZC;
+ wm8904->reg_cache[WM8904_ANALOGUE_OUT1_RIGHT] |= WM8904_HPOUT_VU |
+ WM8904_HPOUTRZC;
+ wm8904->reg_cache[WM8904_ANALOGUE_OUT2_LEFT] |= WM8904_LINEOUT_VU |
+ WM8904_LINEOUTLZC;
+ wm8904->reg_cache[WM8904_ANALOGUE_OUT2_RIGHT] |= WM8904_LINEOUT_VU |
+ WM8904_LINEOUTRZC;
+ wm8904->reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE;
+
+ /* Set Class W by default - this will be managed by the Class
+ * G widget at runtime where bypass paths are available.
+ */
+ wm8904->reg_cache[WM8904_CLASS_W_0] |= WM8904_CP_DYN_PWR;
+
+ /* Use normal bias source */
+ wm8904->reg_cache[WM8904_BIAS_CONTROL_0] &= ~WM8904_POBCTRL;
+
+ wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+
+ wm8904_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_dai(&wm8904_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ snd_soc_unregister_codec(codec);
+ return ret;
+ }
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+err:
+ kfree(wm8904);
+ return ret;
+}
+
+static void wm8904_unregister(struct wm8904_priv *wm8904)
+{
+ wm8904_set_bias_level(&wm8904->codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+ snd_soc_unregister_dai(&wm8904_dai);
+ snd_soc_unregister_codec(&wm8904->codec);
+ kfree(wm8904);
+ wm8904_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8904_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8904_priv *wm8904;
+ struct snd_soc_codec *codec;
+
+ wm8904 = kzalloc(sizeof(struct wm8904_priv), GFP_KERNEL);
+ if (wm8904 == NULL)
+ return -ENOMEM;
+
+ codec = &wm8904->codec;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ i2c_set_clientdata(i2c, wm8904);
+ codec->control_data = i2c;
+ wm8904->pdata = i2c->dev.platform_data;
+
+ codec->dev = &i2c->dev;
+
+ return wm8904_register(wm8904, SND_SOC_I2C);
+}
+
+static __devexit int wm8904_i2c_remove(struct i2c_client *client)
+{
+ struct wm8904_priv *wm8904 = i2c_get_clientdata(client);
+ wm8904_unregister(wm8904);
+ return 0;
+}
+
+static const struct i2c_device_id wm8904_i2c_id[] = {
+ { "wm8904", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id);
+
+static struct i2c_driver wm8904_i2c_driver = {
+ .driver = {
+ .name = "WM8904",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8904_i2c_probe,
+ .remove = __devexit_p(wm8904_i2c_remove),
+ .id_table = wm8904_i2c_id,
+};
+#endif
+
+static int __init wm8904_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8904_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8904 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8904_modinit);
+
+static void __exit wm8904_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8904_i2c_driver);
+#endif
+}
+module_exit(wm8904_exit);
+
+MODULE_DESCRIPTION("ASoC WM8904 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h
new file mode 100644
index 0000000..b68886d
--- /dev/null
+++ b/sound/soc/codecs/wm8904.h
@@ -0,0 +1,1681 @@
+/*
+ * wm8904.h -- WM8904 ASoC driver
+ *
+ * Copyright 2009 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.
+ */
+
+#ifndef _WM8904_H
+#define _WM8904_H
+
+#define WM8904_CLK_MCLK 1
+#define WM8904_CLK_FLL 2
+
+#define WM8904_FLL_MCLK 1
+#define WM8904_FLL_BCLK 2
+#define WM8904_FLL_LRCLK 3
+#define WM8904_FLL_FREE_RUNNING 4
+
+extern struct snd_soc_dai wm8904_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8904;
+
+/*
+ * Register values.
+ */
+#define WM8904_SW_RESET_AND_ID 0x00
+#define WM8904_REVISION 0x01
+#define WM8904_BIAS_CONTROL_0 0x04
+#define WM8904_VMID_CONTROL_0 0x05
+#define WM8904_MIC_BIAS_CONTROL_0 0x06
+#define WM8904_MIC_BIAS_CONTROL_1 0x07
+#define WM8904_ANALOGUE_DAC_0 0x08
+#define WM8904_MIC_FILTER_CONTROL 0x09
+#define WM8904_ANALOGUE_ADC_0 0x0A
+#define WM8904_POWER_MANAGEMENT_0 0x0C
+#define WM8904_POWER_MANAGEMENT_2 0x0E
+#define WM8904_POWER_MANAGEMENT_3 0x0F
+#define WM8904_POWER_MANAGEMENT_6 0x12
+#define WM8904_CLOCK_RATES_0 0x14
+#define WM8904_CLOCK_RATES_1 0x15
+#define WM8904_CLOCK_RATES_2 0x16
+#define WM8904_AUDIO_INTERFACE_0 0x18
+#define WM8904_AUDIO_INTERFACE_1 0x19
+#define WM8904_AUDIO_INTERFACE_2 0x1A
+#define WM8904_AUDIO_INTERFACE_3 0x1B
+#define WM8904_DAC_DIGITAL_VOLUME_LEFT 0x1E
+#define WM8904_DAC_DIGITAL_VOLUME_RIGHT 0x1F
+#define WM8904_DAC_DIGITAL_0 0x20
+#define WM8904_DAC_DIGITAL_1 0x21
+#define WM8904_ADC_DIGITAL_VOLUME_LEFT 0x24
+#define WM8904_ADC_DIGITAL_VOLUME_RIGHT 0x25
+#define WM8904_ADC_DIGITAL_0 0x26
+#define WM8904_DIGITAL_MICROPHONE_0 0x27
+#define WM8904_DRC_0 0x28
+#define WM8904_DRC_1 0x29
+#define WM8904_DRC_2 0x2A
+#define WM8904_DRC_3 0x2B
+#define WM8904_ANALOGUE_LEFT_INPUT_0 0x2C
+#define WM8904_ANALOGUE_RIGHT_INPUT_0 0x2D
+#define WM8904_ANALOGUE_LEFT_INPUT_1 0x2E
+#define WM8904_ANALOGUE_RIGHT_INPUT_1 0x2F
+#define WM8904_ANALOGUE_OUT1_LEFT 0x39
+#define WM8904_ANALOGUE_OUT1_RIGHT 0x3A
+#define WM8904_ANALOGUE_OUT2_LEFT 0x3B
+#define WM8904_ANALOGUE_OUT2_RIGHT 0x3C
+#define WM8904_ANALOGUE_OUT12_ZC 0x3D
+#define WM8904_DC_SERVO_0 0x43
+#define WM8904_DC_SERVO_1 0x44
+#define WM8904_DC_SERVO_2 0x45
+#define WM8904_DC_SERVO_4 0x47
+#define WM8904_DC_SERVO_5 0x48
+#define WM8904_DC_SERVO_6 0x49
+#define WM8904_DC_SERVO_7 0x4A
+#define WM8904_DC_SERVO_8 0x4B
+#define WM8904_DC_SERVO_9 0x4C
+#define WM8904_DC_SERVO_READBACK_0 0x4D
+#define WM8904_ANALOGUE_HP_0 0x5A
+#define WM8904_ANALOGUE_LINEOUT_0 0x5E
+#define WM8904_CHARGE_PUMP_0 0x62
+#define WM8904_CLASS_W_0 0x68
+#define WM8904_WRITE_SEQUENCER_0 0x6C
+#define WM8904_WRITE_SEQUENCER_1 0x6D
+#define WM8904_WRITE_SEQUENCER_2 0x6E
+#define WM8904_WRITE_SEQUENCER_3 0x6F
+#define WM8904_WRITE_SEQUENCER_4 0x70
+#define WM8904_FLL_CONTROL_1 0x74
+#define WM8904_FLL_CONTROL_2 0x75
+#define WM8904_FLL_CONTROL_3 0x76
+#define WM8904_FLL_CONTROL_4 0x77
+#define WM8904_FLL_CONTROL_5 0x78
+#define WM8904_GPIO_CONTROL_1 0x79
+#define WM8904_GPIO_CONTROL_2 0x7A
+#define WM8904_GPIO_CONTROL_3 0x7B
+#define WM8904_GPIO_CONTROL_4 0x7C
+#define WM8904_DIGITAL_PULLS 0x7E
+#define WM8904_INTERRUPT_STATUS 0x7F
+#define WM8904_INTERRUPT_STATUS_MASK 0x80
+#define WM8904_INTERRUPT_POLARITY 0x81
+#define WM8904_INTERRUPT_DEBOUNCE 0x82
+#define WM8904_EQ1 0x86
+#define WM8904_EQ2 0x87
+#define WM8904_EQ3 0x88
+#define WM8904_EQ4 0x89
+#define WM8904_EQ5 0x8A
+#define WM8904_EQ6 0x8B
+#define WM8904_EQ7 0x8C
+#define WM8904_EQ8 0x8D
+#define WM8904_EQ9 0x8E
+#define WM8904_EQ10 0x8F
+#define WM8904_EQ11 0x90
+#define WM8904_EQ12 0x91
+#define WM8904_EQ13 0x92
+#define WM8904_EQ14 0x93
+#define WM8904_EQ15 0x94
+#define WM8904_EQ16 0x95
+#define WM8904_EQ17 0x96
+#define WM8904_EQ18 0x97
+#define WM8904_EQ19 0x98
+#define WM8904_EQ20 0x99
+#define WM8904_EQ21 0x9A
+#define WM8904_EQ22 0x9B
+#define WM8904_EQ23 0x9C
+#define WM8904_EQ24 0x9D
+#define WM8904_CONTROL_INTERFACE_TEST_1 0xA1
+#define WM8904_ANALOGUE_OUTPUT_BIAS_0 0xCC
+#define WM8904_FLL_NCO_TEST_0 0xF7
+#define WM8904_FLL_NCO_TEST_1 0xF8
+
+#define WM8904_REGISTER_COUNT 101
+#define WM8904_MAX_REGISTER 0xF8
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8904_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision
+ */
+#define WM8904_REVISION_MASK 0x000F /* REVISION - [3:0] */
+#define WM8904_REVISION_SHIFT 0 /* REVISION - [3:0] */
+#define WM8904_REVISION_WIDTH 16 /* REVISION - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8904_POBCTRL 0x0010 /* POBCTRL */
+#define WM8904_POBCTRL_MASK 0x0010 /* POBCTRL */
+#define WM8904_POBCTRL_SHIFT 4 /* POBCTRL */
+#define WM8904_POBCTRL_WIDTH 1 /* POBCTRL */
+#define WM8904_ISEL_MASK 0x000C /* ISEL - [3:2] */
+#define WM8904_ISEL_SHIFT 2 /* ISEL - [3:2] */
+#define WM8904_ISEL_WIDTH 2 /* ISEL - [3:2] */
+#define WM8904_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */
+#define WM8904_BIAS_ENA 0x0001 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_SHIFT 0 /* BIAS_ENA */
+#define WM8904_BIAS_ENA_WIDTH 1 /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8904_VMID_BUF_ENA 0x0040 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_MASK 0x0040 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_SHIFT 6 /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */
+#define WM8904_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */
+#define WM8904_VMID_ENA 0x0001 /* VMID_ENA */
+#define WM8904_VMID_ENA_MASK 0x0001 /* VMID_ENA */
+#define WM8904_VMID_ENA_SHIFT 0 /* VMID_ENA */
+#define WM8904_VMID_ENA_WIDTH 1 /* VMID_ENA */
+
+/*
+ * R6 (0x06) - Mic Bias Control 0
+ */
+#define WM8904_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */
+#define WM8904_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */
+#define WM8904_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */
+#define WM8904_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */
+#define WM8904_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */
+#define WM8904_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */
+#define WM8904_MICDET_ENA 0x0002 /* MICDET_ENA */
+#define WM8904_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */
+#define WM8904_MICDET_ENA_SHIFT 1 /* MICDET_ENA */
+#define WM8904_MICDET_ENA_WIDTH 1 /* MICDET_ENA */
+#define WM8904_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */
+#define WM8904_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */
+#define WM8904_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */
+#define WM8904_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */
+
+/*
+ * R7 (0x07) - Mic Bias Control 1
+ */
+#define WM8904_MIC_DET_FILTER_ENA 0x8000 /* MIC_DET_FILTER_ENA */
+#define WM8904_MIC_DET_FILTER_ENA_MASK 0x8000 /* MIC_DET_FILTER_ENA */
+#define WM8904_MIC_DET_FILTER_ENA_SHIFT 15 /* MIC_DET_FILTER_ENA */
+#define WM8904_MIC_DET_FILTER_ENA_WIDTH 1 /* MIC_DET_FILTER_ENA */
+#define WM8904_MIC_SHORT_FILTER_ENA 0x4000 /* MIC_SHORT_FILTER_ENA */
+#define WM8904_MIC_SHORT_FILTER_ENA_MASK 0x4000 /* MIC_SHORT_FILTER_ENA */
+#define WM8904_MIC_SHORT_FILTER_ENA_SHIFT 14 /* MIC_SHORT_FILTER_ENA */
+#define WM8904_MIC_SHORT_FILTER_ENA_WIDTH 1 /* MIC_SHORT_FILTER_ENA */
+#define WM8904_MICBIAS_SEL_MASK 0x0007 /* MICBIAS_SEL - [2:0] */
+#define WM8904_MICBIAS_SEL_SHIFT 0 /* MICBIAS_SEL - [2:0] */
+#define WM8904_MICBIAS_SEL_WIDTH 3 /* MICBIAS_SEL - [2:0] */
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8904_DAC_BIAS_SEL_MASK 0x0018 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_SHIFT 3 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_WIDTH 2 /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_VMID_BIAS_SEL_MASK 0x0006 /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_SHIFT 1 /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_WIDTH 2 /* DAC_VMID_BIAS_SEL - [2:1] */
+
+/*
+ * R9 (0x09) - mic Filter Control
+ */
+#define WM8904_MIC_DET_SET_THRESHOLD_MASK 0xF000 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_SHIFT 12 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_WIDTH 4 /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_MASK 0x0F00 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_SHIFT 8 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_WIDTH 4 /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_MASK 0x00F0 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_SHIFT 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_WIDTH 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_MASK 0x000F /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_SHIFT 0 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_WIDTH 4 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8904_ADC_OSR128 0x0001 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8904_INL_ENA 0x0002 /* INL_ENA */
+#define WM8904_INL_ENA_MASK 0x0002 /* INL_ENA */
+#define WM8904_INL_ENA_SHIFT 1 /* INL_ENA */
+#define WM8904_INL_ENA_WIDTH 1 /* INL_ENA */
+#define WM8904_INR_ENA 0x0001 /* INR_ENA */
+#define WM8904_INR_ENA_MASK 0x0001 /* INR_ENA */
+#define WM8904_INR_ENA_SHIFT 0 /* INR_ENA */
+#define WM8904_INR_ENA_WIDTH 1 /* INR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8904_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */
+#define WM8904_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8904_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8904_DACL_ENA 0x0008 /* DACL_ENA */
+#define WM8904_DACL_ENA_MASK 0x0008 /* DACL_ENA */
+#define WM8904_DACL_ENA_SHIFT 3 /* DACL_ENA */
+#define WM8904_DACL_ENA_WIDTH 1 /* DACL_ENA */
+#define WM8904_DACR_ENA 0x0004 /* DACR_ENA */
+#define WM8904_DACR_ENA_MASK 0x0004 /* DACR_ENA */
+#define WM8904_DACR_ENA_SHIFT 2 /* DACR_ENA */
+#define WM8904_DACR_ENA_WIDTH 1 /* DACR_ENA */
+#define WM8904_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_SHIFT 1 /* ADCL_ENA */
+#define WM8904_ADCL_ENA_WIDTH 1 /* ADCL_ENA */
+#define WM8904_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_SHIFT 0 /* ADCR_ENA */
+#define WM8904_ADCR_ENA_WIDTH 1 /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8904_TOCLK_RATE_DIV16 0x4000 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_MASK 0x4000 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_SHIFT 14 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_WIDTH 1 /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_X4 0x2000 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_MASK 0x2000 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_SHIFT 13 /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_WIDTH 1 /* TOCLK_RATE_X4 */
+#define WM8904_SR_MODE 0x1000 /* SR_MODE */
+#define WM8904_SR_MODE_MASK 0x1000 /* SR_MODE */
+#define WM8904_SR_MODE_SHIFT 12 /* SR_MODE */
+#define WM8904_SR_MODE_WIDTH 1 /* SR_MODE */
+#define WM8904_MCLK_DIV 0x0001 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_MASK 0x0001 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_SHIFT 0 /* MCLK_DIV */
+#define WM8904_MCLK_DIV_WIDTH 1 /* MCLK_DIV */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8904_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */
+#define WM8904_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8904_MCLK_INV 0x8000 /* MCLK_INV */
+#define WM8904_MCLK_INV_MASK 0x8000 /* MCLK_INV */
+#define WM8904_MCLK_INV_SHIFT 15 /* MCLK_INV */
+#define WM8904_MCLK_INV_WIDTH 1 /* MCLK_INV */
+#define WM8904_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */
+#define WM8904_TOCLK_RATE 0x1000 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_MASK 0x1000 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_SHIFT 12 /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */
+#define WM8904_OPCLK_ENA 0x0008 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_MASK 0x0008 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_SHIFT 3 /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */
+#define WM8904_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
+#define WM8904_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
+#define WM8904_TOCLK_ENA 0x0001 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_MASK 0x0001 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_SHIFT 0 /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8904_DACL_DATINV 0x1000 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_SHIFT 12 /* DACL_DATINV */
+#define WM8904_DACL_DATINV_WIDTH 1 /* DACL_DATINV */
+#define WM8904_DACR_DATINV 0x0800 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_SHIFT 11 /* DACR_DATINV */
+#define WM8904_DACR_DATINV_WIDTH 1 /* DACR_DATINV */
+#define WM8904_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */
+#define WM8904_LOOPBACK 0x0100 /* LOOPBACK */
+#define WM8904_LOOPBACK_MASK 0x0100 /* LOOPBACK */
+#define WM8904_LOOPBACK_SHIFT 8 /* LOOPBACK */
+#define WM8904_LOOPBACK_WIDTH 1 /* LOOPBACK */
+#define WM8904_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */
+#define WM8904_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */
+#define WM8904_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */
+#define WM8904_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */
+#define WM8904_ADC_COMP 0x0008 /* ADC_COMP */
+#define WM8904_ADC_COMP_MASK 0x0008 /* ADC_COMP */
+#define WM8904_ADC_COMP_SHIFT 3 /* ADC_COMP */
+#define WM8904_ADC_COMP_WIDTH 1 /* ADC_COMP */
+#define WM8904_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */
+#define WM8904_DAC_COMP 0x0002 /* DAC_COMP */
+#define WM8904_DAC_COMP_MASK 0x0002 /* DAC_COMP */
+#define WM8904_DAC_COMP_SHIFT 1 /* DAC_COMP */
+#define WM8904_DAC_COMP_WIDTH 1 /* DAC_COMP */
+#define WM8904_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8904_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFADC_TDM 0x0800 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */
+#define WM8904_AIF_TRIS 0x0100 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_MASK 0x0100 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_SHIFT 8 /* AIF_TRIS */
+#define WM8904_AIF_TRIS_WIDTH 1 /* AIF_TRIS */
+#define WM8904_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */
+#define WM8904_BCLK_DIR 0x0040 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_SHIFT 6 /* BCLK_DIR */
+#define WM8904_BCLK_DIR_WIDTH 1 /* BCLK_DIR */
+#define WM8904_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */
+#define WM8904_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */
+#define WM8904_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8904_OPCLK_DIV_MASK 0x0F00 /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_SHIFT 8 /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [11:8] */
+#define WM8904_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8904_LRCLK_DIR 0x0800 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */
+#define WM8904_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8904_DAC_VU 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8904_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8904_DAC_VU 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */
+#define WM8904_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8904_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8904_DAC_MONO 0x1000 /* DAC_MONO */
+#define WM8904_DAC_MONO_MASK 0x1000 /* DAC_MONO */
+#define WM8904_DAC_MONO_SHIFT 12 /* DAC_MONO */
+#define WM8904_DAC_MONO_WIDTH 1 /* DAC_MONO */
+#define WM8904_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */
+#define WM8904_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */
+#define WM8904_DAC_UNMUTE_RAMP 0x0200 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_MASK 0x0200 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_SHIFT 9 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_OSR128 0x0040 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_MASK 0x0040 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_SHIFT 6 /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
+#define WM8904_DAC_MUTE 0x0008 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_SHIFT 3 /* DAC_MUTE */
+#define WM8904_DAC_MUTE_WIDTH 1 /* DAC_MUTE */
+#define WM8904_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8904_ADC_VU 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8904_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8904_ADC_VU 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */
+#define WM8904_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8904_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF 0x0010 /* ADC_HPF */
+#define WM8904_ADC_HPF_MASK 0x0010 /* ADC_HPF */
+#define WM8904_ADC_HPF_SHIFT 4 /* ADC_HPF */
+#define WM8904_ADC_HPF_WIDTH 1 /* ADC_HPF */
+#define WM8904_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */
+#define WM8904_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8904_DMIC_ENA 0x1000 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_MASK 0x1000 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_SHIFT 12 /* DMIC_ENA */
+#define WM8904_DMIC_ENA_WIDTH 1 /* DMIC_ENA */
+#define WM8904_DMIC_SRC 0x0800 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_MASK 0x0800 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_SHIFT 11 /* DMIC_SRC */
+#define WM8904_DMIC_SRC_WIDTH 1 /* DMIC_SRC */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8904_DRC_ENA 0x8000 /* DRC_ENA */
+#define WM8904_DRC_ENA_MASK 0x8000 /* DRC_ENA */
+#define WM8904_DRC_ENA_SHIFT 15 /* DRC_ENA */
+#define WM8904_DRC_ENA_WIDTH 1 /* DRC_ENA */
+#define WM8904_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */
+#define WM8904_DRC_GS_HYST_LVL_MASK 0x1800 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_SHIFT 11 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_WIDTH 2 /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */
+#define WM8904_DRC_GS_ENA 0x0008 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_MASK 0x0008 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_SHIFT 3 /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_WIDTH 1 /* DRC_GS_ENA */
+#define WM8904_DRC_QR 0x0004 /* DRC_QR */
+#define WM8904_DRC_QR_MASK 0x0004 /* DRC_QR */
+#define WM8904_DRC_QR_SHIFT 2 /* DRC_QR */
+#define WM8904_DRC_QR_WIDTH 1 /* DRC_QR */
+#define WM8904_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */
+#define WM8904_DRC_GS_HYST 0x0001 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_MASK 0x0001 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_SHIFT 0 /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_WIDTH 1 /* DRC_GS_HYST */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8904_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */
+#define WM8904_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */
+#define WM8904_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8904_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8904_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8904_LINMUTE 0x0080 /* LINMUTE */
+#define WM8904_LINMUTE_MASK 0x0080 /* LINMUTE */
+#define WM8904_LINMUTE_SHIFT 7 /* LINMUTE */
+#define WM8904_LINMUTE_WIDTH 1 /* LINMUTE */
+#define WM8904_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8904_RINMUTE 0x0080 /* RINMUTE */
+#define WM8904_RINMUTE_MASK 0x0080 /* RINMUTE */
+#define WM8904_RINMUTE_SHIFT 7 /* RINMUTE */
+#define WM8904_RINMUTE_WIDTH 1 /* RINMUTE */
+#define WM8904_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8904_INL_CM_ENA 0x0040 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */
+#define WM8904_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */
+#define WM8904_L_MODE_SHIFT 0 /* L_MODE - [1:0] */
+#define WM8904_L_MODE_WIDTH 2 /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8904_INR_CM_ENA 0x0040 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */
+#define WM8904_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */
+#define WM8904_R_MODE_SHIFT 0 /* R_MODE - [1:0] */
+#define WM8904_R_MODE_WIDTH 2 /* R_MODE - [1:0] */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8904_HPOUTL_MUTE 0x0100 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_MASK 0x0100 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_SHIFT 8 /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_WIDTH 1 /* HPOUTL_MUTE */
+#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8904_HPOUTLZC 0x0040 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_SHIFT 6 /* HPOUTLZC */
+#define WM8904_HPOUTLZC_WIDTH 1 /* HPOUTLZC */
+#define WM8904_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8904_HPOUTR_MUTE 0x0100 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_MASK 0x0100 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_SHIFT 8 /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_WIDTH 1 /* HPOUTR_MUTE */
+#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */
+#define WM8904_HPOUTRZC 0x0040 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_SHIFT 6 /* HPOUTRZC */
+#define WM8904_HPOUTRZC_WIDTH 1 /* HPOUTRZC */
+#define WM8904_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8904_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */
+#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */
+#define WM8904_LINEOUTLZC 0x0040 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */
+#define WM8904_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8904_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */
+#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */
+#define WM8904_LINEOUTRZC 0x0040 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */
+#define WM8904_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R61 (0x3D) - Analogue OUT12 ZC
+ */
+#define WM8904_HPL_BYP_ENA 0x0008 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_MASK 0x0008 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_SHIFT 3 /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_WIDTH 1 /* HPL_BYP_ENA */
+#define WM8904_HPR_BYP_ENA 0x0004 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_MASK 0x0004 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_SHIFT 2 /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_WIDTH 1 /* HPR_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA 0x0002 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_MASK 0x0002 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_SHIFT 1 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_WIDTH 1 /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA 0x0001 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_MASK 0x0001 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_SHIFT 0 /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_WIDTH 1 /* LINEOUTR_BYP_ENA */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8904_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */
+
+/*
+ * R68 (0x44) - DC Servo 1
+ */
+#define WM8904_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8904_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R71 (0x47) - DC Servo 4
+ */
+#define WM8904_DCS_SERIES_NO_23_MASK 0x007F /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_SHIFT 0 /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [6:0] */
+
+/*
+ * R72 (0x48) - DC Servo 5
+ */
+#define WM8904_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */
+
+/*
+ * R73 (0x49) - DC Servo 6
+ */
+#define WM8904_DCS_DAC_WR_VAL_3_MASK 0x00FF /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_SHIFT 0 /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [7:0] */
+
+/*
+ * R74 (0x4A) - DC Servo 7
+ */
+#define WM8904_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */
+
+/*
+ * R75 (0x4B) - DC Servo 8
+ */
+#define WM8904_DCS_DAC_WR_VAL_1_MASK 0x00FF /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_SHIFT 0 /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [7:0] */
+
+/*
+ * R76 (0x4C) - DC Servo 9
+ */
+#define WM8904_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R77 (0x4D) - DC Servo Readback 0
+ */
+#define WM8904_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8904_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
+#define WM8904_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA 0x0010 /* HPL_ENA */
+#define WM8904_HPL_ENA_MASK 0x0010 /* HPL_ENA */
+#define WM8904_HPL_ENA_SHIFT 4 /* HPL_ENA */
+#define WM8904_HPL_ENA_WIDTH 1 /* HPL_ENA */
+#define WM8904_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
+#define WM8904_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA 0x0001 /* HPR_ENA */
+#define WM8904_HPR_ENA_MASK 0x0001 /* HPR_ENA */
+#define WM8904_HPR_ENA_SHIFT 0 /* HPR_ENA */
+#define WM8904_HPR_ENA_WIDTH 1 /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8904_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */
+#define WM8904_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8904_CP_ENA 0x0001 /* CP_ENA */
+#define WM8904_CP_ENA_MASK 0x0001 /* CP_ENA */
+#define WM8904_CP_ENA_SHIFT 0 /* CP_ENA */
+#define WM8904_CP_ENA_WIDTH 1 /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8904_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8904_WSEQ_ENA 0x0100 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
+#define WM8904_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8904_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8904_WSEQ_EOS 0x4000 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
+#define WM8904_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8904_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
+#define WM8904_WSEQ_START 0x0100 /* WSEQ_START */
+#define WM8904_WSEQ_START_MASK 0x0100 /* WSEQ_START */
+#define WM8904_WSEQ_START_SHIFT 8 /* WSEQ_START */
+#define WM8904_WSEQ_START_WIDTH 1 /* WSEQ_START */
+#define WM8904_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8904_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
+
+/*
+ * R116 (0x74) - FLL Control 1
+ */
+#define WM8904_FLL_FRACN_ENA 0x0004 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_MASK 0x0004 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_SHIFT 2 /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_WIDTH 1 /* FLL_FRACN_ENA */
+#define WM8904_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */
+#define WM8904_FLL_ENA 0x0001 /* FLL_ENA */
+#define WM8904_FLL_ENA_MASK 0x0001 /* FLL_ENA */
+#define WM8904_FLL_ENA_SHIFT 0 /* FLL_ENA */
+#define WM8904_FLL_ENA_WIDTH 1 /* FLL_ENA */
+
+/*
+ * R117 (0x75) - FLL Control 2
+ */
+#define WM8904_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */
+
+/*
+ * R118 (0x76) - FLL Control 3
+ */
+#define WM8904_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */
+#define WM8904_FLL_K_SHIFT 0 /* FLL_K - [15:0] */
+#define WM8904_FLL_K_WIDTH 16 /* FLL_K - [15:0] */
+
+/*
+ * R119 (0x77) - FLL Control 4
+ */
+#define WM8904_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */
+#define WM8904_FLL_N_SHIFT 5 /* FLL_N - [14:5] */
+#define WM8904_FLL_N_WIDTH 10 /* FLL_N - [14:5] */
+#define WM8904_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */
+
+/*
+ * R120 (0x78) - FLL Control 5
+ */
+#define WM8904_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_SRC_MASK 0x0003 /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_SHIFT 0 /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */
+
+/*
+ * R121 (0x79) - GPIO Control 1
+ */
+#define WM8904_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8904_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */
+#define WM8904_GPIO1_PU_SHIFT 5 /* GPIO1_PU */
+#define WM8904_GPIO1_PU_WIDTH 1 /* GPIO1_PU */
+#define WM8904_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8904_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */
+#define WM8904_GPIO1_PD_SHIFT 4 /* GPIO1_PD */
+#define WM8904_GPIO1_PD_WIDTH 1 /* GPIO1_PD */
+#define WM8904_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+#define WM8904_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */
+#define WM8904_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */
+
+/*
+ * R122 (0x7A) - GPIO Control 2
+ */
+#define WM8904_GPIO2_PU 0x0020 /* GPIO2_PU */
+#define WM8904_GPIO2_PU_MASK 0x0020 /* GPIO2_PU */
+#define WM8904_GPIO2_PU_SHIFT 5 /* GPIO2_PU */
+#define WM8904_GPIO2_PU_WIDTH 1 /* GPIO2_PU */
+#define WM8904_GPIO2_PD 0x0010 /* GPIO2_PD */
+#define WM8904_GPIO2_PD_MASK 0x0010 /* GPIO2_PD */
+#define WM8904_GPIO2_PD_SHIFT 4 /* GPIO2_PD */
+#define WM8904_GPIO2_PD_WIDTH 1 /* GPIO2_PD */
+#define WM8904_GPIO2_SEL_MASK 0x000F /* GPIO2_SEL - [3:0] */
+#define WM8904_GPIO2_SEL_SHIFT 0 /* GPIO2_SEL - [3:0] */
+#define WM8904_GPIO2_SEL_WIDTH 4 /* GPIO2_SEL - [3:0] */
+
+/*
+ * R123 (0x7B) - GPIO Control 3
+ */
+#define WM8904_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8904_GPIO3_PU_MASK 0x0020 /* GPIO3_PU */
+#define WM8904_GPIO3_PU_SHIFT 5 /* GPIO3_PU */
+#define WM8904_GPIO3_PU_WIDTH 1 /* GPIO3_PU */
+#define WM8904_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8904_GPIO3_PD_MASK 0x0010 /* GPIO3_PD */
+#define WM8904_GPIO3_PD_SHIFT 4 /* GPIO3_PD */
+#define WM8904_GPIO3_PD_WIDTH 1 /* GPIO3_PD */
+#define WM8904_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+#define WM8904_GPIO3_SEL_SHIFT 0 /* GPIO3_SEL - [3:0] */
+#define WM8904_GPIO3_SEL_WIDTH 4 /* GPIO3_SEL - [3:0] */
+
+/*
+ * R124 (0x7C) - GPIO Control 4
+ */
+#define WM8904_GPI7_ENA 0x0200 /* GPI7_ENA */
+#define WM8904_GPI7_ENA_MASK 0x0200 /* GPI7_ENA */
+#define WM8904_GPI7_ENA_SHIFT 9 /* GPI7_ENA */
+#define WM8904_GPI7_ENA_WIDTH 1 /* GPI7_ENA */
+#define WM8904_GPI8_ENA 0x0100 /* GPI8_ENA */
+#define WM8904_GPI8_ENA_MASK 0x0100 /* GPI8_ENA */
+#define WM8904_GPI8_ENA_SHIFT 8 /* GPI8_ENA */
+#define WM8904_GPI8_ENA_WIDTH 1 /* GPI8_ENA */
+#define WM8904_GPIO_BCLK_MODE_ENA 0x0080 /* GPIO_BCLK_MODE_ENA */
+#define WM8904_GPIO_BCLK_MODE_ENA_MASK 0x0080 /* GPIO_BCLK_MODE_ENA */
+#define WM8904_GPIO_BCLK_MODE_ENA_SHIFT 7 /* GPIO_BCLK_MODE_ENA */
+#define WM8904_GPIO_BCLK_MODE_ENA_WIDTH 1 /* GPIO_BCLK_MODE_ENA */
+#define WM8904_GPIO_BCLK_SEL_MASK 0x000F /* GPIO_BCLK_SEL - [3:0] */
+#define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */
+#define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */
+
+/*
+ * R126 (0x7E) - Digital Pulls
+ */
+#define WM8904_MCLK_PU 0x0080 /* MCLK_PU */
+#define WM8904_MCLK_PU_MASK 0x0080 /* MCLK_PU */
+#define WM8904_MCLK_PU_SHIFT 7 /* MCLK_PU */
+#define WM8904_MCLK_PU_WIDTH 1 /* MCLK_PU */
+#define WM8904_MCLK_PD 0x0040 /* MCLK_PD */
+#define WM8904_MCLK_PD_MASK 0x0040 /* MCLK_PD */
+#define WM8904_MCLK_PD_SHIFT 6 /* MCLK_PD */
+#define WM8904_MCLK_PD_WIDTH 1 /* MCLK_PD */
+#define WM8904_DACDAT_PU 0x0020 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_SHIFT 5 /* DACDAT_PU */
+#define WM8904_DACDAT_PU_WIDTH 1 /* DACDAT_PU */
+#define WM8904_DACDAT_PD 0x0010 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_SHIFT 4 /* DACDAT_PD */
+#define WM8904_DACDAT_PD_WIDTH 1 /* DACDAT_PD */
+#define WM8904_LRCLK_PU 0x0008 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_SHIFT 3 /* LRCLK_PU */
+#define WM8904_LRCLK_PU_WIDTH 1 /* LRCLK_PU */
+#define WM8904_LRCLK_PD 0x0004 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_SHIFT 2 /* LRCLK_PD */
+#define WM8904_LRCLK_PD_WIDTH 1 /* LRCLK_PD */
+#define WM8904_BCLK_PU 0x0002 /* BCLK_PU */
+#define WM8904_BCLK_PU_MASK 0x0002 /* BCLK_PU */
+#define WM8904_BCLK_PU_SHIFT 1 /* BCLK_PU */
+#define WM8904_BCLK_PU_WIDTH 1 /* BCLK_PU */
+#define WM8904_BCLK_PD 0x0001 /* BCLK_PD */
+#define WM8904_BCLK_PD_MASK 0x0001 /* BCLK_PD */
+#define WM8904_BCLK_PD_SHIFT 0 /* BCLK_PD */
+#define WM8904_BCLK_PD_WIDTH 1 /* BCLK_PD */
+
+/*
+ * R127 (0x7F) - Interrupt Status
+ */
+#define WM8904_IRQ 0x0400 /* IRQ */
+#define WM8904_IRQ_MASK 0x0400 /* IRQ */
+#define WM8904_IRQ_SHIFT 10 /* IRQ */
+#define WM8904_IRQ_WIDTH 1 /* IRQ */
+#define WM8904_GPIO_BCLK_EINT 0x0200 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_MASK 0x0200 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_SHIFT 9 /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_WIDTH 1 /* GPIO_BCLK_EINT */
+#define WM8904_WSEQ_EINT 0x0100 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_MASK 0x0100 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_SHIFT 8 /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */
+#define WM8904_GPIO3_EINT 0x0080 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_MASK 0x0080 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_SHIFT 7 /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_WIDTH 1 /* GPIO3_EINT */
+#define WM8904_GPIO2_EINT 0x0040 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_MASK 0x0040 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_SHIFT 6 /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_WIDTH 1 /* GPIO2_EINT */
+#define WM8904_GPIO1_EINT 0x0020 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_MASK 0x0020 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_SHIFT 5 /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */
+#define WM8904_GPI8_EINT 0x0010 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_MASK 0x0010 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_SHIFT 4 /* GPI8_EINT */
+#define WM8904_GPI8_EINT_WIDTH 1 /* GPI8_EINT */
+#define WM8904_GPI7_EINT 0x0008 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_MASK 0x0008 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_SHIFT 3 /* GPI7_EINT */
+#define WM8904_GPI7_EINT_WIDTH 1 /* GPI7_EINT */
+#define WM8904_FLL_LOCK_EINT 0x0004 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_MASK 0x0004 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_SHIFT 2 /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */
+#define WM8904_MIC_SHRT_EINT 0x0002 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_MASK 0x0002 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_SHIFT 1 /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_WIDTH 1 /* MIC_SHRT_EINT */
+#define WM8904_MIC_DET_EINT 0x0001 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_MASK 0x0001 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_SHIFT 0 /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_WIDTH 1 /* MIC_DET_EINT */
+
+/*
+ * R128 (0x80) - Interrupt Status Mask
+ */
+#define WM8904_IM_GPIO_BCLK_EINT 0x0200 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_MASK 0x0200 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_SHIFT 9 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_WIDTH 1 /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_WSEQ_EINT 0x0100 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_MASK 0x0100 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_SHIFT 8 /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */
+#define WM8904_IM_GPIO3_EINT 0x0080 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_MASK 0x0080 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_SHIFT 7 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_WIDTH 1 /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO2_EINT 0x0040 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_MASK 0x0040 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_SHIFT 6 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_WIDTH 1 /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */
+#define WM8904_IM_GPI8_EINT 0x0010 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_MASK 0x0010 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_SHIFT 4 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */
+#define WM8904_IM_GPI7_EINT 0x0008 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_MASK 0x0008 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_SHIFT 3 /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */
+#define WM8904_IM_FLL_LOCK_EINT 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_MASK 0x0004 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_SHIFT 2 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_MIC_SHRT_EINT 0x0002 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_MASK 0x0002 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_SHIFT 1 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_WIDTH 1 /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_DET_EINT 0x0001 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_MASK 0x0001 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_SHIFT 0 /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_WIDTH 1 /* IM_MIC_DET_EINT */
+
+/*
+ * R129 (0x81) - Interrupt Polarity
+ */
+#define WM8904_GPIO_BCLK_EINT_POL 0x0200 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_MASK 0x0200 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_SHIFT 9 /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_WIDTH 1 /* GPIO_BCLK_EINT_POL */
+#define WM8904_WSEQ_EINT_POL 0x0100 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_MASK 0x0100 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_SHIFT 8 /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_WIDTH 1 /* WSEQ_EINT_POL */
+#define WM8904_GPIO3_EINT_POL 0x0080 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_MASK 0x0080 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_SHIFT 7 /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_WIDTH 1 /* GPIO3_EINT_POL */
+#define WM8904_GPIO2_EINT_POL 0x0040 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_MASK 0x0040 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_SHIFT 6 /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_WIDTH 1 /* GPIO2_EINT_POL */
+#define WM8904_GPIO1_EINT_POL 0x0020 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_MASK 0x0020 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_SHIFT 5 /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_WIDTH 1 /* GPIO1_EINT_POL */
+#define WM8904_GPI8_EINT_POL 0x0010 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_MASK 0x0010 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_SHIFT 4 /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_WIDTH 1 /* GPI8_EINT_POL */
+#define WM8904_GPI7_EINT_POL 0x0008 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_MASK 0x0008 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_SHIFT 3 /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_WIDTH 1 /* GPI7_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL 0x0004 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_MASK 0x0004 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_SHIFT 2 /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_WIDTH 1 /* FLL_LOCK_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL 0x0002 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_MASK 0x0002 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_SHIFT 1 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_WIDTH 1 /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL 0x0001 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_MASK 0x0001 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_SHIFT 0 /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_WIDTH 1 /* MIC_DET_EINT_POL */
+
+/*
+ * R130 (0x82) - Interrupt Debounce
+ */
+#define WM8904_GPIO_BCLK_EINT_DB 0x0200 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_MASK 0x0200 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_SHIFT 9 /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_WIDTH 1 /* GPIO_BCLK_EINT_DB */
+#define WM8904_WSEQ_EINT_DB 0x0100 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_MASK 0x0100 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_SHIFT 8 /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_WIDTH 1 /* WSEQ_EINT_DB */
+#define WM8904_GPIO3_EINT_DB 0x0080 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_MASK 0x0080 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_SHIFT 7 /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_WIDTH 1 /* GPIO3_EINT_DB */
+#define WM8904_GPIO2_EINT_DB 0x0040 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_MASK 0x0040 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_SHIFT 6 /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_WIDTH 1 /* GPIO2_EINT_DB */
+#define WM8904_GPIO1_EINT_DB 0x0020 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_MASK 0x0020 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_SHIFT 5 /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_WIDTH 1 /* GPIO1_EINT_DB */
+#define WM8904_GPI8_EINT_DB 0x0010 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_MASK 0x0010 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_SHIFT 4 /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_WIDTH 1 /* GPI8_EINT_DB */
+#define WM8904_GPI7_EINT_DB 0x0008 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_MASK 0x0008 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_SHIFT 3 /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_WIDTH 1 /* GPI7_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB 0x0004 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_MASK 0x0004 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_SHIFT 2 /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_WIDTH 1 /* FLL_LOCK_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB 0x0002 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_MASK 0x0002 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_SHIFT 1 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_WIDTH 1 /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB 0x0001 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_MASK 0x0001 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_SHIFT 0 /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_WIDTH 1 /* MIC_DET_EINT_DB */
+
+/*
+ * R134 (0x86) - EQ1
+ */
+#define WM8904_EQ_ENA 0x0001 /* EQ_ENA */
+#define WM8904_EQ_ENA_MASK 0x0001 /* EQ_ENA */
+#define WM8904_EQ_ENA_SHIFT 0 /* EQ_ENA */
+#define WM8904_EQ_ENA_WIDTH 1 /* EQ_ENA */
+
+/*
+ * R135 (0x87) - EQ2
+ */
+#define WM8904_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */
+
+/*
+ * R136 (0x88) - EQ3
+ */
+#define WM8904_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */
+
+/*
+ * R137 (0x89) - EQ4
+ */
+#define WM8904_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */
+
+/*
+ * R138 (0x8A) - EQ5
+ */
+#define WM8904_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */
+
+/*
+ * R139 (0x8B) - EQ6
+ */
+#define WM8904_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */
+
+/*
+ * R140 (0x8C) - EQ7
+ */
+#define WM8904_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */
+
+/*
+ * R141 (0x8D) - EQ8
+ */
+#define WM8904_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */
+
+/*
+ * R142 (0x8E) - EQ9
+ */
+#define WM8904_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */
+
+/*
+ * R143 (0x8F) - EQ10
+ */
+#define WM8904_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */
+
+/*
+ * R144 (0x90) - EQ11
+ */
+#define WM8904_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */
+
+/*
+ * R145 (0x91) - EQ12
+ */
+#define WM8904_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */
+
+/*
+ * R146 (0x92) - EQ13
+ */
+#define WM8904_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */
+
+/*
+ * R147 (0x93) - EQ14
+ */
+#define WM8904_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */
+
+/*
+ * R148 (0x94) - EQ15
+ */
+#define WM8904_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */
+
+/*
+ * R149 (0x95) - EQ16
+ */
+#define WM8904_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */
+
+/*
+ * R150 (0x96) - EQ17
+ */
+#define WM8904_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */
+
+/*
+ * R151 (0x97) - EQ18
+ */
+#define WM8904_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */
+
+/*
+ * R152 (0x98) - EQ19
+ */
+#define WM8904_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */
+
+/*
+ * R153 (0x99) - EQ20
+ */
+#define WM8904_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */
+
+/*
+ * R154 (0x9A) - EQ21
+ */
+#define WM8904_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */
+
+/*
+ * R155 (0x9B) - EQ22
+ */
+#define WM8904_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */
+
+/*
+ * R156 (0x9C) - EQ23
+ */
+#define WM8904_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */
+
+/*
+ * R157 (0x9D) - EQ24
+ */
+#define WM8904_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */
+
+/*
+ * R161 (0xA1) - Control Interface Test 1
+ */
+#define WM8904_USER_KEY 0x0002 /* USER_KEY */
+#define WM8904_USER_KEY_MASK 0x0002 /* USER_KEY */
+#define WM8904_USER_KEY_SHIFT 1 /* USER_KEY */
+#define WM8904_USER_KEY_WIDTH 1 /* USER_KEY */
+
+/*
+ * R204 (0xCC) - Analogue Output Bias 0
+ */
+#define WM8904_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */
+
+/*
+ * R247 (0xF7) - FLL NCO Test 0
+ */
+#define WM8904_FLL_FRC_NCO 0x0001 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_MASK 0x0001 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_SHIFT 0 /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */
+
+/*
+ * R248 (0xF8) - FLL NCO Test 1
+ */
+#define WM8904_FLL_FRC_NCO_VAL_MASK 0x003F /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_SHIFT 0 /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [5:0] */
+
+#endif
diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c
new file mode 100644
index 0000000..615dab2
--- /dev/null
+++ b/sound/soc/codecs/wm8955.c
@@ -0,0 +1,1151 @@
+/*
+ * wm8955.c -- WM8955 ALSA SoC Audio driver
+ *
+ * Copyright 2009 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.
+ */
+
+#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/regulator/consumer.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 <sound/wm8955.h>
+
+#include "wm8955.h"
+
+static struct snd_soc_codec *wm8955_codec;
+struct snd_soc_codec_device soc_codec_dev_wm8955;
+
+#define WM8955_NUM_SUPPLIES 4
+static const char *wm8955_supply_names[WM8955_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD",
+ "HPVDD",
+ "AVDD",
+};
+
+/* codec private data */
+struct wm8955_priv {
+ struct snd_soc_codec codec;
+ u16 reg_cache[WM8955_MAX_REGISTER + 1];
+
+ unsigned int mclk_rate;
+
+ int deemph;
+ int fs;
+
+ struct regulator_bulk_data supplies[WM8955_NUM_SUPPLIES];
+
+ struct wm8955_pdata *pdata;
+};
+
+static const u16 wm8955_reg[WM8955_MAX_REGISTER + 1] = {
+ 0x0000, /* R0 */
+ 0x0000, /* R1 */
+ 0x0079, /* R2 - LOUT1 volume */
+ 0x0079, /* R3 - ROUT1 volume */
+ 0x0000, /* R4 */
+ 0x0008, /* R5 - DAC Control */
+ 0x0000, /* R6 */
+ 0x000A, /* R7 - Audio Interface */
+ 0x0000, /* R8 - Sample Rate */
+ 0x0000, /* R9 */
+ 0x00FF, /* R10 - Left DAC volume */
+ 0x00FF, /* R11 - Right DAC volume */
+ 0x000F, /* R12 - Bass control */
+ 0x000F, /* R13 - Treble control */
+ 0x0000, /* R14 */
+ 0x0000, /* R15 - Reset */
+ 0x0000, /* R16 */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 */
+ 0x0000, /* R19 */
+ 0x0000, /* R20 */
+ 0x0000, /* R21 */
+ 0x0000, /* R22 */
+ 0x00C1, /* R23 - Additional control (1) */
+ 0x0000, /* R24 - Additional control (2) */
+ 0x0000, /* R25 - Power Management (1) */
+ 0x0000, /* R26 - Power Management (2) */
+ 0x0000, /* R27 - Additional Control (3) */
+ 0x0000, /* R28 */
+ 0x0000, /* R29 */
+ 0x0000, /* R30 */
+ 0x0000, /* R31 */
+ 0x0000, /* R32 */
+ 0x0000, /* R33 */
+ 0x0050, /* R34 - Left out Mix (1) */
+ 0x0050, /* R35 - Left out Mix (2) */
+ 0x0050, /* R36 - Right out Mix (1) */
+ 0x0050, /* R37 - Right Out Mix (2) */
+ 0x0050, /* R38 - Mono out Mix (1) */
+ 0x0050, /* R39 - Mono out Mix (2) */
+ 0x0079, /* R40 - LOUT2 volume */
+ 0x0079, /* R41 - ROUT2 volume */
+ 0x0079, /* R42 - MONOOUT volume */
+ 0x0000, /* R43 - Clocking / PLL */
+ 0x0103, /* R44 - PLL Control 1 */
+ 0x0024, /* R45 - PLL Control 2 */
+ 0x01BA, /* R46 - PLL Control 3 */
+ 0x0000, /* R47 */
+ 0x0000, /* R48 */
+ 0x0000, /* R49 */
+ 0x0000, /* R50 */
+ 0x0000, /* R51 */
+ 0x0000, /* R52 */
+ 0x0000, /* R53 */
+ 0x0000, /* R54 */
+ 0x0000, /* R55 */
+ 0x0000, /* R56 */
+ 0x0000, /* R57 */
+ 0x0000, /* R58 */
+ 0x0000, /* R59 - PLL Control 4 */
+};
+
+static int wm8955_reset(struct snd_soc_codec *codec)
+{
+ return snd_soc_write(codec, WM8955_RESET, 0);
+}
+
+struct pll_factors {
+ int n;
+ int k;
+ int outdiv;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 22) * 10)
+
+static int wm8995_pll_factors(struct device *dev,
+ int Fref, int Fout, struct pll_factors *pll)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod, target;
+
+ dev_dbg(dev, "Fref=%u Fout=%u\n", Fref, Fout);
+
+ /* The oscilator should run at should be 90-100MHz, and
+ * there's a divide by 4 plus an optional divide by 2 in the
+ * output path to generate the system clock. The clock table
+ * is sortd so we should always generate a suitable target. */
+ target = Fout * 4;
+ if (target < 90000000) {
+ pll->outdiv = 1;
+ target *= 2;
+ } else {
+ pll->outdiv = 0;
+ }
+
+ WARN_ON(target < 90000000 || target > 100000000);
+
+ dev_dbg(dev, "Fvco=%dHz\n", target);
+
+ /* Now, calculate N.K */
+ Ndiv = target / Fref;
+
+ pll->n = Ndiv;
+ Nmod = target % Fref;
+ dev_dbg(dev, "Nmod=%d\n", Nmod);
+
+ /* 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 */
+ pll->k = K / 10;
+
+ dev_dbg(dev, "N=%x K=%x OUTDIV=%x\n", pll->n, pll->k, pll->outdiv);
+
+ return 0;
+}
+
+/* Lookup table specifiying SRATE (table 25 in datasheet); some of the
+ * output frequencies have been rounded to the standard frequencies
+ * they are intended to match where the error is slight. */
+static struct {
+ int mclk;
+ int fs;
+ int usb;
+ int sr;
+} clock_cfgs[] = {
+ { 18432000, 8000, 0, 3, },
+ { 18432000, 12000, 0, 9, },
+ { 18432000, 16000, 0, 11, },
+ { 18432000, 24000, 0, 29, },
+ { 18432000, 32000, 0, 13, },
+ { 18432000, 48000, 0, 1, },
+ { 18432000, 96000, 0, 15, },
+
+ { 16934400, 8018, 0, 19, },
+ { 16934400, 11025, 0, 25, },
+ { 16934400, 22050, 0, 27, },
+ { 16934400, 44100, 0, 17, },
+ { 16934400, 88200, 0, 31, },
+
+ { 12000000, 8000, 1, 2, },
+ { 12000000, 11025, 1, 25, },
+ { 12000000, 12000, 1, 8, },
+ { 12000000, 16000, 1, 10, },
+ { 12000000, 22050, 1, 27, },
+ { 12000000, 24000, 1, 28, },
+ { 12000000, 32000, 1, 12, },
+ { 12000000, 44100, 1, 17, },
+ { 12000000, 48000, 1, 0, },
+ { 12000000, 88200, 1, 31, },
+ { 12000000, 96000, 1, 14, },
+
+ { 12288000, 8000, 0, 2, },
+ { 12288000, 12000, 0, 8, },
+ { 12288000, 16000, 0, 10, },
+ { 12288000, 24000, 0, 28, },
+ { 12288000, 32000, 0, 12, },
+ { 12288000, 48000, 0, 0, },
+ { 12288000, 96000, 0, 14, },
+
+ { 12289600, 8018, 0, 18, },
+ { 12289600, 11025, 0, 24, },
+ { 12289600, 22050, 0, 26, },
+ { 11289600, 44100, 0, 16, },
+ { 11289600, 88200, 0, 31, },
+};
+
+static int wm8955_configure_clocking(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = codec->private_data;
+ int i, ret, val;
+ int clocking = 0;
+ int srate = 0;
+ int sr = -1;
+ struct pll_factors pll;
+
+ /* If we're not running a sample rate currently just pick one */
+ if (wm8955->fs == 0)
+ wm8955->fs = 8000;
+
+ /* Can we generate an exact output? */
+ for (i = 0; i < ARRAY_SIZE(clock_cfgs); i++) {
+ if (wm8955->fs != clock_cfgs[i].fs)
+ continue;
+ sr = i;
+
+ if (wm8955->mclk_rate == clock_cfgs[i].mclk)
+ break;
+ }
+
+ /* We should never get here with an unsupported sample rate */
+ if (sr == -1) {
+ dev_err(codec->dev, "Sample rate %dHz unsupported\n",
+ wm8955->fs);
+ WARN_ON(sr == -1);
+ return -EINVAL;
+ }
+
+ if (i == ARRAY_SIZE(clock_cfgs)) {
+ /* If we can't generate the right clock from MCLK then
+ * we should configure the PLL to supply us with an
+ * appropriate clock.
+ */
+ clocking |= WM8955_MCLKSEL;
+
+ /* Use the last divider configuration we saw for the
+ * sample rate. */
+ ret = wm8995_pll_factors(codec->dev, wm8955->mclk_rate,
+ clock_cfgs[sr].mclk, &pll);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Unable to generate %dHz from %dHz MCLK\n",
+ wm8955->fs, wm8955->mclk_rate);
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_1,
+ WM8955_N_MASK | WM8955_K_21_18_MASK,
+ (pll.n << WM8955_N_SHIFT) |
+ pll.k >> 18);
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+ WM8955_K_17_9_MASK,
+ (pll.k >> 9) & WM8955_K_17_9_MASK);
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+ WM8955_K_8_0_MASK,
+ pll.k & WM8955_K_8_0_MASK);
+ if (pll.k)
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+ WM8955_KEN, WM8955_KEN);
+ else
+ snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+ WM8955_KEN, 0);
+
+ if (pll.outdiv)
+ val = WM8955_PLL_RB | WM8955_PLLOUTDIV2;
+ else
+ val = WM8955_PLL_RB;
+
+ /* Now start the PLL running */
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLOUTDIV2, val);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLLEN, WM8955_PLLEN);
+ }
+
+ srate = clock_cfgs[sr].usb | (clock_cfgs[sr].sr << WM8955_SR_SHIFT);
+
+ snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+ WM8955_USB | WM8955_SR_MASK, srate);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_MCLKSEL, clocking);
+
+ return 0;
+}
+
+static int wm8955_sysclk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ int ret = 0;
+
+ /* Always disable the clocks - if we're doing reconfiguration this
+ * avoids misclocking.
+ */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_DIGENB, 0);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMD:
+ break;
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = wm8955_configure_clocking(codec);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8955_set_deemph(struct snd_soc_codec *codec)
+{
+ struct wm8955_priv *wm8955 = codec->private_data;
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8955->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8955->fs) <
+ abs(deemph_settings[best] - wm8955->fs))
+ best = i;
+ }
+
+ val = best << WM8955_DEEMPH_SHIFT;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8955_DAC_CONTROL,
+ WM8955_DEEMPH_MASK, val);
+}
+
+static int wm8955_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8955_priv *wm8955 = codec->private_data;
+
+ return wm8955->deemph;
+}
+
+static int wm8955_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8955_priv *wm8955 = codec->private_data;
+ int deemph = ucontrol->value.enumerated.item[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8955->deemph = deemph;
+
+ return wm8955_set_deemph(codec);
+}
+
+static const char *bass_mode_text[] = {
+ "Linear", "Adaptive",
+};
+
+static const struct soc_enum bass_mode =
+ SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 7, 2, bass_mode_text);
+
+static const char *bass_cutoff_text[] = {
+ "Low", "High"
+};
+
+static const struct soc_enum bass_cutoff =
+ SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 6, 2, bass_cutoff_text);
+
+static const char *treble_cutoff_text[] = {
+ "High", "Low"
+};
+
+static const struct soc_enum treble_cutoff =
+ SOC_ENUM_SINGLE(WM8955_TREBLE_CONTROL, 6, 2, treble_cutoff_text);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(atten_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(treble_tlv, -1200, 150, 1);
+
+static const struct snd_kcontrol_new wm8955_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8955_LEFT_DAC_VOLUME,
+ WM8955_RIGHT_DAC_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Playback Attenuation Volume", WM8955_DAC_CONTROL, 7, 1, 1,
+ atten_tlv),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8955_get_deemph, wm8955_put_deemph),
+
+SOC_ENUM("Bass Mode", bass_mode),
+SOC_ENUM("Bass Cutoff", bass_cutoff),
+SOC_SINGLE("Bass Volume", WM8955_BASS_CONTROL, 0, 15, 1),
+
+SOC_ENUM("Treble Cutoff", treble_cutoff),
+SOC_SINGLE_TLV("Treble Volume", WM8955_TREBLE_CONTROL, 0, 14, 1, treble_tlv),
+
+SOC_SINGLE_TLV("Left Bypass Volume", WM8955_LEFT_OUT_MIX_1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Left Mono Volume", WM8955_LEFT_OUT_MIX_2, 4, 7, 1,
+ bypass_tlv),
+
+SOC_SINGLE_TLV("Right Mono Volume", WM8955_RIGHT_OUT_MIX_1, 4, 7, 1,
+ bypass_tlv),
+SOC_SINGLE_TLV("Right Bypass Volume", WM8955_RIGHT_OUT_MIX_2, 4, 7, 1,
+ bypass_tlv),
+
+/* Not a stereo pair so they line up with the DAPM switches */
+SOC_SINGLE_TLV("Mono Left Bypass Volume", WM8955_MONO_OUT_MIX_1, 4, 7, 1,
+ mono_tlv),
+SOC_SINGLE_TLV("Mono Right Bypass Volume", WM8955_MONO_OUT_MIX_2, 4, 7, 1,
+ mono_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8955_LOUT1_VOLUME,
+ WM8955_ROUT1_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8955_LOUT1_VOLUME,
+ WM8955_ROUT1_VOLUME, 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8955_LOUT2_VOLUME,
+ WM8955_ROUT2_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8955_LOUT2_VOLUME,
+ WM8955_ROUT2_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("Mono Volume", WM8955_MONOOUT_VOLUME, 0, 127, 0, out_tlv),
+SOC_SINGLE("Mono ZC Switch", WM8955_MONOOUT_VOLUME, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new lmixer[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8955_LEFT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_LEFT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_LEFT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_LEFT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new rmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_RIGHT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_RIGHT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8955_RIGHT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_RIGHT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new mmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_MONO_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8955_MONO_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_MONO_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8955_MONO_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8955_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("MONOIN-"),
+SND_SOC_DAPM_INPUT("MONOIN+"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+
+SND_SOC_DAPM_PGA("Mono Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8955_POWER_MANAGEMENT_1, 0, 1, wm8955_sysclk,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("TSDEN", WM8955_ADDITIONAL_CONTROL_1, 8, 0, NULL, 0),
+
+SND_SOC_DAPM_DAC("DACL", "Playback", WM8955_POWER_MANAGEMENT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", WM8955_POWER_MANAGEMENT_2, 7, 0),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8955_POWER_MANAGEMENT_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8955_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LOUT2 PGA", WM8955_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT2 PGA", WM8955_POWER_MANAGEMENT_2, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MOUT PGA", WM8955_POWER_MANAGEMENT_2, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("OUT3 PGA", WM8955_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+
+/* The names are chosen to make the control names nice */
+SND_SOC_DAPM_MIXER("Left", SND_SOC_NOPM, 0, 0,
+ lmixer, ARRAY_SIZE(lmixer)),
+SND_SOC_DAPM_MIXER("Right", SND_SOC_NOPM, 0, 0,
+ rmixer, ARRAY_SIZE(rmixer)),
+SND_SOC_DAPM_MIXER("Mono", SND_SOC_NOPM, 0, 0,
+ mmixer, ARRAY_SIZE(mmixer)),
+
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_route wm8955_intercon[] = {
+ { "DACL", NULL, "SYSCLK" },
+ { "DACR", NULL, "SYSCLK" },
+
+ { "Mono Input", NULL, "MONOIN-" },
+ { "Mono Input", NULL, "MONOIN+" },
+
+ { "Left", "Playback Switch", "DACL" },
+ { "Left", "Right Playback Switch", "DACR" },
+ { "Left", "Bypass Switch", "LINEINL" },
+ { "Left", "Mono Switch", "Mono Input" },
+
+ { "Right", "Playback Switch", "DACR" },
+ { "Right", "Left Playback Switch", "DACL" },
+ { "Right", "Bypass Switch", "LINEINR" },
+ { "Right", "Mono Switch", "Mono Input" },
+
+ { "Mono", "Left Playback Switch", "DACL" },
+ { "Mono", "Right Playback Switch", "DACR" },
+ { "Mono", "Left Bypass Switch", "LINEINL" },
+ { "Mono", "Right Bypass Switch", "LINEINR" },
+
+ { "LOUT1 PGA", NULL, "Left" },
+ { "LOUT1", NULL, "TSDEN" },
+ { "LOUT1", NULL, "LOUT1 PGA" },
+
+ { "ROUT1 PGA", NULL, "Right" },
+ { "ROUT1", NULL, "TSDEN" },
+ { "ROUT1", NULL, "ROUT1 PGA" },
+
+ { "LOUT2 PGA", NULL, "Left" },
+ { "LOUT2", NULL, "TSDEN" },
+ { "LOUT2", NULL, "LOUT2 PGA" },
+
+ { "ROUT2 PGA", NULL, "Right" },
+ { "ROUT2", NULL, "TSDEN" },
+ { "ROUT2", NULL, "ROUT2 PGA" },
+
+ { "MOUT PGA", NULL, "Mono" },
+ { "MONOOUT", NULL, "MOUT PGA" },
+
+ /* OUT3 not currently implemented */
+ { "OUT3", NULL, "OUT3 PGA" },
+};
+
+static int wm8955_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_add_controls(codec, wm8955_snd_controls,
+ ARRAY_SIZE(wm8955_snd_controls));
+
+ snd_soc_dapm_new_controls(codec, wm8955_dapm_widgets,
+ ARRAY_SIZE(wm8955_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, wm8955_intercon,
+ ARRAY_SIZE(wm8955_intercon));
+
+ return 0;
+}
+
+static int wm8955_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8955_priv *wm8955 = codec->private_data;
+ int ret;
+ int wl;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ wl = 0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ wl = 0x4;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ wl = 0x8;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ wl = 0xc;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+ WM8955_WL_MASK, wl);
+
+ wm8955->fs = params_rate(params);
+ wm8955_set_deemph(codec);
+
+ /* If the chip is clocked then disable the clocks and force a
+ * reconfiguration, otherwise DAPM will power up the
+ * clocks for us later. */
+ ret = snd_soc_read(codec, WM8955_POWER_MANAGEMENT_1);
+ if (ret < 0)
+ return ret;
+ if (ret & WM8955_DIGENB) {
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_DIGENB, 0);
+ snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+ WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+ wm8955_configure_clocking(codec);
+ }
+
+ return 0;
+}
+
+
+static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8955_priv *priv = codec->private_data;
+ int div;
+
+ switch (clk_id) {
+ case WM8955_CLK_MCLK:
+ if (freq > 15000000) {
+ priv->mclk_rate = freq /= 2;
+ div = WM8955_MCLKDIV2;
+ } else {
+ priv->mclk_rate = freq;
+ div = 0;
+ }
+
+ snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+ WM8955_MCLKDIV2, div);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+ return 0;
+}
+
+static int wm8955_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 aif = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aif |= WM8955_MS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_B:
+ aif |= WM8955_LRP;
+ case SND_SOC_DAIFMT_DSP_A:
+ aif |= 0x3;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ aif |= 0x2;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ aif |= 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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:
+ aif |= WM8955_BCLKINV;
+ 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:
+ aif |= WM8955_BCLKINV | WM8955_LRP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ aif |= WM8955_BCLKINV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ aif |= WM8955_LRP;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+ WM8955_MS | WM8955_FORMAT_MASK | WM8955_BCLKINV |
+ WM8955_LRP, aif);
+
+ return 0;
+}
+
+
+static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val;
+
+ if (mute)
+ val = WM8955_DACMU;
+ else
+ val = 0;
+
+ snd_soc_update_bits(codec, WM8955_DAC_CONTROL, WM8955_DACMU, val);
+
+ return 0;
+}
+
+static int wm8955_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8955_priv *wm8955 = codec->private_data;
+ int ret, i;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID resistance 2*50k */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VMIDSEL_MASK,
+ 0x1 << WM8955_VMIDSEL_SHIFT);
+
+ /* Default bias current */
+ snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+ WM8955_VSEL_MASK,
+ 0x2 << WM8955_VSEL_SHIFT);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev,
+ "Failed to enable supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Sync back cached values if they're
+ * different from the hardware default.
+ */
+ for (i = 0; i < ARRAY_SIZE(wm8955->reg_cache); i++) {
+ if (i == WM8955_RESET)
+ continue;
+
+ if (wm8955->reg_cache[i] == wm8955_reg[i])
+ continue;
+
+ snd_soc_write(codec, i, wm8955->reg_cache[i]);
+ }
+
+ /* Enable VREF and VMID */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VREF |
+ WM8955_VMIDSEL_MASK,
+ WM8955_VREF |
+ 0x3 << WM8955_VREF_SHIFT);
+
+ /* Let VMID ramp */
+ msleep(500);
+
+ /* High resistance VROI to maintain outputs */
+ snd_soc_update_bits(codec,
+ WM8955_ADDITIONAL_CONTROL_3,
+ WM8955_VROI, WM8955_VROI);
+ }
+
+ /* Maintain VMID with 2*250k */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VMIDSEL_MASK,
+ 0x2 << WM8955_VMIDSEL_SHIFT);
+
+ /* Minimum bias current */
+ snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+ WM8955_VSEL_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Low resistance VROI to help discharge */
+ snd_soc_update_bits(codec,
+ WM8955_ADDITIONAL_CONTROL_3,
+ WM8955_VROI, 0);
+
+ /* Turn off VMID and VREF */
+ snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+ WM8955_VREF |
+ WM8955_VMIDSEL_MASK, 0);
+
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8955_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8955_dai_ops = {
+ .set_sysclk = wm8955_set_sysclk,
+ .set_fmt = wm8955_set_fmt,
+ .hw_params = wm8955_hw_params,
+ .digital_mute = wm8955_digital_mute,
+};
+
+struct snd_soc_dai wm8955_dai = {
+ .name = "WM8955",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8955_RATES,
+ .formats = WM8955_FORMATS,
+ },
+ .ops = &wm8955_dai_ops,
+};
+EXPORT_SYMBOL_GPL(wm8955_dai);
+
+#ifdef CONFIG_PM
+static int wm8955_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8955_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define wm8955_suspend NULL
+#define wm8955_resume NULL
+#endif
+
+static int wm8955_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ if (wm8955_codec == NULL) {
+ dev_err(&pdev->dev, "Codec device not registered\n");
+ return -ENODEV;
+ }
+
+ socdev->card->codec = wm8955_codec;
+ codec = wm8955_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+ goto pcm_err;
+ }
+
+ wm8955_add_widgets(codec);
+
+ return ret;
+
+pcm_err:
+ return ret;
+}
+
+static int wm8955_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8955 = {
+ .probe = wm8955_probe,
+ .remove = wm8955_remove,
+ .suspend = wm8955_suspend,
+ .resume = wm8955_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8955);
+
+static int wm8955_register(struct wm8955_priv *wm8955,
+ enum snd_soc_control_type control)
+{
+ int ret;
+ struct snd_soc_codec *codec = &wm8955->codec;
+ int i;
+
+ if (wm8955_codec) {
+ dev_err(codec->dev, "Another WM8955 is registered\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = wm8955;
+ codec->name = "WM8955";
+ codec->owner = THIS_MODULE;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = wm8955_set_bias_level;
+ codec->dai = &wm8955_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = WM8955_MAX_REGISTER;
+ codec->reg_cache = &wm8955->reg_cache;
+
+ memcpy(codec->reg_cache, wm8955_reg, sizeof(wm8955_reg));
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++)
+ wm8955->supplies[i].supply = wm8955_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_get;
+ }
+
+ ret = wm8955_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+ goto err_enable;
+ }
+
+ wm8955_dai.dev = codec->dev;
+
+ /* Change some default settings - latch VU and enable ZC */
+ wm8955->reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU;
+ wm8955->reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU;
+ wm8955->reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC;
+ wm8955->reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC;
+ wm8955->reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC;
+ wm8955->reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC;
+ wm8955->reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC;
+
+ /* Also enable adaptive bass boost by default */
+ wm8955->reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB;
+
+ /* Set platform data values */
+ if (wm8955->pdata) {
+ if (wm8955->pdata->out2_speaker)
+ wm8955->reg_cache[WM8955_ADDITIONAL_CONTROL_2]
+ |= WM8955_ROUT2INV;
+
+ if (wm8955->pdata->monoin_diff)
+ wm8955->reg_cache[WM8955_MONO_OUT_MIX_1]
+ |= WM8955_DMEN;
+ }
+
+ wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Bias level configuration will have done an extra enable */
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+
+ wm8955_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_dai(&wm8955_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ snd_soc_unregister_codec(codec);
+ return ret;
+ }
+
+ return 0;
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+err_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+err:
+ kfree(wm8955);
+ return ret;
+}
+
+static void wm8955_unregister(struct wm8955_priv *wm8955)
+{
+ wm8955_set_bias_level(&wm8955->codec, SND_SOC_BIAS_OFF);
+ regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+ snd_soc_unregister_dai(&wm8955_dai);
+ snd_soc_unregister_codec(&wm8955->codec);
+ kfree(wm8955);
+ wm8955_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8955_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8955_priv *wm8955;
+ struct snd_soc_codec *codec;
+
+ wm8955 = kzalloc(sizeof(struct wm8955_priv), GFP_KERNEL);
+ if (wm8955 == NULL)
+ return -ENOMEM;
+
+ codec = &wm8955->codec;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ i2c_set_clientdata(i2c, wm8955);
+ codec->control_data = i2c;
+ wm8955->pdata = i2c->dev.platform_data;
+
+ codec->dev = &i2c->dev;
+
+ return wm8955_register(wm8955, SND_SOC_I2C);
+}
+
+static __devexit int wm8955_i2c_remove(struct i2c_client *client)
+{
+ struct wm8955_priv *wm8955 = i2c_get_clientdata(client);
+ wm8955_unregister(wm8955);
+ return 0;
+}
+
+static const struct i2c_device_id wm8955_i2c_id[] = {
+ { "wm8955", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8955_i2c_id);
+
+static struct i2c_driver wm8955_i2c_driver = {
+ .driver = {
+ .name = "wm8955",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8955_i2c_probe,
+ .remove = __devexit_p(wm8955_i2c_remove),
+ .id_table = wm8955_i2c_id,
+};
+#endif
+
+static int __init wm8955_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&wm8955_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8955 I2C driver: %d\n",
+ ret);
+ }
+#endif
+ return 0;
+}
+module_init(wm8955_modinit);
+
+static void __exit wm8955_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&wm8955_i2c_driver);
+#endif
+}
+module_exit(wm8955_exit);
+
+MODULE_DESCRIPTION("ASoC WM8955 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8955.h b/sound/soc/codecs/wm8955.h
new file mode 100644
index 0000000..ae349c8
--- /dev/null
+++ b/sound/soc/codecs/wm8955.h
@@ -0,0 +1,489 @@
+/*
+ * wm8955.h -- WM8904 ASoC driver
+ *
+ * Copyright 2009 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.
+ */
+
+#ifndef _WM8955_H
+#define _WM8955_H
+
+#define WM8955_CLK_MCLK 1
+
+extern struct snd_soc_dai wm8955_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8955;
+
+/*
+ * Register values.
+ */
+#define WM8955_LOUT1_VOLUME 0x02
+#define WM8955_ROUT1_VOLUME 0x03
+#define WM8955_DAC_CONTROL 0x05
+#define WM8955_AUDIO_INTERFACE 0x07
+#define WM8955_SAMPLE_RATE 0x08
+#define WM8955_LEFT_DAC_VOLUME 0x0A
+#define WM8955_RIGHT_DAC_VOLUME 0x0B
+#define WM8955_BASS_CONTROL 0x0C
+#define WM8955_TREBLE_CONTROL 0x0D
+#define WM8955_RESET 0x0F
+#define WM8955_ADDITIONAL_CONTROL_1 0x17
+#define WM8955_ADDITIONAL_CONTROL_2 0x18
+#define WM8955_POWER_MANAGEMENT_1 0x19
+#define WM8955_POWER_MANAGEMENT_2 0x1A
+#define WM8955_ADDITIONAL_CONTROL_3 0x1B
+#define WM8955_LEFT_OUT_MIX_1 0x22
+#define WM8955_LEFT_OUT_MIX_2 0x23
+#define WM8955_RIGHT_OUT_MIX_1 0x24
+#define WM8955_RIGHT_OUT_MIX_2 0x25
+#define WM8955_MONO_OUT_MIX_1 0x26
+#define WM8955_MONO_OUT_MIX_2 0x27
+#define WM8955_LOUT2_VOLUME 0x28
+#define WM8955_ROUT2_VOLUME 0x29
+#define WM8955_MONOOUT_VOLUME 0x2A
+#define WM8955_CLOCKING_PLL 0x2B
+#define WM8955_PLL_CONTROL_1 0x2C
+#define WM8955_PLL_CONTROL_2 0x2D
+#define WM8955_PLL_CONTROL_3 0x2E
+#define WM8955_PLL_CONTROL_4 0x3B
+
+#define WM8955_REGISTER_COUNT 29
+#define WM8955_MAX_REGISTER 0x3B
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R2 (0x02) - LOUT1 volume
+ */
+#define WM8955_LO1VU 0x0100 /* LO1VU */
+#define WM8955_LO1VU_MASK 0x0100 /* LO1VU */
+#define WM8955_LO1VU_SHIFT 8 /* LO1VU */
+#define WM8955_LO1VU_WIDTH 1 /* LO1VU */
+#define WM8955_LO1ZC 0x0080 /* LO1ZC */
+#define WM8955_LO1ZC_MASK 0x0080 /* LO1ZC */
+#define WM8955_LO1ZC_SHIFT 7 /* LO1ZC */
+#define WM8955_LO1ZC_WIDTH 1 /* LO1ZC */
+#define WM8955_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_SHIFT 0 /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_WIDTH 7 /* LOUTVOL - [6:0] */
+
+/*
+ * R3 (0x03) - ROUT1 volume
+ */
+#define WM8955_RO1VU 0x0100 /* RO1VU */
+#define WM8955_RO1VU_MASK 0x0100 /* RO1VU */
+#define WM8955_RO1VU_SHIFT 8 /* RO1VU */
+#define WM8955_RO1VU_WIDTH 1 /* RO1VU */
+#define WM8955_RO1ZC 0x0080 /* RO1ZC */
+#define WM8955_RO1ZC_MASK 0x0080 /* RO1ZC */
+#define WM8955_RO1ZC_SHIFT 7 /* RO1ZC */
+#define WM8955_RO1ZC_WIDTH 1 /* RO1ZC */
+#define WM8955_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_SHIFT 0 /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_WIDTH 7 /* ROUTVOL - [6:0] */
+
+/*
+ * R5 (0x05) - DAC Control
+ */
+#define WM8955_DAT 0x0080 /* DAT */
+#define WM8955_DAT_MASK 0x0080 /* DAT */
+#define WM8955_DAT_SHIFT 7 /* DAT */
+#define WM8955_DAT_WIDTH 1 /* DAT */
+#define WM8955_DACMU 0x0008 /* DACMU */
+#define WM8955_DACMU_MASK 0x0008 /* DACMU */
+#define WM8955_DACMU_SHIFT 3 /* DACMU */
+#define WM8955_DACMU_WIDTH 1 /* DACMU */
+#define WM8955_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
+
+/*
+ * R7 (0x07) - Audio Interface
+ */
+#define WM8955_BCLKINV 0x0080 /* BCLKINV */
+#define WM8955_BCLKINV_MASK 0x0080 /* BCLKINV */
+#define WM8955_BCLKINV_SHIFT 7 /* BCLKINV */
+#define WM8955_BCLKINV_WIDTH 1 /* BCLKINV */
+#define WM8955_MS 0x0040 /* MS */
+#define WM8955_MS_MASK 0x0040 /* MS */
+#define WM8955_MS_SHIFT 6 /* MS */
+#define WM8955_MS_WIDTH 1 /* MS */
+#define WM8955_LRSWAP 0x0020 /* LRSWAP */
+#define WM8955_LRSWAP_MASK 0x0020 /* LRSWAP */
+#define WM8955_LRSWAP_SHIFT 5 /* LRSWAP */
+#define WM8955_LRSWAP_WIDTH 1 /* LRSWAP */
+#define WM8955_LRP 0x0010 /* LRP */
+#define WM8955_LRP_MASK 0x0010 /* LRP */
+#define WM8955_LRP_SHIFT 4 /* LRP */
+#define WM8955_LRP_WIDTH 1 /* LRP */
+#define WM8955_WL_MASK 0x000C /* WL - [3:2] */
+#define WM8955_WL_SHIFT 2 /* WL - [3:2] */
+#define WM8955_WL_WIDTH 2 /* WL - [3:2] */
+#define WM8955_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
+#define WM8955_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
+#define WM8955_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Sample Rate
+ */
+#define WM8955_BCLKDIV2 0x0080 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_MASK 0x0080 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_SHIFT 7 /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_WIDTH 1 /* BCLKDIV2 */
+#define WM8955_MCLKDIV2 0x0040 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_MASK 0x0040 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_SHIFT 6 /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */
+#define WM8955_SR_MASK 0x003E /* SR - [5:1] */
+#define WM8955_SR_SHIFT 1 /* SR - [5:1] */
+#define WM8955_SR_WIDTH 5 /* SR - [5:1] */
+#define WM8955_USB 0x0001 /* USB */
+#define WM8955_USB_MASK 0x0001 /* USB */
+#define WM8955_USB_SHIFT 0 /* USB */
+#define WM8955_USB_WIDTH 1 /* USB */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8955_LDVU 0x0100 /* LDVU */
+#define WM8955_LDVU_MASK 0x0100 /* LDVU */
+#define WM8955_LDVU_SHIFT 8 /* LDVU */
+#define WM8955_LDVU_WIDTH 1 /* LDVU */
+#define WM8955_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8955_RDVU 0x0100 /* RDVU */
+#define WM8955_RDVU_MASK 0x0100 /* RDVU */
+#define WM8955_RDVU_SHIFT 8 /* RDVU */
+#define WM8955_RDVU_WIDTH 1 /* RDVU */
+#define WM8955_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */
+
+/*
+ * R12 (0x0C) - Bass control
+ */
+#define WM8955_BB 0x0080 /* BB */
+#define WM8955_BB_MASK 0x0080 /* BB */
+#define WM8955_BB_SHIFT 7 /* BB */
+#define WM8955_BB_WIDTH 1 /* BB */
+#define WM8955_BC 0x0040 /* BC */
+#define WM8955_BC_MASK 0x0040 /* BC */
+#define WM8955_BC_SHIFT 6 /* BC */
+#define WM8955_BC_WIDTH 1 /* BC */
+#define WM8955_BASS_MASK 0x000F /* BASS - [3:0] */
+#define WM8955_BASS_SHIFT 0 /* BASS - [3:0] */
+#define WM8955_BASS_WIDTH 4 /* BASS - [3:0] */
+
+/*
+ * R13 (0x0D) - Treble control
+ */
+#define WM8955_TC 0x0040 /* TC */
+#define WM8955_TC_MASK 0x0040 /* TC */
+#define WM8955_TC_SHIFT 6 /* TC */
+#define WM8955_TC_WIDTH 1 /* TC */
+#define WM8955_TRBL_MASK 0x000F /* TRBL - [3:0] */
+#define WM8955_TRBL_SHIFT 0 /* TRBL - [3:0] */
+#define WM8955_TRBL_WIDTH 4 /* TRBL - [3:0] */
+
+/*
+ * R15 (0x0F) - Reset
+ */
+#define WM8955_RESET_MASK 0x01FF /* RESET - [8:0] */
+#define WM8955_RESET_SHIFT 0 /* RESET - [8:0] */
+#define WM8955_RESET_WIDTH 9 /* RESET - [8:0] */
+
+/*
+ * R23 (0x17) - Additional control (1)
+ */
+#define WM8955_TSDEN 0x0100 /* TSDEN */
+#define WM8955_TSDEN_MASK 0x0100 /* TSDEN */
+#define WM8955_TSDEN_SHIFT 8 /* TSDEN */
+#define WM8955_TSDEN_WIDTH 1 /* TSDEN */
+#define WM8955_VSEL_MASK 0x00C0 /* VSEL - [7:6] */
+#define WM8955_VSEL_SHIFT 6 /* VSEL - [7:6] */
+#define WM8955_VSEL_WIDTH 2 /* VSEL - [7:6] */
+#define WM8955_DMONOMIX_MASK 0x0030 /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_SHIFT 4 /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_WIDTH 2 /* DMONOMIX - [5:4] */
+#define WM8955_DACINV 0x0002 /* DACINV */
+#define WM8955_DACINV_MASK 0x0002 /* DACINV */
+#define WM8955_DACINV_SHIFT 1 /* DACINV */
+#define WM8955_DACINV_WIDTH 1 /* DACINV */
+#define WM8955_TOEN 0x0001 /* TOEN */
+#define WM8955_TOEN_MASK 0x0001 /* TOEN */
+#define WM8955_TOEN_SHIFT 0 /* TOEN */
+#define WM8955_TOEN_WIDTH 1 /* TOEN */
+
+/*
+ * R24 (0x18) - Additional control (2)
+ */
+#define WM8955_OUT3SW_MASK 0x0180 /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_SHIFT 7 /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_WIDTH 2 /* OUT3SW - [8:7] */
+#define WM8955_ROUT2INV 0x0010 /* ROUT2INV */
+#define WM8955_ROUT2INV_MASK 0x0010 /* ROUT2INV */
+#define WM8955_ROUT2INV_SHIFT 4 /* ROUT2INV */
+#define WM8955_ROUT2INV_WIDTH 1 /* ROUT2INV */
+#define WM8955_DACOSR 0x0001 /* DACOSR */
+#define WM8955_DACOSR_MASK 0x0001 /* DACOSR */
+#define WM8955_DACOSR_SHIFT 0 /* DACOSR */
+#define WM8955_DACOSR_WIDTH 1 /* DACOSR */
+
+/*
+ * R25 (0x19) - Power Management (1)
+ */
+#define WM8955_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */
+#define WM8955_VREF 0x0040 /* VREF */
+#define WM8955_VREF_MASK 0x0040 /* VREF */
+#define WM8955_VREF_SHIFT 6 /* VREF */
+#define WM8955_VREF_WIDTH 1 /* VREF */
+#define WM8955_DIGENB 0x0001 /* DIGENB */
+#define WM8955_DIGENB_MASK 0x0001 /* DIGENB */
+#define WM8955_DIGENB_SHIFT 0 /* DIGENB */
+#define WM8955_DIGENB_WIDTH 1 /* DIGENB */
+
+/*
+ * R26 (0x1A) - Power Management (2)
+ */
+#define WM8955_DACL 0x0100 /* DACL */
+#define WM8955_DACL_MASK 0x0100 /* DACL */
+#define WM8955_DACL_SHIFT 8 /* DACL */
+#define WM8955_DACL_WIDTH 1 /* DACL */
+#define WM8955_DACR 0x0080 /* DACR */
+#define WM8955_DACR_MASK 0x0080 /* DACR */
+#define WM8955_DACR_SHIFT 7 /* DACR */
+#define WM8955_DACR_WIDTH 1 /* DACR */
+#define WM8955_LOUT1 0x0040 /* LOUT1 */
+#define WM8955_LOUT1_MASK 0x0040 /* LOUT1 */
+#define WM8955_LOUT1_SHIFT 6 /* LOUT1 */
+#define WM8955_LOUT1_WIDTH 1 /* LOUT1 */
+#define WM8955_ROUT1 0x0020 /* ROUT1 */
+#define WM8955_ROUT1_MASK 0x0020 /* ROUT1 */
+#define WM8955_ROUT1_SHIFT 5 /* ROUT1 */
+#define WM8955_ROUT1_WIDTH 1 /* ROUT1 */
+#define WM8955_LOUT2 0x0010 /* LOUT2 */
+#define WM8955_LOUT2_MASK 0x0010 /* LOUT2 */
+#define WM8955_LOUT2_SHIFT 4 /* LOUT2 */
+#define WM8955_LOUT2_WIDTH 1 /* LOUT2 */
+#define WM8955_ROUT2 0x0008 /* ROUT2 */
+#define WM8955_ROUT2_MASK 0x0008 /* ROUT2 */
+#define WM8955_ROUT2_SHIFT 3 /* ROUT2 */
+#define WM8955_ROUT2_WIDTH 1 /* ROUT2 */
+#define WM8955_MONO 0x0004 /* MONO */
+#define WM8955_MONO_MASK 0x0004 /* MONO */
+#define WM8955_MONO_SHIFT 2 /* MONO */
+#define WM8955_MONO_WIDTH 1 /* MONO */
+#define WM8955_OUT3 0x0002 /* OUT3 */
+#define WM8955_OUT3_MASK 0x0002 /* OUT3 */
+#define WM8955_OUT3_SHIFT 1 /* OUT3 */
+#define WM8955_OUT3_WIDTH 1 /* OUT3 */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8955_VROI 0x0040 /* VROI */
+#define WM8955_VROI_MASK 0x0040 /* VROI */
+#define WM8955_VROI_SHIFT 6 /* VROI */
+#define WM8955_VROI_WIDTH 1 /* VROI */
+
+/*
+ * R34 (0x22) - Left out Mix (1)
+ */
+#define WM8955_LD2LO 0x0100 /* LD2LO */
+#define WM8955_LD2LO_MASK 0x0100 /* LD2LO */
+#define WM8955_LD2LO_SHIFT 8 /* LD2LO */
+#define WM8955_LD2LO_WIDTH 1 /* LD2LO */
+#define WM8955_LI2LO 0x0080 /* LI2LO */
+#define WM8955_LI2LO_MASK 0x0080 /* LI2LO */
+#define WM8955_LI2LO_SHIFT 7 /* LI2LO */
+#define WM8955_LI2LO_WIDTH 1 /* LI2LO */
+#define WM8955_LI2LOVOL_MASK 0x0070 /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_SHIFT 4 /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_WIDTH 3 /* LI2LOVOL - [6:4] */
+
+/*
+ * R35 (0x23) - Left out Mix (2)
+ */
+#define WM8955_RD2LO 0x0100 /* RD2LO */
+#define WM8955_RD2LO_MASK 0x0100 /* RD2LO */
+#define WM8955_RD2LO_SHIFT 8 /* RD2LO */
+#define WM8955_RD2LO_WIDTH 1 /* RD2LO */
+#define WM8955_RI2LO 0x0080 /* RI2LO */
+#define WM8955_RI2LO_MASK 0x0080 /* RI2LO */
+#define WM8955_RI2LO_SHIFT 7 /* RI2LO */
+#define WM8955_RI2LO_WIDTH 1 /* RI2LO */
+#define WM8955_RI2LOVOL_MASK 0x0070 /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_SHIFT 4 /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_WIDTH 3 /* RI2LOVOL - [6:4] */
+
+/*
+ * R36 (0x24) - Right out Mix (1)
+ */
+#define WM8955_LD2RO 0x0100 /* LD2RO */
+#define WM8955_LD2RO_MASK 0x0100 /* LD2RO */
+#define WM8955_LD2RO_SHIFT 8 /* LD2RO */
+#define WM8955_LD2RO_WIDTH 1 /* LD2RO */
+#define WM8955_LI2RO 0x0080 /* LI2RO */
+#define WM8955_LI2RO_MASK 0x0080 /* LI2RO */
+#define WM8955_LI2RO_SHIFT 7 /* LI2RO */
+#define WM8955_LI2RO_WIDTH 1 /* LI2RO */
+#define WM8955_LI2ROVOL_MASK 0x0070 /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_SHIFT 4 /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_WIDTH 3 /* LI2ROVOL - [6:4] */
+
+/*
+ * R37 (0x25) - Right Out Mix (2)
+ */
+#define WM8955_RD2RO 0x0100 /* RD2RO */
+#define WM8955_RD2RO_MASK 0x0100 /* RD2RO */
+#define WM8955_RD2RO_SHIFT 8 /* RD2RO */
+#define WM8955_RD2RO_WIDTH 1 /* RD2RO */
+#define WM8955_RI2RO 0x0080 /* RI2RO */
+#define WM8955_RI2RO_MASK 0x0080 /* RI2RO */
+#define WM8955_RI2RO_SHIFT 7 /* RI2RO */
+#define WM8955_RI2RO_WIDTH 1 /* RI2RO */
+#define WM8955_RI2ROVOL_MASK 0x0070 /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_SHIFT 4 /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_WIDTH 3 /* RI2ROVOL - [6:4] */
+
+/*
+ * R38 (0x26) - Mono out Mix (1)
+ */
+#define WM8955_LD2MO 0x0100 /* LD2MO */
+#define WM8955_LD2MO_MASK 0x0100 /* LD2MO */
+#define WM8955_LD2MO_SHIFT 8 /* LD2MO */
+#define WM8955_LD2MO_WIDTH 1 /* LD2MO */
+#define WM8955_LI2MO 0x0080 /* LI2MO */
+#define WM8955_LI2MO_MASK 0x0080 /* LI2MO */
+#define WM8955_LI2MO_SHIFT 7 /* LI2MO */
+#define WM8955_LI2MO_WIDTH 1 /* LI2MO */
+#define WM8955_LI2MOVOL_MASK 0x0070 /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_SHIFT 4 /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_WIDTH 3 /* LI2MOVOL - [6:4] */
+#define WM8955_DMEN 0x0001 /* DMEN */
+#define WM8955_DMEN_MASK 0x0001 /* DMEN */
+#define WM8955_DMEN_SHIFT 0 /* DMEN */
+#define WM8955_DMEN_WIDTH 1 /* DMEN */
+
+/*
+ * R39 (0x27) - Mono out Mix (2)
+ */
+#define WM8955_RD2MO 0x0100 /* RD2MO */
+#define WM8955_RD2MO_MASK 0x0100 /* RD2MO */
+#define WM8955_RD2MO_SHIFT 8 /* RD2MO */
+#define WM8955_RD2MO_WIDTH 1 /* RD2MO */
+#define WM8955_RI2MO 0x0080 /* RI2MO */
+#define WM8955_RI2MO_MASK 0x0080 /* RI2MO */
+#define WM8955_RI2MO_SHIFT 7 /* RI2MO */
+#define WM8955_RI2MO_WIDTH 1 /* RI2MO */
+#define WM8955_RI2MOVOL_MASK 0x0070 /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_SHIFT 4 /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_WIDTH 3 /* RI2MOVOL - [6:4] */
+
+/*
+ * R40 (0x28) - LOUT2 volume
+ */
+#define WM8955_LO2VU 0x0100 /* LO2VU */
+#define WM8955_LO2VU_MASK 0x0100 /* LO2VU */
+#define WM8955_LO2VU_SHIFT 8 /* LO2VU */
+#define WM8955_LO2VU_WIDTH 1 /* LO2VU */
+#define WM8955_LO2ZC 0x0080 /* LO2ZC */
+#define WM8955_LO2ZC_MASK 0x0080 /* LO2ZC */
+#define WM8955_LO2ZC_SHIFT 7 /* LO2ZC */
+#define WM8955_LO2ZC_WIDTH 1 /* LO2ZC */
+#define WM8955_LOUT2VOL_MASK 0x007F /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_WIDTH 7 /* LOUT2VOL - [6:0] */
+
+/*
+ * R41 (0x29) - ROUT2 volume
+ */
+#define WM8955_RO2VU 0x0100 /* RO2VU */
+#define WM8955_RO2VU_MASK 0x0100 /* RO2VU */
+#define WM8955_RO2VU_SHIFT 8 /* RO2VU */
+#define WM8955_RO2VU_WIDTH 1 /* RO2VU */
+#define WM8955_RO2ZC 0x0080 /* RO2ZC */
+#define WM8955_RO2ZC_MASK 0x0080 /* RO2ZC */
+#define WM8955_RO2ZC_SHIFT 7 /* RO2ZC */
+#define WM8955_RO2ZC_WIDTH 1 /* RO2ZC */
+#define WM8955_ROUT2VOL_MASK 0x007F /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_WIDTH 7 /* ROUT2VOL - [6:0] */
+
+/*
+ * R42 (0x2A) - MONOOUT volume
+ */
+#define WM8955_MOZC 0x0080 /* MOZC */
+#define WM8955_MOZC_MASK 0x0080 /* MOZC */
+#define WM8955_MOZC_SHIFT 7 /* MOZC */
+#define WM8955_MOZC_WIDTH 1 /* MOZC */
+#define WM8955_MOUTVOL_MASK 0x007F /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_SHIFT 0 /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_WIDTH 7 /* MOUTVOL - [6:0] */
+
+/*
+ * R43 (0x2B) - Clocking / PLL
+ */
+#define WM8955_MCLKSEL 0x0100 /* MCLKSEL */
+#define WM8955_MCLKSEL_MASK 0x0100 /* MCLKSEL */
+#define WM8955_MCLKSEL_SHIFT 8 /* MCLKSEL */
+#define WM8955_MCLKSEL_WIDTH 1 /* MCLKSEL */
+#define WM8955_PLLOUTDIV2 0x0020 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_MASK 0x0020 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_SHIFT 5 /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_WIDTH 1 /* PLLOUTDIV2 */
+#define WM8955_PLL_RB 0x0010 /* PLL_RB */
+#define WM8955_PLL_RB_MASK 0x0010 /* PLL_RB */
+#define WM8955_PLL_RB_SHIFT 4 /* PLL_RB */
+#define WM8955_PLL_RB_WIDTH 1 /* PLL_RB */
+#define WM8955_PLLEN 0x0008 /* PLLEN */
+#define WM8955_PLLEN_MASK 0x0008 /* PLLEN */
+#define WM8955_PLLEN_SHIFT 3 /* PLLEN */
+#define WM8955_PLLEN_WIDTH 1 /* PLLEN */
+
+/*
+ * R44 (0x2C) - PLL Control 1
+ */
+#define WM8955_N_MASK 0x01E0 /* N - [8:5] */
+#define WM8955_N_SHIFT 5 /* N - [8:5] */
+#define WM8955_N_WIDTH 4 /* N - [8:5] */
+#define WM8955_K_21_18_MASK 0x000F /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_SHIFT 0 /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_WIDTH 4 /* K(21:18) - [3:0] */
+
+/*
+ * R45 (0x2D) - PLL Control 2
+ */
+#define WM8955_K_17_9_MASK 0x01FF /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_SHIFT 0 /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_WIDTH 9 /* K(17:9) - [8:0] */
+
+/*
+ * R46 (0x2E) - PLL Control 3
+ */
+#define WM8955_K_8_0_MASK 0x01FF /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_SHIFT 0 /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_WIDTH 9 /* K(8:0) - [8:0] */
+
+/*
+ * R59 (0x3B) - PLL Control 4
+ */
+#define WM8955_KEN 0x0080 /* KEN */
+#define WM8955_KEN_MASK 0x0080 /* KEN */
+#define WM8955_KEN_SHIFT 7 /* KEN */
+#define WM8955_KEN_WIDTH 1 /* KEN */
+
+#endif
diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c
index a8007d5..d2342c5 100644
--- a/sound/soc/codecs/wm8961.c
+++ b/sound/soc/codecs/wm8961.c
@@ -1022,6 +1022,9 @@
int i;
for (i = 0; i < codec->reg_cache_size; i++) {
+ if (reg_cache[i] == wm8961_reg_defaults[i])
+ continue;
+
if (i == WM8961_SOFTWARE_RESET)
continue;
diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..8dcebaa
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,1119 @@
+/*
+ * wm8978.c -- WM8978 ALSA SoC Audio Codec driver
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2007 Carlos Munoz <carlos@kenati.com>
+ * Copyright 2006-2009 Wolfson Microelectronics PLC.
+ * Based on wm8974 and wm8990 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 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 <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 "wm8978.h"
+
+static struct snd_soc_codec *wm8978_codec;
+
+/* wm8978 register cache. Note that register 0 is not included in the cache. */
+static const u16 wm8978_reg[WM8978_CACHEREGNUM] = {
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x00...0x03 */
+ 0x0050, 0x0000, 0x0140, 0x0000, /* 0x04...0x07 */
+ 0x0000, 0x0000, 0x0000, 0x00ff, /* 0x08...0x0b */
+ 0x00ff, 0x0000, 0x0100, 0x00ff, /* 0x0c...0x0f */
+ 0x00ff, 0x0000, 0x012c, 0x002c, /* 0x10...0x13 */
+ 0x002c, 0x002c, 0x002c, 0x0000, /* 0x14...0x17 */
+ 0x0032, 0x0000, 0x0000, 0x0000, /* 0x18...0x1b */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x1c...0x1f */
+ 0x0038, 0x000b, 0x0032, 0x0000, /* 0x20...0x23 */
+ 0x0008, 0x000c, 0x0093, 0x00e9, /* 0x24...0x27 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 0x28...0x2b */
+ 0x0033, 0x0010, 0x0010, 0x0100, /* 0x2c...0x2f */
+ 0x0100, 0x0002, 0x0001, 0x0001, /* 0x30...0x33 */
+ 0x0039, 0x0039, 0x0039, 0x0039, /* 0x34...0x37 */
+ 0x0001, 0x0001, /* 0x38...0x3b */
+};
+
+/* codec private data */
+struct wm8978_priv {
+ struct snd_soc_codec codec;
+ unsigned int f_pllout;
+ unsigned int f_mclk;
+ unsigned int f_256fs;
+ unsigned int f_opclk;
+ enum wm8978_sysclk_src sysclk;
+ u16 reg_cache[WM8978_CACHEREGNUM];
+};
+
+static const char *wm8978_companding[] = {"Off", "NC", "u-law", "A-law"};
+static const char *wm8978_eqmode[] = {"Capture", "Playback"};
+static const char *wm8978_bw[] = {"Narrow", "Wide"};
+static const char *wm8978_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz"};
+static const char *wm8978_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz"};
+static const char *wm8978_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz"};
+static const char *wm8978_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"};
+static const char *wm8978_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"};
+static const char *wm8978_alc3[] = {"ALC", "Limiter"};
+static const char *wm8978_alc1[] = {"Off", "Right", "Left", "Both"};
+
+static const SOC_ENUM_SINGLE_DECL(adc_compand, WM8978_COMPANDING_CONTROL, 1,
+ wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(dac_compand, WM8978_COMPANDING_CONTROL, 3,
+ wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode);
+static const SOC_ENUM_SINGLE_DECL(eq1, WM8978_EQ1, 5, wm8978_eq1);
+static const SOC_ENUM_SINGLE_DECL(eq2bw, WM8978_EQ2, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq2, WM8978_EQ2, 5, wm8978_eq2);
+static const SOC_ENUM_SINGLE_DECL(eq3bw, WM8978_EQ3, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq3, WM8978_EQ3, 5, wm8978_eq3);
+static const SOC_ENUM_SINGLE_DECL(eq4bw, WM8978_EQ4, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq4, WM8978_EQ4, 5, wm8978_eq4);
+static const SOC_ENUM_SINGLE_DECL(eq5, WM8978_EQ5, 5, wm8978_eq5);
+static const SOC_ENUM_SINGLE_DECL(alc3, WM8978_ALC_CONTROL_3, 8, wm8978_alc3);
+static const SOC_ENUM_SINGLE_DECL(alc1, WM8978_ALC_CONTROL_1, 7, wm8978_alc1);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1);
+
+static const struct snd_kcontrol_new wm8978_snd_controls[] = {
+
+ SOC_SINGLE("Digital Loopback Switch",
+ WM8978_COMPANDING_CONTROL, 0, 1, 0),
+
+ SOC_ENUM("ADC Companding", adc_compand),
+ SOC_ENUM("DAC Companding", dac_compand),
+
+ SOC_DOUBLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 1, 0),
+
+ SOC_DOUBLE_R_TLV("PCM Volume",
+ WM8978_LEFT_DAC_DIGITAL_VOLUME, WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+ 0, 255, 0, digital_tlv),
+
+ SOC_SINGLE("High Pass Filter Switch", WM8978_ADC_CONTROL, 8, 1, 0),
+ SOC_SINGLE("High Pass Cut Off", WM8978_ADC_CONTROL, 4, 7, 0),
+ SOC_DOUBLE("ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 1, 0),
+
+ SOC_DOUBLE_R_TLV("ADC Volume",
+ WM8978_LEFT_ADC_DIGITAL_VOLUME, WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+ 0, 255, 0, digital_tlv),
+
+ SOC_ENUM("Equaliser Function", eqmode),
+ SOC_ENUM("EQ1 Cut Off", eq1),
+ SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ2 Bandwith", eq2bw),
+ SOC_ENUM("EQ2 Cut Off", eq2),
+ SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ3 Bandwith", eq3bw),
+ SOC_ENUM("EQ3 Cut Off", eq3),
+ SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("Equaliser EQ4 Bandwith", eq4bw),
+ SOC_ENUM("EQ4 Cut Off", eq4),
+ SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4, 0, 24, 1, eq_tlv),
+
+ SOC_ENUM("EQ5 Cut Off", eq5),
+ SOC_SINGLE_TLV("EQ5 Volume", WM8978_EQ5, 0, 24, 1, eq_tlv),
+
+ SOC_SINGLE("DAC Playback Limiter Switch",
+ WM8978_DAC_LIMITER_1, 8, 1, 0),
+ SOC_SINGLE("DAC Playback Limiter Decay",
+ WM8978_DAC_LIMITER_1, 4, 15, 0),
+ SOC_SINGLE("DAC Playback Limiter Attack",
+ WM8978_DAC_LIMITER_1, 0, 15, 0),
+
+ SOC_SINGLE("DAC Playback Limiter Threshold",
+ WM8978_DAC_LIMITER_2, 4, 7, 0),
+ SOC_SINGLE("DAC Playback Limiter Boost",
+ WM8978_DAC_LIMITER_2, 0, 15, 0),
+
+ SOC_ENUM("ALC Enable Switch", alc1),
+ SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0),
+ SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+
+ SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0),
+ SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0),
+
+ SOC_ENUM("ALC Capture Mode", alc3),
+ SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0),
+ SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0),
+
+ SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0),
+ SOC_SINGLE("ALC Capture Noise Gate Threshold",
+ WM8978_NOISE_GATE, 0, 7, 0),
+
+ SOC_DOUBLE_R("Capture PGA ZC Switch",
+ WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+ 7, 1, 0),
+
+ /* OUT1 - Headphones */
+ SOC_DOUBLE_R("Headphone Playback ZC Switch",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Headphone Playback Volume",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL,
+ 0, 63, 0, spk_tlv),
+
+ /* OUT2 - Speakers */
+ SOC_DOUBLE_R("Speaker Playback ZC Switch",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+
+ SOC_DOUBLE_R_TLV("Speaker Playback Volume",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL,
+ 0, 63, 0, spk_tlv),
+
+ /* OUT3/4 - Line Output */
+ SOC_DOUBLE_R("Line Playback Switch",
+ WM8978_OUT3_MIXER_CONTROL, WM8978_OUT4_MIXER_CONTROL, 6, 1, 1),
+
+ /* Mixer #3: Boost (Input) mixer */
+ SOC_DOUBLE_R("PGA Boost (+20dB)",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 8, 1, 0),
+ SOC_DOUBLE_R_TLV("L2/R2 Boost Volume",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 4, 7, 0, boost_tlv),
+ SOC_DOUBLE_R_TLV("Aux Boost Volume",
+ WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+ 0, 7, 0, boost_tlv),
+
+ /* Input PGA volume */
+ SOC_DOUBLE_R_TLV("Input PGA Volume",
+ WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+ 0, 63, 0, inpga_tlv),
+
+ /* Headphone */
+ SOC_DOUBLE_R("Headphone Switch",
+ WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+
+ /* Speaker */
+ SOC_DOUBLE_R("Speaker Switch",
+ WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+};
+
+/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */
+static const struct snd_kcontrol_new wm8978_left_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_LEFT_MIXER_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_LEFT_MIXER_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_LEFT_MIXER_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8978_right_out_mixer[] = {
+ SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_RIGHT_MIXER_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* OUT3/OUT4 Mixer not implemented */
+
+/* Mixer #2: Input PGA Mute */
+static const struct snd_kcontrol_new wm8978_left_input_mixer[] = {
+ SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_input_mixer[] = {
+ SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
+ SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
+ SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ WM8978_POWER_MANAGEMENT_3, 0, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ WM8978_POWER_MANAGEMENT_3, 1, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+ WM8978_POWER_MANAGEMENT_2, 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+ WM8978_POWER_MANAGEMENT_2, 1, 0),
+
+ /* Mixer #1: OUT1,2 */
+ SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3,
+ 2, 0, wm8978_left_out_mixer),
+ SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3,
+ 3, 0, wm8978_right_out_mixer),
+
+ SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2,
+ 2, 0, wm8978_left_input_mixer),
+ SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2,
+ 3, 0, wm8978_right_input_mixer),
+
+ SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+ 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+ 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL,
+ 6, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL,
+ 6, 1, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2,
+ 8, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3,
+ 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3,
+ 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3,
+ 8, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0),
+
+ SND_SOC_DAPM_INPUT("LMICN"),
+ SND_SOC_DAPM_INPUT("LMICP"),
+ SND_SOC_DAPM_INPUT("RMICN"),
+ SND_SOC_DAPM_INPUT("RMICP"),
+ SND_SOC_DAPM_INPUT("LAUX"),
+ SND_SOC_DAPM_INPUT("RAUX"),
+ SND_SOC_DAPM_INPUT("L2"),
+ SND_SOC_DAPM_INPUT("R2"),
+ SND_SOC_DAPM_OUTPUT("LHP"),
+ SND_SOC_DAPM_OUTPUT("RHP"),
+ SND_SOC_DAPM_OUTPUT("LSPK"),
+ SND_SOC_DAPM_OUTPUT("RSPK"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Output mixer */
+ {"Right Output Mixer", "PCM Playback Switch", "Right DAC"},
+ {"Right Output Mixer", "Aux Playback Switch", "RAUX"},
+ {"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"},
+
+ {"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
+ {"Left Output Mixer", "Aux Playback Switch", "LAUX"},
+ {"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"},
+
+ /* Outputs */
+ {"Right Headphone Out", NULL, "Right Output Mixer"},
+ {"RHP", NULL, "Right Headphone Out"},
+
+ {"Left Headphone Out", NULL, "Left Output Mixer"},
+ {"LHP", NULL, "Left Headphone Out"},
+
+ {"Right Speaker Out", NULL, "Right Output Mixer"},
+ {"RSPK", NULL, "Right Speaker Out"},
+
+ {"Left Speaker Out", NULL, "Left Output Mixer"},
+ {"LSPK", NULL, "Left Speaker Out"},
+
+ /* Boost Mixer */
+ {"Right ADC", NULL, "Right Boost Mixer"},
+
+ {"Right Boost Mixer", NULL, "RAUX"},
+ {"Right Boost Mixer", NULL, "Right Capture PGA"},
+ {"Right Boost Mixer", NULL, "R2"},
+
+ {"Left ADC", NULL, "Left Boost Mixer"},
+
+ {"Left Boost Mixer", NULL, "LAUX"},
+ {"Left Boost Mixer", NULL, "Left Capture PGA"},
+ {"Left Boost Mixer", NULL, "L2"},
+
+ /* Input PGA */
+ {"Right Capture PGA", NULL, "Right Input Mixer"},
+ {"Left Capture PGA", NULL, "Left Input Mixer"},
+
+ {"Right Input Mixer", "R2 Switch", "R2"},
+ {"Right Input Mixer", "MicN Switch", "RMICN"},
+ {"Right Input Mixer", "MicP Switch", "RMICP"},
+
+ {"Left Input Mixer", "L2 Switch", "L2"},
+ {"Left Input Mixer", "MicN Switch", "LMICN"},
+ {"Left Input Mixer", "MicP Switch", "LMICP"},
+};
+
+static int wm8978_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8978_dapm_widgets,
+ ARRAY_SIZE(wm8978_dapm_widgets));
+
+ /* set up the WM8978 audio map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* PLL divisors */
+struct wm8978_pll_div {
+ u32 k;
+ u8 n;
+ u8 div2;
+};
+
+#define FIXED_PLL_SIZE (1 << 24)
+
+static void pll_factors(struct wm8978_pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 k_part;
+ unsigned int k, n_div, n_mod;
+
+ n_div = target / source;
+ if (n_div < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ n_div = target / source;
+ } else {
+ pll_div->div2 = 0;
+ }
+
+ if (n_div < 6 || n_div > 12)
+ dev_warn(wm8978_codec->dev,
+ "WM8978 N value exceeds recommended range! N = %u\n",
+ n_div);
+
+ pll_div->n = n_div;
+ n_mod = target - source * n_div;
+ k_part = FIXED_PLL_SIZE * (long long)n_mod + source / 2;
+
+ do_div(k_part, source);
+
+ k = k_part & 0xFFFFFFFF;
+
+ pll_div->k = k;
+}
+/*
+ * Calculate internal frequencies and dividers, according to Figure 40
+ * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
+ */
+static int wm8978_configure_pll(struct snd_soc_codec *codec)
+{
+ struct wm8978_priv *wm8978 = codec->private_data;
+ struct wm8978_pll_div pll_div;
+ unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk,
+ f_256fs = wm8978->f_256fs;
+ unsigned int f2, opclk_div;
+
+ if (!f_mclk)
+ return -EINVAL;
+
+ if (f_opclk) {
+ /*
+ * The user needs OPCLK. Choose OPCLKDIV to put
+ * 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4.
+ * f_opclk = f_mclk * prescale * R / 4 / OPCLKDIV, where
+ * prescale = 1, or prescale = 2. Prescale is calculated inside
+ * pll_factors(). We have to select f_PLLOUT, such that
+ * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+ * f_mclk * 3 / 16 <= f_opclk < f_mclk * 13 / 4.
+ */
+ if (16 * f_opclk < 3 * f_mclk || 4 * f_opclk >= 13 * f_mclk)
+ return -EINVAL;
+
+ if (4 * f_opclk < 3 * f_mclk)
+ /* Have to use OPCLKDIV */
+ opclk_div = (3 * f_mclk / 4 + f_opclk - 1) / f_opclk;
+ else
+ opclk_div = 1;
+
+ dev_dbg(codec->dev, "%s: OPCLKDIV=%d\n", __func__, opclk_div);
+
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 0x30,
+ (opclk_div - 1) << 4);
+
+ wm8978->f_pllout = f_opclk * opclk_div;
+ } else if (f_256fs) {
+ /*
+ * Not using OPCLK, choose R:
+ * 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12.
+ * f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where
+ * prescale = 1, or prescale = 2. Prescale is calculated inside
+ * pll_factors(). We have to select f_PLLOUT, such that
+ * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+ * f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK
+ * must be 3.781MHz <= f_MCLK <= 32.768MHz
+ */
+ if (48 * f_256fs < 3 * f_mclk || 4 * f_256fs >= 13 * f_mclk)
+ return -EINVAL;
+
+ /*
+ * MCLKDIV will be selected in .hw_params(), just choose a
+ * suitable f_PLLOUT
+ */
+ if (4 * f_256fs < 3 * f_mclk)
+ /* Will have to use MCLKDIV */
+ wm8978->f_pllout = wm8978->f_mclk * 3 / 4;
+ else
+ wm8978->f_pllout = f_256fs;
+
+ /* GPIO1 into default mode as input - before configuring PLL */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+ } else {
+ return -EINVAL;
+ }
+
+ f2 = wm8978->f_pllout * 4;
+
+ dev_dbg(codec->dev, "%s: f_MCLK=%uHz, f_PLLOUT=%uHz\n", __func__,
+ wm8978->f_mclk, wm8978->f_pllout);
+
+ pll_factors(&pll_div, f2, wm8978->f_mclk);
+
+ dev_dbg(codec->dev, "%s: calculated PLL N=0x%x, K=0x%x, div2=%d\n",
+ __func__, pll_div.n, pll_div.k, pll_div.div2);
+
+ /* Turn PLL off for configuration... */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+
+ snd_soc_write(codec, WM8978_PLL_N, (pll_div.div2 << 4) | pll_div.n);
+ snd_soc_write(codec, WM8978_PLL_K1, pll_div.k >> 18);
+ snd_soc_write(codec, WM8978_PLL_K2, (pll_div.k >> 9) & 0x1ff);
+ snd_soc_write(codec, WM8978_PLL_K3, pll_div.k & 0x1ff);
+
+ /* ...and on again */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+ if (f_opclk)
+ /* Output PLL (OPCLK) to GPIO1 */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 4);
+
+ return 0;
+}
+
+/*
+ * Configure WM8978 clock dividers.
+ */
+static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8978_priv *wm8978 = codec->private_data;
+ int ret = 0;
+
+ switch (div_id) {
+ case WM8978_OPCLKRATE:
+ wm8978->f_opclk = div;
+
+ if (wm8978->f_mclk)
+ ret = wm8978_configure_pll(codec);
+ break;
+ case WM8978_MCLKDIV:
+ if (div & ~0xe0)
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, div);
+ break;
+ case WM8978_ADCCLK:
+ if (div & ~8)
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8978_ADC_CONTROL, 8, div);
+ break;
+ case WM8978_DACCLK:
+ if (div & ~8)
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 8, div);
+ break;
+ case WM8978_BCLKDIV:
+ if (div & ~0x1c)
+ return -EINVAL;
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x1c, div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(codec->dev, "%s: ID %d, value %u\n", __func__, div_id, div);
+
+ return ret;
+}
+
+/*
+ * @freq: when .set_pll() us not used, freq is codec MCLK input frequency
+ */
+static int wm8978_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 wm8978_priv *wm8978 = codec->private_data;
+ int ret = 0;
+
+ dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq);
+
+ if (freq) {
+ wm8978->f_mclk = freq;
+
+ /* Even if MCLK is used for system clock, might have to drive OPCLK */
+ if (wm8978->f_opclk)
+ ret = wm8978_configure_pll(codec);
+
+ /* Our sysclk is fixed to 256 * fs, will configure in .hw_params() */
+
+ if (!ret)
+ wm8978->sysclk = clk_id;
+ }
+
+ if (wm8978->sysclk == WM8978_PLL && (!freq || clk_id == WM8978_MCLK)) {
+ /* Clock CODEC directly from MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+
+ /* GPIO1 into default mode as input - before configuring PLL */
+ snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+
+ /* Turn off PLL */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+ wm8978->sysclk = WM8978_MCLK;
+ wm8978->f_pllout = 0;
+ wm8978->f_opclk = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Set ADC and Voice DAC format.
+ */
+static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ /*
+ * BCLK polarity mask = 0x100, LRC clock polarity mask = 0x80,
+ * Data Format mask = 0x18: all will be calculated anew
+ */
+ u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
+ u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
+
+ dev_dbg(codec->dev, "%s\n", __func__);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ clk |= 1;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ clk &= ~1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x10;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x8;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x18;
+ 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 |= 0x180;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x100;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x80;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface);
+ snd_soc_write(codec, WM8978_CLOCKING, clk);
+
+ return 0;
+}
+
+/* MCLK dividers */
+static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12};
+static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1};
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8978_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct wm8978_priv *wm8978 = codec->private_data;
+ /* Word length mask = 0x60 */
+ u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
+ /* Sampling rate mask = 0xe (for filters) */
+ u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
+ u16 clking = snd_soc_read(codec, WM8978_CLOCKING);
+ enum wm8978_sysclk_src current_clk_id = clking & 0x100 ?
+ WM8978_PLL : WM8978_MCLK;
+ unsigned int f_sel, diff, diff_best = INT_MAX;
+ int i, best = 0;
+
+ if (!wm8978->f_mclk)
+ return -EINVAL;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface_ctl |= 0x20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface_ctl |= 0x40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface_ctl |= 0x60;
+ break;
+ }
+
+ /* filter coefficient */
+ switch (params_rate(params)) {
+ case 8000:
+ add_ctl |= 0x5 << 1;
+ break;
+ case 11025:
+ add_ctl |= 0x4 << 1;
+ break;
+ case 16000:
+ add_ctl |= 0x3 << 1;
+ break;
+ case 22050:
+ add_ctl |= 0x2 << 1;
+ break;
+ case 32000:
+ add_ctl |= 0x1 << 1;
+ break;
+ case 44100:
+ case 48000:
+ break;
+ }
+
+ /* Sampling rate is known now, can configure the MCLK divider */
+ wm8978->f_256fs = params_rate(params) * 256;
+
+ if (wm8978->sysclk == WM8978_MCLK) {
+ f_sel = wm8978->f_mclk;
+ } else {
+ if (!wm8978->f_pllout) {
+ int ret = wm8978_configure_pll(codec);
+ if (ret < 0)
+ return ret;
+ }
+ f_sel = wm8978->f_pllout;
+ }
+
+ /*
+ * In some cases it is possible to reconfigure PLL to a higher frequency
+ * by raising OPCLKDIV, but normally OPCLK is configured to 256 * fs or
+ * 512 * fs, so, we should be fine.
+ */
+ if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
+ diff = abs(wm8978->f_256fs * 3 -
+ f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]);
+
+ if (diff < diff_best) {
+ diff_best = diff;
+ best = i;
+ }
+
+ if (!diff)
+ break;
+ }
+
+ if (diff)
+ dev_warn(codec->dev, "Imprecise clock: %u%s\n",
+ f_sel * mclk_denominator[best] / mclk_numerator[best],
+ wm8978->sysclk == WM8978_MCLK ?
+ ", consider using PLL" : "");
+
+ dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
+ params_format(params), params_rate(params), best);
+
+ /* MCLK divisor mask = 0xe0 */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, best << 5);
+
+ snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+ snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+ if (wm8978->sysclk != current_clk_id) {
+ if (wm8978->sysclk == WM8978_PLL)
+ /* Run CODEC from PLL instead of MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING,
+ 0x100, 0x100);
+ else
+ /* Clock CODEC directly from MCLK */
+ snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+ }
+
+ return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+ if (mute)
+ snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0x40);
+ else
+ snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0);
+
+ return 0;
+}
+
+static int wm8978_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ power1 |= 1; /* VMID 75k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* bit 3: enable bias, bit 2: enable I/O tie off buffer */
+ power1 |= 0xc;
+
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ /* Initial cap charge at VMID 5k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
+ power1 | 0x3);
+ mdelay(100);
+ }
+
+ power1 |= 0x2; /* VMID 500k */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* Preserve PLL - OPCLK may be used by someone */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, ~0x20, 0);
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0);
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0);
+ break;
+ }
+
+ dev_dbg(codec->dev, "%s: %d, %x\n", __func__, level, power1);
+
+ codec->bias_level = level;
+ return 0;
+}
+
+#define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8978_dai_ops = {
+ .hw_params = wm8978_hw_params,
+ .digital_mute = wm8978_mute,
+ .set_fmt = wm8978_set_dai_fmt,
+ .set_clkdiv = wm8978_set_dai_clkdiv,
+ .set_sysclk = wm8978_set_dai_sysclk,
+};
+
+/* Also supports 12kHz */
+struct snd_soc_dai wm8978_dai = {
+ .name = "WM8978 HiFi",
+ .id = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8978_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = WM8978_FORMATS,
+ },
+ .ops = &wm8978_dai_ops,
+};
+EXPORT_SYMBOL_GPL(wm8978_dai);
+
+static int wm8978_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ /* Also switch PLL off */
+ snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);
+
+ return 0;
+}
+
+static int wm8978_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct wm8978_priv *wm8978 = codec->private_data;
+ int i;
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
+ if (i == WM8978_RESET)
+ continue;
+ if (cache[i] != wm8978_reg[i])
+ snd_soc_write(codec, i, cache[i]);
+ }
+
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ if (wm8978->f_pllout)
+ /* Switch PLL on */
+ snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+ return 0;
+}
+
+static int wm8978_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ if (wm8978_codec == NULL) {
+ dev_err(&pdev->dev, "Codec device not registered\n");
+ return -ENODEV;
+ }
+
+ socdev->card->codec = wm8978_codec;
+ codec = wm8978_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+ goto pcm_err;
+ }
+
+ snd_soc_add_controls(codec, wm8978_snd_controls,
+ ARRAY_SIZE(wm8978_snd_controls));
+ wm8978_add_widgets(codec);
+
+pcm_err:
+ return ret;
+}
+
+/* power down chip */
+static int wm8978_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_wm8978 = {
+ .probe = wm8978_probe,
+ .remove = wm8978_remove,
+ .suspend = wm8978_suspend,
+ .resume = wm8978_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8978);
+
+/*
+ * These registers contain an "update" bit - bit 8. This means, for example,
+ * that one can write new DAC digital volume for both channels, but only when
+ * the update bit is set, will also the volume be updated - simultaneously for
+ * both channels.
+ */
+static const int update_reg[] = {
+ WM8978_LEFT_DAC_DIGITAL_VOLUME,
+ WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8978_LEFT_ADC_DIGITAL_VOLUME,
+ WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8978_LEFT_INP_PGA_CONTROL,
+ WM8978_RIGHT_INP_PGA_CONTROL,
+ WM8978_LOUT1_HP_CONTROL,
+ WM8978_ROUT1_HP_CONTROL,
+ WM8978_LOUT2_SPK_CONTROL,
+ WM8978_ROUT2_SPK_CONTROL,
+};
+
+static __devinit int wm8978_register(struct wm8978_priv *wm8978)
+{
+ int ret, i;
+ struct snd_soc_codec *codec = &wm8978->codec;
+
+ if (wm8978_codec) {
+ dev_err(codec->dev, "Another WM8978 is registered\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Set default system clock to PLL, it is more precise, this is also the
+ * default hardware setting
+ */
+ wm8978->sysclk = WM8978_PLL;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = wm8978;
+ codec->name = "WM8978";
+ codec->owner = THIS_MODULE;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = wm8978_set_bias_level;
+ codec->dai = &wm8978_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = WM8978_CACHEREGNUM;
+ codec->reg_cache = &wm8978->reg_cache;
+
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ memcpy(codec->reg_cache, wm8978_reg, sizeof(wm8978_reg));
+
+ /*
+ * Set the update bit in all registers, that have one. This way all
+ * writes to those registers will also cause the update bit to be
+ * written.
+ */
+ for (i = 0; i < ARRAY_SIZE(update_reg); i++)
+ ((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100;
+
+ /* Reset the codec */
+ ret = snd_soc_write(codec, WM8978_RESET, 0);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ goto err;
+ }
+
+ wm8978_dai.dev = codec->dev;
+
+ wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ wm8978_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_register_dai(&wm8978_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ goto err_codec;
+ }
+
+ return 0;
+
+err_codec:
+ snd_soc_unregister_codec(codec);
+err:
+ kfree(wm8978);
+ return ret;
+}
+
+static __devexit void wm8978_unregister(struct wm8978_priv *wm8978)
+{
+ wm8978_set_bias_level(&wm8978->codec, SND_SOC_BIAS_OFF);
+ snd_soc_unregister_dai(&wm8978_dai);
+ snd_soc_unregister_codec(&wm8978->codec);
+ kfree(wm8978);
+ wm8978_codec = NULL;
+}
+
+static __devinit int wm8978_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8978_priv *wm8978;
+ struct snd_soc_codec *codec;
+
+ wm8978 = kzalloc(sizeof(struct wm8978_priv), GFP_KERNEL);
+ if (wm8978 == NULL)
+ return -ENOMEM;
+
+ codec = &wm8978->codec;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ i2c_set_clientdata(i2c, wm8978);
+ codec->control_data = i2c;
+
+ codec->dev = &i2c->dev;
+
+ return wm8978_register(wm8978);
+}
+
+static __devexit int wm8978_i2c_remove(struct i2c_client *client)
+{
+ struct wm8978_priv *wm8978 = i2c_get_clientdata(client);
+ wm8978_unregister(wm8978);
+ return 0;
+}
+
+static const struct i2c_device_id wm8978_i2c_id[] = {
+ { "wm8978", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id);
+
+static struct i2c_driver wm8978_i2c_driver = {
+ .driver = {
+ .name = "WM8978",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8978_i2c_probe,
+ .remove = __devexit_p(wm8978_i2c_remove),
+ .id_table = wm8978_i2c_id,
+};
+
+static int __init wm8978_modinit(void)
+{
+ return i2c_add_driver(&wm8978_i2c_driver);
+}
+module_init(wm8978_modinit);
+
+static void __exit wm8978_exit(void)
+{
+ i2c_del_driver(&wm8978_i2c_driver);
+}
+module_exit(wm8978_exit);
+
+MODULE_DESCRIPTION("ASoC WM8978 codec driver");
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8978.h b/sound/soc/codecs/wm8978.h
new file mode 100644
index 0000000..b58f0bf
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,89 @@
+/*
+ * wm8978.h -- codec driver for WM8978
+ *
+ * Copyright 2009 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * 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 __WM8978_H__
+#define __WM8978_H__
+
+/*
+ * Register values.
+ */
+#define WM8978_RESET 0x00
+#define WM8978_POWER_MANAGEMENT_1 0x01
+#define WM8978_POWER_MANAGEMENT_2 0x02
+#define WM8978_POWER_MANAGEMENT_3 0x03
+#define WM8978_AUDIO_INTERFACE 0x04
+#define WM8978_COMPANDING_CONTROL 0x05
+#define WM8978_CLOCKING 0x06
+#define WM8978_ADDITIONAL_CONTROL 0x07
+#define WM8978_GPIO_CONTROL 0x08
+#define WM8978_JACK_DETECT_CONTROL_1 0x09
+#define WM8978_DAC_CONTROL 0x0A
+#define WM8978_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8978_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8978_JACK_DETECT_CONTROL_2 0x0D
+#define WM8978_ADC_CONTROL 0x0E
+#define WM8978_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8978_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8978_EQ1 0x12
+#define WM8978_EQ2 0x13
+#define WM8978_EQ3 0x14
+#define WM8978_EQ4 0x15
+#define WM8978_EQ5 0x16
+#define WM8978_DAC_LIMITER_1 0x18
+#define WM8978_DAC_LIMITER_2 0x19
+#define WM8978_NOTCH_FILTER_1 0x1b
+#define WM8978_NOTCH_FILTER_2 0x1c
+#define WM8978_NOTCH_FILTER_3 0x1d
+#define WM8978_NOTCH_FILTER_4 0x1e
+#define WM8978_ALC_CONTROL_1 0x20
+#define WM8978_ALC_CONTROL_2 0x21
+#define WM8978_ALC_CONTROL_3 0x22
+#define WM8978_NOISE_GATE 0x23
+#define WM8978_PLL_N 0x24
+#define WM8978_PLL_K1 0x25
+#define WM8978_PLL_K2 0x26
+#define WM8978_PLL_K3 0x27
+#define WM8978_3D_CONTROL 0x29
+#define WM8978_BEEP_CONTROL 0x2b
+#define WM8978_INPUT_CONTROL 0x2c
+#define WM8978_LEFT_INP_PGA_CONTROL 0x2d
+#define WM8978_RIGHT_INP_PGA_CONTROL 0x2e
+#define WM8978_LEFT_ADC_BOOST_CONTROL 0x2f
+#define WM8978_RIGHT_ADC_BOOST_CONTROL 0x30
+#define WM8978_OUTPUT_CONTROL 0x31
+#define WM8978_LEFT_MIXER_CONTROL 0x32
+#define WM8978_RIGHT_MIXER_CONTROL 0x33
+#define WM8978_LOUT1_HP_CONTROL 0x34
+#define WM8978_ROUT1_HP_CONTROL 0x35
+#define WM8978_LOUT2_SPK_CONTROL 0x36
+#define WM8978_ROUT2_SPK_CONTROL 0x37
+#define WM8978_OUT3_MIXER_CONTROL 0x38
+#define WM8978_OUT4_MIXER_CONTROL 0x39
+
+#define WM8978_CACHEREGNUM 58
+
+/* Clock divider Id's */
+enum wm8978_clk_id {
+ WM8978_OPCLKRATE,
+ WM8978_MCLKDIV,
+ WM8978_ADCCLK,
+ WM8978_DACCLK,
+ WM8978_BCLKDIV,
+};
+
+enum wm8978_sysclk_src {
+ WM8978_PLL,
+ WM8978_MCLK
+};
+
+extern struct snd_soc_dai wm8978_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8978;
+
+#endif /* __WM8978_H__ */
diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c
index 341481e..a54dc77 100644
--- a/sound/soc/codecs/wm8990.c
+++ b/sound/soc/codecs/wm8990.c
@@ -1319,10 +1319,6 @@
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->card->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;
}
@@ -1335,10 +1331,6 @@
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)
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index 2981afa..828d817 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -227,6 +227,7 @@
int class_w_users;
unsigned int fll_fref;
unsigned int fll_fout;
+ int fll_src;
};
static unsigned int wm8993_read_hw(struct snd_soc_codec *codec, u8 reg)
@@ -506,6 +507,7 @@
wm8993->fll_fref = Fref;
wm8993->fll_fout = Fout;
+ wm8993->fll_src = source;
return 0;
}
@@ -1480,9 +1482,74 @@
return 0;
}
+#ifdef CONFIG_PM
+static int wm8993_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct wm8993_priv *wm8993 = codec->private_data;
+ int fll_fout = wm8993->fll_fout;
+ int fll_fref = wm8993->fll_fref;
+ int ret;
+
+ /* Stop the FLL in an orderly fashion */
+ ret = wm8993_set_fll(codec->dai, 0, 0, 0, 0);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to stop FLL\n");
+ return ret;
+ }
+
+ wm8993->fll_fout = fll_fout;
+ wm8993->fll_fref = fll_fref;
+
+ wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int wm8993_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct wm8993_priv *wm8993 = codec->private_data;
+ u16 *cache = wm8993->reg_cache;
+ int i, ret;
+
+ /* Restore the register settings */
+ for (i = 1; i < WM8993_MAX_REGISTER; i++) {
+ if (cache[i] == wm8993_reg_defaults[i])
+ continue;
+ snd_soc_write(codec, i, cache[i]);
+ }
+
+ wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restart the FLL? */
+ if (wm8993->fll_fout) {
+ int fll_fout = wm8993->fll_fout;
+ int fll_fref = wm8993->fll_fref;
+
+ wm8993->fll_fref = 0;
+ wm8993->fll_fout = 0;
+
+ ret = wm8993_set_fll(codec->dai, 0, wm8993->fll_src,
+ fll_fref, fll_fout);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to restart FLL\n");
+ }
+
+ return 0;
+}
+#else
+#define wm8993_suspend NULL
+#define wm8993_resume NULL
+#endif
+
struct snd_soc_codec_device soc_codec_dev_wm8993 = {
.probe = wm8993_probe,
.remove = wm8993_remove,
+ .suspend = wm8993_suspend,
+ .resume = wm8993_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8993);
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
index c58aab3..ceb86b4 100644
--- a/sound/soc/codecs/wm9713.c
+++ b/sound/soc/codecs/wm9713.c
@@ -23,13 +23,12 @@
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/pcm_params.h>
+#include <sound/tlv.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 */
};
@@ -115,15 +114,27 @@
SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
};
+static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0);
+static unsigned int mic_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
-SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv),
SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
-SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+ out_tlv),
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_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv),
+SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv),
+SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv),
SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
@@ -133,7 +144,7 @@
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_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv),
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),
@@ -154,28 +165,43 @@
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_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv),
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_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv),
-SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1),
+SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv),
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_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv),
-SOC_SINGLE("Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1),
-SOC_SINGLE("Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1),
-SOC_SINGLE("Beep Playback Mono Volume", AC97_AUX, 4, 7, 1),
+SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv),
-SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1),
+SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1,
+ misc_tlv),
SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
+SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1,
+ misc_tlv),
+
+SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1,
+ misc_tlv),
+
+SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1,
+ misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1,
+ misc_tlv),
+
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),
@@ -1186,8 +1212,6 @@
struct snd_soc_codec *codec;
int ret = 0, reg;
- printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION);
-
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec),
GFP_KERNEL);
if (socdev->card->codec == NULL)
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
index d73c305..a67319d 100644
--- a/sound/soc/codecs/wm_hubs.c
+++ b/sound/soc/codecs/wm_hubs.c
@@ -753,6 +753,12 @@
WM8993_LINEOUT2_MODE,
WM8993_LINEOUT2_MODE);
+ /* If the line outputs are differential then we aren't presenting
+ * VMID as an output and can disable it.
+ */
+ if (lineout1_diff && lineout2_diff)
+ codec->idle_bias_off = 1;
+
if (lineout1fb)
snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB);
diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c
index 0a302e1..ab6518d 100644
--- a/sound/soc/davinci/davinci-mcasp.c
+++ b/sound/soc/davinci/davinci-mcasp.c
@@ -767,14 +767,26 @@
int ret = 0;
switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!dev->clk_active) {
+ clk_enable(dev->clk);
+ dev->clk_active = 1;
+ }
davinci_mcasp_start(dev, substream->stream);
break;
- case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
+ davinci_mcasp_stop(dev, substream->stream);
+ if (dev->clk_active) {
+ clk_disable(dev->clk);
+ dev->clk_active = 0;
+ }
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
davinci_mcasp_stop(dev, substream->stream);
break;
@@ -866,6 +878,7 @@
}
clk_enable(dev->clk);
+ dev->clk_active = 1;
dev->base = (void __iomem *)IO_ADDRESS(mem->start);
dev->op_mode = pdata->op_mode;
diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h
index 582c924..e755b51 100644
--- a/sound/soc/davinci/davinci-mcasp.h
+++ b/sound/soc/davinci/davinci-mcasp.h
@@ -44,6 +44,7 @@
int sample_rate;
struct clk *clk;
unsigned int codec_fmt;
+ u8 clk_active;
/* McASP specific data */
int tdm_slots;
diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c
index ad4d7f4..80c7fdf 100644
--- a/sound/soc/davinci/davinci-pcm.c
+++ b/sound/soc/davinci/davinci-pcm.c
@@ -49,7 +49,7 @@
static struct snd_pcm_hardware pcm_hardware_playback = {
.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_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
index a700562..5f006f0 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -1,21 +1,13 @@
-config SND_MX1_MX2_SOC
- tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs"
- depends on ARCH_MX2 || ARCH_MX1
+config SND_IMX_SOC
+ tristate "SoC Audio for Freecale i.MX CPUs"
+ depends on ARCH_MXC && BROKEN
select SND_PCM
+ select FIQ
+ select SND_SOC_AC97_BUS
help
Say Y or M if you want to add support for codecs attached to
- the MX1 or MX2 SSI interface.
+ the i.MX SSI interface.
config SND_MXC_SOC_SSI
tristate
-config SND_SOC_MX27VIS_WM8974
- tristate "SoC Audio support for MX27 - WM8974 Visstrim_sm10 board"
- depends on SND_MX1_MX2_SOC && MACH_MX27 && MACH_IMX27_VISSTRIM_M10
- select SND_MXC_SOC_SSI
- select SND_SOC_WM8974
- help
- Say Y if you want to add support for SoC audio on Visstrim SM10
- board with WM8974.
-
-
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index c2ffd2c..d05cc95 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -1,10 +1,10 @@
# i.MX Platform Support
-snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o
-snd-soc-mxc-ssi-objs := mxc-ssi.o
+snd-soc-imx-objs := imx-ssi.o imx-pcm-fiq.o
-obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o
-obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o
+ifdef CONFIG_MACH_MX27
+snd-soc-imx-objs += imx-pcm-dma-mx2.o
+endif
+
+obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
# i.MX Machine Support
-snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o
-obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o
diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c
new file mode 100644
index 0000000..19452e4
--- /dev/null
+++ b/sound/soc/imx/imx-pcm-dma-mx2.c
@@ -0,0 +1,313 @@
+/*
+ * imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma-mx1-mx2.h>
+
+#include "imx-ssi.h"
+
+struct imx_pcm_runtime_data {
+ int sg_count;
+ struct scatterlist *sg_list;
+ int period;
+ int periods;
+ unsigned long dma_addr;
+ int dma;
+ struct snd_pcm_substream *substream;
+ unsigned long offset;
+ unsigned long size;
+ unsigned long period_cnt;
+ void *buf;
+ int period_time;
+};
+
+/* Called by the DMA framework when a period has elapsed */
+static void imx_ssi_dma_progression(int channel, void *data,
+ struct scatterlist *sg)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ if (!sg)
+ return;
+
+ runtime = iprtd->substream->runtime;
+
+ iprtd->offset = sg->dma_address - runtime->dma_addr;
+
+ snd_pcm_period_elapsed(iprtd->substream);
+}
+
+static void imx_ssi_dma_callback(int channel, void *data)
+{
+ pr_err("%s shouldn't be called\n", __func__);
+}
+
+static void snd_imx_dma_err_callback(int channel, void *data, int err)
+{
+ pr_err("DMA error callback called\n");
+
+ pr_err("DMA timeout on channel %d -%s%s%s%s\n",
+ channel,
+ err & IMX_DMA_ERR_BURST ? " burst" : "",
+ err & IMX_DMA_ERR_REQUEST ? " request" : "",
+ err & IMX_DMA_ERR_TRANSFER ? " transfer" : "",
+ err & IMX_DMA_ERR_BUFFER ? " buffer" : "");
+}
+
+static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ int ret;
+
+ iprtd->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH);
+ if (iprtd->dma < 0) {
+ pr_err("Failed to claim the audio DMA\n");
+ return -ENODEV;
+ }
+
+ ret = imx_dma_setup_handlers(iprtd->dma,
+ imx_ssi_dma_callback,
+ snd_imx_dma_err_callback, substream);
+ if (ret)
+ goto out;
+
+ ret = imx_dma_setup_progression_handler(iprtd->dma,
+ imx_ssi_dma_progression);
+ if (ret) {
+ pr_err("Failed to setup the DMA handler\n");
+ goto out;
+ }
+
+ ret = imx_dma_config_channel(iprtd->dma,
+ IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
+ IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
+ dma_params->dma, 1);
+ if (ret < 0) {
+ pr_err("Cannot configure DMA channel: %d\n", ret);
+ goto out;
+ }
+
+ imx_dma_config_burstlen(iprtd->dma, dma_params->burstsize * 2);
+
+ return 0;
+out:
+ imx_dma_free(iprtd->dma);
+ return ret;
+}
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ int i;
+ unsigned long dma_addr;
+
+ imx_ssi_dma_alloc(substream);
+
+ iprtd->size = params_buffer_bytes(params);
+ iprtd->periods = params_periods(params);
+ iprtd->period = params_period_bytes(params);
+ iprtd->offset = 0;
+ iprtd->period_time = HZ / (params_rate(params) /
+ params_period_size(params));
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ if (iprtd->sg_count != iprtd->periods) {
+ kfree(iprtd->sg_list);
+
+ iprtd->sg_list = kcalloc(iprtd->periods + 1,
+ sizeof(struct scatterlist), GFP_KERNEL);
+ if (!iprtd->sg_list)
+ return -ENOMEM;
+ iprtd->sg_count = iprtd->periods + 1;
+ }
+
+ sg_init_table(iprtd->sg_list, iprtd->sg_count);
+ dma_addr = runtime->dma_addr;
+
+ for (i = 0; i < iprtd->periods; i++) {
+ iprtd->sg_list[i].page_link = 0;
+ iprtd->sg_list[i].offset = 0;
+ iprtd->sg_list[i].dma_address = dma_addr;
+ iprtd->sg_list[i].length = iprtd->period;
+ dma_addr += iprtd->period;
+ }
+
+ /* close the loop */
+ iprtd->sg_list[iprtd->sg_count - 1].offset = 0;
+ iprtd->sg_list[iprtd->sg_count - 1].length = 0;
+ iprtd->sg_list[iprtd->sg_count - 1].page_link =
+ ((unsigned long) iprtd->sg_list | 0x01) & ~0x02;
+ return 0;
+}
+
+static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ if (iprtd->dma >= 0) {
+ imx_dma_free(iprtd->dma);
+ iprtd->dma = -EINVAL;
+ }
+
+ kfree(iprtd->sg_list);
+ iprtd->sg_list = NULL;
+
+ return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ int err;
+
+ iprtd->substream = substream;
+ iprtd->buf = (unsigned int *)substream->dma_buffer.area;
+ iprtd->period_cnt = 0;
+
+ pr_debug("%s: buf: %p period: %d periods: %d\n",
+ __func__, iprtd->buf, iprtd->period, iprtd->periods);
+
+ err = imx_dma_setup_sg(iprtd->dma, iprtd->sg_list, iprtd->sg_count,
+ IMX_DMA_LENGTH_LOOP, dma_params->dma_addr,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DMA_MODE_WRITE : DMA_MODE_READ);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ imx_dma_enable(iprtd->dma);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ imx_dma_disable(iprtd->dma);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_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,
+ .rate_min = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 16 * 1024,
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd;
+ int ret;
+
+ iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+ runtime->private_data = iprtd;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+ return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = snd_imx_open,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_imx_pcm_hw_params,
+ .hw_free = snd_imx_pcm_hw_free,
+ .prepare = snd_imx_pcm_prepare,
+ .trigger = snd_imx_pcm_trigger,
+ .pointer = snd_imx_pcm_pointer,
+ .mmap = snd_imx_pcm_mmap,
+};
+
+static struct snd_soc_platform imx_soc_platform_dma = {
+ .name = "imx-audio",
+ .pcm_ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_new,
+ .pcm_free = imx_pcm_free,
+};
+
+struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
+ struct imx_ssi *ssi)
+{
+ ssi->dma_params_tx.burstsize = DMA_TXFIFO_BURST;
+ ssi->dma_params_rx.burstsize = DMA_RXFIFO_BURST;
+
+ return &imx_soc_platform_dma;
+}
+
diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c
new file mode 100644
index 0000000..5532579
--- /dev/null
+++ b/sound/soc/imx/imx-pcm-fiq.c
@@ -0,0 +1,277 @@
+/*
+ * imx-pcm-fiq.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/fiq.h>
+
+#include <mach/ssi.h>
+
+#include "imx-ssi.h"
+
+struct imx_pcm_runtime_data {
+ int period;
+ int periods;
+ unsigned long dma_addr;
+ int dma;
+ unsigned long offset;
+ unsigned long size;
+ unsigned long period_cnt;
+ void *buf;
+ struct timer_list timer;
+ int period_time;
+};
+
+static void imx_ssi_timer_callback(unsigned long data)
+{
+ struct snd_pcm_substream *substream = (void *)data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ struct pt_regs regs;
+
+ get_fiq_regs(®s);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ iprtd->offset = regs.ARM_r8 & 0xffff;
+ else
+ iprtd->offset = regs.ARM_r9 & 0xffff;
+
+ iprtd->timer.expires = jiffies + iprtd->period_time;
+ add_timer(&iprtd->timer);
+ snd_pcm_period_elapsed(substream);
+}
+
+static struct fiq_handler fh = {
+ .name = DRV_NAME,
+};
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ iprtd->size = params_buffer_bytes(params);
+ iprtd->periods = params_periods(params);
+ iprtd->period = params_period_bytes(params);
+ iprtd->offset = 0;
+ iprtd->period_time = HZ / (params_rate(params) / params_period_size(params));
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+ struct pt_regs regs;
+
+ get_fiq_regs(®s);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
+ else
+ regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
+
+ set_fiq_regs(®s);
+
+ return 0;
+}
+
+static int fiq_enable;
+static int imx_pcm_fiq;
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ iprtd->timer.expires = jiffies + iprtd->period_time;
+ add_timer(&iprtd->timer);
+ if (++fiq_enable == 1)
+ enable_fiq(imx_pcm_fiq);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ del_timer(&iprtd->timer);
+ if (--fiq_enable == 0)
+ disable_fiq(imx_pcm_fiq);
+
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_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,
+ .rate_min = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 16 * 1024,
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd;
+ int ret;
+
+ iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+ runtime->private_data = iprtd;
+
+ init_timer(&iprtd->timer);
+ iprtd->timer.data = (unsigned long)substream;
+ iprtd->timer.function = imx_ssi_timer_callback;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+ return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ del_timer_sync(&iprtd->timer);
+ kfree(iprtd);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = snd_imx_open,
+ .close = snd_imx_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_imx_pcm_hw_params,
+ .prepare = snd_imx_pcm_prepare,
+ .trigger = snd_imx_pcm_trigger,
+ .pointer = snd_imx_pcm_pointer,
+ .mmap = snd_imx_pcm_mmap,
+};
+
+static int imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret;
+
+ ret = imx_pcm_new(card, dai, pcm);
+ if (ret)
+ return ret;
+
+ if (dai->playback.channels_min) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
+ }
+
+ if (dai->capture.channels_min) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
+ }
+
+ set_fiq_handler(&imx_ssi_fiq_start,
+ &imx_ssi_fiq_end - &imx_ssi_fiq_start);
+
+ return 0;
+}
+
+static struct snd_soc_platform imx_soc_platform_fiq = {
+ .pcm_ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_fiq_new,
+ .pcm_free = imx_pcm_free,
+};
+
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
+ struct imx_ssi *ssi)
+{
+ int ret = 0;
+
+ ret = claim_fiq(&fh);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
+ return ERR_PTR(ret);
+ }
+
+ mxc_set_irq_fiq(ssi->irq, 1);
+
+ imx_pcm_fiq = ssi->irq;
+
+ imx_ssi_fiq_base = (unsigned long)ssi->base;
+
+ ssi->dma_params_tx.burstsize = 4;
+ ssi->dma_params_rx.burstsize = 6;
+
+ return &imx_soc_platform_fiq;
+}
+
+void imx_ssi_fiq_exit(struct platform_device *pdev,
+ struct imx_ssi *ssi)
+{
+ mxc_set_irq_fiq(ssi->irq, 0);
+ release_fiq(&fh);
+}
+
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c
new file mode 100644
index 0000000..56f46a7
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.c
@@ -0,0 +1,758 @@
+/*
+ * imx-ssi.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * 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.
+ *
+ *
+ * The i.MX SSI core has some nasty limitations in AC97 mode. While most
+ * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
+ * one FIFO which combines all valid receive slots. We cannot even select
+ * which slots we want to receive. The WM9712 with which this driver
+ * was developped with always sends GPIO status data in slot 12 which
+ * we receive in our (PCM-) data stream. The only chance we have is to
+ * manually skip this data in the FIQ handler. With sampling rates different
+ * from 48000Hz not every frame has valid receive data, so the ratio
+ * between pcm data and GPIO status data changes. Our FIQ handler is not
+ * able to handle this, hence this driver only works with 48000Hz sampling
+ * rate.
+ * Reading and writing AC97 registers is another challange. The core
+ * provides us status bits when the read register is updated with *another*
+ * value. When we read the same register two times (and the register still
+ * contains the same value) these status bits are not set. We work
+ * around this by not polling these bits but only wait a fixed delay.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/ssi.h>
+#include <mach/hardware.h>
+
+#include "imx-ssi.h"
+
+#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
+
+/*
+ * SSI Network Mode or TDM slots configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ u32 sccr;
+
+ sccr = readl(ssi->base + SSI_STCCR);
+ sccr &= ~SSI_STCCR_DC_MASK;
+ sccr |= SSI_STCCR_DC(slots - 1);
+ writel(sccr, ssi->base + SSI_STCCR);
+
+ sccr = readl(ssi->base + SSI_SRCCR);
+ sccr &= ~SSI_STCCR_DC_MASK;
+ sccr |= SSI_STCCR_DC(slots - 1);
+ writel(sccr, ssi->base + SSI_SRCCR);
+
+ writel(tx_mask, ssi->base + SSI_STMSK);
+ writel(rx_mask, ssi->base + SSI_SRMSK);
+
+ return 0;
+}
+
+/*
+ * SSI DAI format configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ * Note: We don't use the I2S modes but instead manually configure the
+ * SSI for I2S because the I2S mode is only a register preset.
+ */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ u32 strcr = 0, scr;
+
+ scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data on rising edge of bclk, frame low 1clk before data */
+ strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+ scr |= SSI_SCR_NET;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data on rising edge of bclk, frame high with data */
+ strcr |= SSI_STCR_TXBIT0;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* data on rising edge of bclk, frame high with data */
+ strcr |= SSI_STCR_TFSL;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* data on rising edge of bclk, frame high 1clk before data */
+ strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ strcr |= SSI_STCR_TFSI;
+ strcr &= ~SSI_STCR_TSCKP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ strcr &= ~SSI_STCR_TFSI;
+ strcr |= SSI_STCR_TSCKP;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ /* Master mode not implemented, needs handling of clocks. */
+ return -EINVAL;
+ }
+
+ strcr |= SSI_STCR_TFEN0;
+
+ writel(strcr, ssi->base + SSI_STCR);
+ writel(strcr, ssi->base + SSI_SRCR);
+ writel(scr, ssi->base + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ u32 scr;
+
+ scr = readl(ssi->base + SSI_SCR);
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ if (dir == SND_SOC_CLOCK_OUT)
+ scr |= SSI_SCR_SYS_CLK_EN;
+ else
+ scr &= ~SSI_SCR_SYS_CLK_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(scr, ssi->base + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ u32 stccr, srccr;
+
+ stccr = readl(ssi->base + SSI_STCCR);
+ srccr = readl(ssi->base + SSI_SRCCR);
+
+ switch (div_id) {
+ case IMX_SSI_TX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ case IMX_SSI_RX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(stccr, ssi->base + SSI_STCCR);
+ writel(srccr, ssi->base + SSI_SRCCR);
+
+ return 0;
+}
+
+/*
+ * Should only be called when port is inactive (i.e. SSIEN = 0),
+ * although can be called multiple times by upper layers.
+ */
+static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ u32 reg, sccr;
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg = SSI_STCCR;
+ cpu_dai->dma_data = &ssi->dma_params_tx;
+ } else {
+ reg = SSI_SRCCR;
+ cpu_dai->dma_data = &ssi->dma_params_rx;
+ }
+
+ sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ sccr |= SSI_SRCCR_WL(16);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ sccr |= SSI_SRCCR_WL(20);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ sccr |= SSI_SRCCR_WL(24);
+ break;
+ }
+
+ writel(sccr, ssi->base + reg);
+
+ return 0;
+}
+
+static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ struct imx_ssi *ssi = cpu_dai->private_data;
+ unsigned int sier_bits, sier;
+ unsigned int scr;
+
+ scr = readl(ssi->base + SSI_SCR);
+ sier = readl(ssi->base + SSI_SIER);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (ssi->flags & IMX_SSI_DMA)
+ sier_bits = SSI_SIER_TDMAE;
+ else
+ sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
+ } else {
+ if (ssi->flags & IMX_SSI_DMA)
+ sier_bits = SSI_SIER_RDMAE;
+ else
+ sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
+ }
+
+ 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)
+ scr |= SSI_SCR_TE;
+ else
+ scr |= SSI_SCR_RE;
+ sier |= sier_bits;
+
+ if (++ssi->enabled == 1)
+ scr |= SSI_SCR_SSIEN;
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ scr &= ~SSI_SCR_TE;
+ else
+ scr &= ~SSI_SCR_RE;
+ sier &= ~sier_bits;
+
+ if (--ssi->enabled == 0)
+ scr &= ~SSI_SCR_SSIEN;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(ssi->flags & IMX_SSI_USE_AC97))
+ /* rx/tx are always enabled to access ac97 registers */
+ writel(scr, ssi->base + SSI_SCR);
+
+ writel(sier, ssi->base + SSI_SIER);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
+ .hw_params = imx_ssi_hw_params,
+ .set_fmt = imx_ssi_set_dai_fmt,
+ .set_clkdiv = imx_ssi_set_dai_clkdiv,
+ .set_sysclk = imx_ssi_set_dai_sysclk,
+ .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
+ .trigger = imx_ssi_trigger,
+};
+
+static struct snd_soc_dai imx_ssi_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &imx_ssi_pcm_dai_ops,
+};
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+
+ pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+ return ret;
+}
+
+static int imx_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 = IMX_SSI_DMABUF_SIZE;
+
+ 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 u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int imx_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 = &imx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+ if (dai->playback.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+void imx_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;
+ }
+}
+
+struct snd_soc_platform imx_soc_platform = {
+ .name = "imx-audio",
+};
+EXPORT_SYMBOL_GPL(imx_soc_platform);
+
+static struct snd_soc_dai imx_ac97_dai = {
+ .name = "AC97",
+ .ac97_control = 1,
+ .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,
+ },
+ .ops = &imx_ssi_pcm_dai_ops,
+};
+
+static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
+{
+ void __iomem *base = imx_ssi->base;
+
+ writel(0x0, base + SSI_SCR);
+ writel(0x0, base + SSI_STCR);
+ writel(0x0, base + SSI_SRCR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
+
+ writel(SSI_SFCSR_RFWM0(8) |
+ SSI_SFCSR_TFWM0(8) |
+ SSI_SFCSR_RFWM1(8) |
+ SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
+
+ writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
+ writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
+ writel(SSI_SOR_WAIT(3), base + SSI_SOR);
+
+ writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
+ SSI_SCR_TE | SSI_SCR_RE,
+ base + SSI_SCR);
+
+ writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
+ writel(0xff, base + SSI_SACCDIS);
+ writel(0x300, base + SSI_SACCEN);
+}
+
+static struct imx_ssi *ac97_ssi;
+
+static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+ void __iomem *base = imx_ssi->base;
+ unsigned int lreg;
+ unsigned int lval;
+
+ if (reg > 0x7f)
+ return;
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ lreg = reg << 12;
+ writel(lreg, base + SSI_SACADD);
+
+ lval = val << 4;
+ writel(lval , base + SSI_SACDAT);
+
+ writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
+ udelay(100);
+}
+
+static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+ void __iomem *base = imx_ssi->base;
+
+ unsigned short val = -1;
+ unsigned int lreg;
+
+ lreg = (reg & 0x7f) << 12 ;
+ writel(lreg, base + SSI_SACADD);
+ writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
+
+ udelay(100);
+
+ val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ return val;
+}
+
+static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+
+ if (imx_ssi->ac97_reset)
+ imx_ssi->ac97_reset(ac97);
+}
+
+static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ struct imx_ssi *imx_ssi = ac97_ssi;
+
+ if (imx_ssi->ac97_warm_reset)
+ imx_ssi->ac97_warm_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = imx_ssi_ac97_read,
+ .write = imx_ssi_ac97_write,
+ .reset = imx_ssi_ac97_reset,
+ .warm_reset = imx_ssi_ac97_warm_reset
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+struct snd_soc_dai imx_ssi_pcm_dai[2];
+EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
+
+static int imx_ssi_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct imx_ssi *ssi;
+ struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
+ struct snd_soc_platform *platform;
+ int ret = 0;
+ unsigned int val;
+ struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
+
+ if (dai->id >= ARRAY_SIZE(imx_ssi_pcm_dai))
+ return -EINVAL;
+
+ ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
+ if (!ssi)
+ return -ENOMEM;
+
+ if (pdata) {
+ ssi->ac97_reset = pdata->ac97_reset;
+ ssi->ac97_warm_reset = pdata->ac97_warm_reset;
+ ssi->flags = pdata->flags;
+ }
+
+ ssi->irq = platform_get_irq(pdev, 0);
+
+ ssi->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ssi->clk)) {
+ ret = PTR_ERR(ssi->clk);
+ dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+ ret);
+ goto failed_clk;
+ }
+ clk_enable(ssi->clk);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto failed_get_resource;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ ret = -EBUSY;
+ goto failed_get_resource;
+ }
+
+ ssi->base = ioremap(res->start, resource_size(res));
+ if (!ssi->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENODEV;
+ goto failed_ioremap;
+ }
+
+ if (ssi->flags & IMX_SSI_USE_AC97) {
+ if (ac97_ssi) {
+ ret = -EBUSY;
+ goto failed_ac97;
+ }
+ ac97_ssi = ssi;
+ setup_channel_to_ac97(ssi);
+ memcpy(dai, &imx_ac97_dai, sizeof(imx_ac97_dai));
+ } else
+ memcpy(dai, &imx_ssi_dai, sizeof(imx_ssi_dai));
+
+ writel(0x0, ssi->base + SSI_SIER);
+
+ ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
+ ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+ if (res)
+ ssi->dma_params_tx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+ if (res)
+ ssi->dma_params_rx.dma = res->start;
+
+ dai->id = pdev->id;
+ dai->dev = &pdev->dev;
+ dai->name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
+ dai->private_data = ssi;
+
+ if ((cpu_is_mx27() || cpu_is_mx21()) &&
+ !(ssi->flags & IMX_SSI_USE_AC97)) {
+ ssi->flags |= IMX_SSI_DMA;
+ platform = imx_ssi_dma_mx2_init(pdev, ssi);
+ } else
+ platform = imx_ssi_fiq_init(pdev, ssi);
+
+ imx_soc_platform.pcm_ops = platform->pcm_ops;
+ imx_soc_platform.pcm_new = platform->pcm_new;
+ imx_soc_platform.pcm_free = platform->pcm_free;
+
+ val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
+ SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
+ writel(val, ssi->base + SSI_SFCSR);
+
+ ret = snd_soc_register_dai(dai);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto failed_register;
+ }
+
+ platform_set_drvdata(pdev, ssi);
+
+ return 0;
+
+failed_register:
+failed_ac97:
+ iounmap(ssi->base);
+failed_ioremap:
+ release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+ clk_disable(ssi->clk);
+ clk_put(ssi->clk);
+failed_clk:
+ kfree(ssi);
+
+ return ret;
+}
+
+static int __devexit imx_ssi_remove(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct imx_ssi *ssi = platform_get_drvdata(pdev);
+ struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
+
+ snd_soc_unregister_dai(dai);
+
+ if (ssi->flags & IMX_SSI_USE_AC97)
+ ac97_ssi = NULL;
+
+ if (!(ssi->flags & IMX_SSI_DMA))
+ imx_ssi_fiq_exit(pdev, ssi);
+
+ iounmap(ssi->base);
+ release_mem_region(res->start, resource_size(res));
+ clk_disable(ssi->clk);
+ clk_put(ssi->clk);
+ kfree(ssi);
+
+ return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+ .probe = imx_ssi_probe,
+ .remove = __devexit_p(imx_ssi_remove),
+
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init imx_ssi_init(void)
+{
+ int ret;
+
+ ret = snd_soc_register_platform(&imx_soc_platform);
+ if (ret) {
+ pr_err("failed to register soc platform: %d\n", ret);
+ return ret;
+ }
+
+ ret = platform_driver_register(&imx_ssi_driver);
+ if (ret) {
+ snd_soc_unregister_platform(&imx_soc_platform);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit imx_ssi_exit(void)
+{
+ platform_driver_unregister(&imx_ssi_driver);
+ snd_soc_unregister_platform(&imx_soc_platform);
+}
+
+module_init(imx_ssi_init);
+module_exit(imx_ssi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h
new file mode 100644
index 0000000..55f26eb
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.h
@@ -0,0 +1,237 @@
+/*
+ * 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 _IMX_SSI_H
+#define _IMX_SSI_H
+
+#define SSI_STX0 0x00
+#define SSI_STX1 0x04
+#define SSI_SRX0 0x08
+#define SSI_SRX1 0x0c
+
+#define SSI_SCR 0x10
+#define SSI_SCR_CLK_IST (1 << 9)
+#define SSI_SCR_CLK_IST_SHIFT 9
+#define SSI_SCR_TCH_EN (1 << 8)
+#define SSI_SCR_SYS_CLK_EN (1 << 7)
+#define SSI_SCR_I2S_MODE_NORM (0 << 5)
+#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
+#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
+#define SSI_I2S_MODE_MASK (3 << 5)
+#define SSI_SCR_SYN (1 << 4)
+#define SSI_SCR_NET (1 << 3)
+#define SSI_SCR_RE (1 << 2)
+#define SSI_SCR_TE (1 << 1)
+#define SSI_SCR_SSIEN (1 << 0)
+
+#define SSI_SISR 0x14
+#define SSI_SISR_MASK ((1 << 19) - 1)
+#define SSI_SISR_CMDAU (1 << 18)
+#define SSI_SISR_CMDDU (1 << 17)
+#define SSI_SISR_RXT (1 << 16)
+#define SSI_SISR_RDR1 (1 << 15)
+#define SSI_SISR_RDR0 (1 << 14)
+#define SSI_SISR_TDE1 (1 << 13)
+#define SSI_SISR_TDE0 (1 << 12)
+#define SSI_SISR_ROE1 (1 << 11)
+#define SSI_SISR_ROE0 (1 << 10)
+#define SSI_SISR_TUE1 (1 << 9)
+#define SSI_SISR_TUE0 (1 << 8)
+#define SSI_SISR_TFS (1 << 7)
+#define SSI_SISR_RFS (1 << 6)
+#define SSI_SISR_TLS (1 << 5)
+#define SSI_SISR_RLS (1 << 4)
+#define SSI_SISR_RFF1 (1 << 3)
+#define SSI_SISR_RFF0 (1 << 2)
+#define SSI_SISR_TFE1 (1 << 1)
+#define SSI_SISR_TFE0 (1 << 0)
+
+#define SSI_SIER 0x18
+#define SSI_SIER_RDMAE (1 << 22)
+#define SSI_SIER_RIE (1 << 21)
+#define SSI_SIER_TDMAE (1 << 20)
+#define SSI_SIER_TIE (1 << 19)
+#define SSI_SIER_CMDAU_EN (1 << 18)
+#define SSI_SIER_CMDDU_EN (1 << 17)
+#define SSI_SIER_RXT_EN (1 << 16)
+#define SSI_SIER_RDR1_EN (1 << 15)
+#define SSI_SIER_RDR0_EN (1 << 14)
+#define SSI_SIER_TDE1_EN (1 << 13)
+#define SSI_SIER_TDE0_EN (1 << 12)
+#define SSI_SIER_ROE1_EN (1 << 11)
+#define SSI_SIER_ROE0_EN (1 << 10)
+#define SSI_SIER_TUE1_EN (1 << 9)
+#define SSI_SIER_TUE0_EN (1 << 8)
+#define SSI_SIER_TFS_EN (1 << 7)
+#define SSI_SIER_RFS_EN (1 << 6)
+#define SSI_SIER_TLS_EN (1 << 5)
+#define SSI_SIER_RLS_EN (1 << 4)
+#define SSI_SIER_RFF1_EN (1 << 3)
+#define SSI_SIER_RFF0_EN (1 << 2)
+#define SSI_SIER_TFE1_EN (1 << 1)
+#define SSI_SIER_TFE0_EN (1 << 0)
+
+#define SSI_STCR 0x1c
+#define SSI_STCR_TXBIT0 (1 << 9)
+#define SSI_STCR_TFEN1 (1 << 8)
+#define SSI_STCR_TFEN0 (1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_STCR_TFDIR (1 << 6)
+#define SSI_STCR_TXDIR (1 << 5)
+#define SSI_STCR_TSHFD (1 << 4)
+#define SSI_STCR_TSCKP (1 << 3)
+#define SSI_STCR_TFSI (1 << 2)
+#define SSI_STCR_TFSL (1 << 1)
+#define SSI_STCR_TEFS (1 << 0)
+
+#define SSI_SRCR 0x20
+#define SSI_SRCR_RXBIT0 (1 << 9)
+#define SSI_SRCR_RFEN1 (1 << 8)
+#define SSI_SRCR_RFEN0 (1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_SRCR_RFDIR (1 << 6)
+#define SSI_SRCR_RXDIR (1 << 5)
+#define SSI_SRCR_RSHFD (1 << 4)
+#define SSI_SRCR_RSCKP (1 << 3)
+#define SSI_SRCR_RFSI (1 << 2)
+#define SSI_SRCR_RFSL (1 << 1)
+#define SSI_SRCR_REFS (1 << 0)
+
+#define SSI_SRCCR 0x28
+#define SSI_SRCCR_DIV2 (1 << 18)
+#define SSI_SRCCR_PSR (1 << 17)
+#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_SRCCR_WL_MASK (0xf << 13)
+#define SSI_SRCCR_DC_MASK (0x1f << 8)
+#define SSI_SRCCR_PM_MASK (0xff << 0)
+
+#define SSI_STCCR 0x24
+#define SSI_STCCR_DIV2 (1 << 18)
+#define SSI_STCCR_PSR (1 << 17)
+#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_STCCR_WL_MASK (0xf << 13)
+#define SSI_STCCR_DC_MASK (0x1f << 8)
+#define SSI_STCCR_PM_MASK (0xff << 0)
+
+#define SSI_SFCSR 0x2c
+#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
+#define SSI_RX_FIFO_1_COUNT_SHIFT 28
+#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
+#define SSI_TX_FIFO_1_COUNT_SHIFT 24
+#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
+#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
+#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
+#define SSI_RX_FIFO_0_COUNT_SHIFT 12
+#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
+#define SSI_TX_FIFO_0_COUNT_SHIFT 8
+#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
+#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
+#define SSI_SFCSR_RFWM0_MASK (0xf << 4)
+#define SSI_SFCSR_TFWM0_MASK (0xf << 0)
+
+#define SSI_STR 0x30
+#define SSI_STR_TEST (1 << 15)
+#define SSI_STR_RCK2TCK (1 << 14)
+#define SSI_STR_RFS2TFS (1 << 13)
+#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
+#define SSI_STR_TXD2RXD (1 << 7)
+#define SSI_STR_TCK2RCK (1 << 6)
+#define SSI_STR_TFS2RFS (1 << 5)
+#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
+
+#define SSI_SOR 0x34
+#define SSI_SOR_CLKOFF (1 << 6)
+#define SSI_SOR_RX_CLR (1 << 5)
+#define SSI_SOR_TX_CLR (1 << 4)
+#define SSI_SOR_INIT (1 << 3)
+#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
+#define SSI_SOR_WAIT_MASK (0x3 << 1)
+#define SSI_SOR_SYNRST (1 << 0)
+
+#define SSI_SACNT 0x38
+#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
+#define SSI_SACNT_WR (1 << 4)
+#define SSI_SACNT_RD (1 << 3)
+#define SSI_SACNT_TIF (1 << 2)
+#define SSI_SACNT_FV (1 << 1)
+#define SSI_SACNT_AC97EN (1 << 0)
+
+#define SSI_SACADD 0x3c
+#define SSI_SACDAT 0x40
+#define SSI_SATAG 0x44
+#define SSI_STMSK 0x48
+#define SSI_SRMSK 0x4c
+#define SSI_SACCST 0x50
+#define SSI_SACCEN 0x54
+#define SSI_SACCDIS 0x58
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK 0
+
+/* SSI audio dividers */
+#define IMX_SSI_TX_DIV_2 0
+#define IMX_SSI_TX_DIV_PSR 1
+#define IMX_SSI_TX_DIV_PM 2
+#define IMX_SSI_RX_DIV_2 3
+#define IMX_SSI_RX_DIV_PSR 4
+#define IMX_SSI_RX_DIV_PM 5
+
+extern struct snd_soc_dai imx_ssi_pcm_dai[2];
+extern struct snd_soc_platform imx_soc_platform;
+
+#define DRV_NAME "imx-ssi"
+
+struct imx_pcm_dma_params {
+ int dma;
+ unsigned long dma_addr;
+ int burstsize;
+};
+
+struct imx_ssi {
+ struct platform_device *ac97_dev;
+
+ struct snd_soc_device imx_ac97;
+ struct clk *clk;
+ void __iomem *base;
+ int irq;
+ int fiq_enable;
+ unsigned int offset;
+
+ unsigned int flags;
+
+ void (*ac97_reset) (struct snd_ac97 *ac97);
+ void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
+ struct imx_pcm_dma_params dma_params_rx;
+ struct imx_pcm_dma_params dma_params_tx;
+
+ int enabled;
+};
+
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
+ struct imx_ssi *ssi);
+void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi);
+struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
+ struct imx_ssi *ssi);
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
+int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm);
+void imx_pcm_free(struct snd_pcm *pcm);
+
+/*
+ * Do not change this as the FIQ handler depends on this size
+ */
+#define IMX_SSI_DMABUF_SIZE (64 * 1024)
+
+#define DMA_RXFIFO_BURST 0x4
+#define DMA_TXFIFO_BURST 0x6
+
+#endif /* _IMX_SSI_H */
diff --git a/sound/soc/imx/mx1_mx2-pcm.c b/sound/soc/imx/mx1_mx2-pcm.c
deleted file mode 100644
index bffffcd..0000000
--- a/sound/soc/imx/mx1_mx2-pcm.c
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * mx1_mx2-pcm.c -- ALSA SoC interface for Freescale i.MX1x, i.MX2x CPUs
- *
- * Copyright 2009 Vista Silicon S.L.
- * Author: Javier Martin
- * javier.martin@vista-silicon.com
- *
- * Based on mxc-pcm.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/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 <mach/hardware.h>
-#include <mach/dma-mx1-mx2.h>
-
-#include "mx1_mx2-pcm.h"
-
-
-static const struct snd_pcm_hardware mx1_mx2_pcm_hardware = {
- .info = (SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_BLOCK_TRANSFER |
- SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID),
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- .buffer_bytes_max = 32 * 1024,
- .period_bytes_min = 64,
- .period_bytes_max = 8 * 1024,
- .periods_min = 2,
- .periods_max = 255,
- .fifo_size = 0,
-};
-
-struct mx1_mx2_runtime_data {
- int dma_ch;
- int active;
- unsigned int period;
- unsigned int periods;
- int tx_spin;
- spinlock_t dma_lock;
- struct mx1_mx2_pcm_dma_params *dma_params;
-};
-
-
-/**
- * This function stops the current dma transfer for playback
- * and clears the dma pointers.
- *
- * @param substream pointer to the structure of the current stream.
- *
- */
-static int audio_stop_dma(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
- unsigned long flags;
-
- spin_lock_irqsave(&prtd->dma_lock, flags);
-
- pr_debug("%s\n", __func__);
-
- prtd->active = 0;
- prtd->period = 0;
- prtd->periods = 0;
-
- /* this stops the dma channel and clears the buffer ptrs */
-
- imx_dma_disable(prtd->dma_ch);
-
- spin_unlock_irqrestore(&prtd->dma_lock, flags);
-
- return 0;
-}
-
-/**
- * This function is called whenever a new audio block needs to be
- * transferred to the codec. The function receives the address and the size
- * of the new block and start a new DMA transfer.
- *
- * @param substream pointer to the structure of the current stream.
- *
- */
-static int dma_new_period(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
- unsigned int dma_size;
- unsigned int offset;
- int ret = 0;
- dma_addr_t mem_addr;
- unsigned int dev_addr;
-
- if (prtd->active) {
- dma_size = frames_to_bytes(runtime, runtime->period_size);
- offset = dma_size * prtd->period;
-
- pr_debug("%s: period (%d) out of (%d)\n", __func__,
- prtd->period,
- runtime->periods);
- pr_debug("period_size %d frames\n offset %d bytes\n",
- (unsigned int)runtime->period_size,
- offset);
- pr_debug("dma_size %d bytes\n", dma_size);
-
- snd_BUG_ON(dma_size > mx1_mx2_pcm_hardware.period_bytes_max);
-
- mem_addr = (dma_addr_t)(runtime->dma_addr + offset);
- dev_addr = prtd->dma_params->per_address;
- pr_debug("%s: mem_addr is %x\n dev_addr is %x\n",
- __func__, mem_addr, dev_addr);
-
- ret = imx_dma_setup_single(prtd->dma_ch, mem_addr,
- dma_size, dev_addr,
- prtd->dma_params->transfer_type);
- if (ret < 0) {
- printk(KERN_ERR "Error %d configuring DMA\n", ret);
- return ret;
- }
- imx_dma_enable(prtd->dma_ch);
-
- pr_debug("%s: transfer enabled\nmem_addr = %x\n",
- __func__, (unsigned int) mem_addr);
- pr_debug("dev_addr = %x\ndma_size = %d\n",
- (unsigned int) dev_addr, dma_size);
-
- prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
- prtd->period++;
- prtd->period %= runtime->periods;
- }
- return ret;
-}
-
-
-/**
- * This is a callback which will be called
- * when a TX transfer finishes. The call occurs
- * in interrupt context.
- *
- * @param dat pointer to the structure of the current stream.
- *
- */
-static void audio_dma_irq(int channel, void *data)
-{
- struct snd_pcm_substream *substream;
- struct snd_pcm_runtime *runtime;
- struct mx1_mx2_runtime_data *prtd;
- unsigned int dma_size;
- unsigned int previous_period;
- unsigned int offset;
-
- substream = data;
- runtime = substream->runtime;
- prtd = runtime->private_data;
- previous_period = prtd->periods;
- dma_size = frames_to_bytes(runtime, runtime->period_size);
- offset = dma_size * previous_period;
-
- prtd->tx_spin = 0;
- prtd->periods++;
- prtd->periods %= runtime->periods;
-
- pr_debug("%s: irq per %d offset %x\n", __func__, prtd->periods, offset);
-
- /*
- * If we are getting a callback for an active stream then we inform
- * the PCM middle layer we've finished a period
- */
- if (prtd->active)
- snd_pcm_period_elapsed(substream);
-
- /*
- * Trig next DMA transfer
- */
- dma_new_period(substream);
-}
-
-/**
- * This function configures the hardware to allow audio
- * playback operations. It is called by ALSA framework.
- *
- * @param substream pointer to the structure of the current stream.
- *
- * @return 0 on success, -1 otherwise.
- */
-static int
-snd_mx1_mx2_prepare(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
-
- prtd->period = 0;
- prtd->periods = 0;
-
- return 0;
-}
-
-static int mx1_mx2_pcm_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *hw_params)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- int ret;
-
- ret = snd_pcm_lib_malloc_pages(substream,
- params_buffer_bytes(hw_params));
- if (ret < 0) {
- printk(KERN_ERR "%s: Error %d failed to malloc pcm pages \n",
- __func__, ret);
- return ret;
- }
-
- pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n",
- __func__, (unsigned int)runtime->dma_addr);
- pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n",
- __func__, (unsigned int)runtime->dma_area);
- pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n",
- __func__, (unsigned int)runtime->dma_bytes);
-
- return ret;
-}
-
-static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
-
- imx_dma_free(prtd->dma_ch);
-
- snd_pcm_lib_free_pages(substream);
-
- return 0;
-}
-
-static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
-{
- struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data;
- int ret = 0;
-
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- prtd->tx_spin = 0;
- /* requested stream startup */
- prtd->active = 1;
- pr_debug("%s: starting dma_new_period\n", __func__);
- ret = dma_new_period(substream);
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- /* requested stream shutdown */
- pr_debug("%s: stopping dma transfer\n", __func__);
- ret = audio_stop_dma(substream);
- break;
- default:
- ret = -EINVAL;
- break;
- }
-
- return ret;
-}
-
-static snd_pcm_uframes_t
-mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
- unsigned int offset = 0;
-
- /* tx_spin value is used here to check if a transfer is active */
- if (prtd->tx_spin) {
- offset = (runtime->period_size * (prtd->periods)) +
- (runtime->period_size >> 1);
- if (offset >= runtime->buffer_size)
- offset = runtime->period_size >> 1;
- } else {
- offset = (runtime->period_size * (prtd->periods));
- if (offset >= runtime->buffer_size)
- offset = 0;
- }
- pr_debug("%s: pointer offset %x\n", __func__, offset);
-
- return offset;
-}
-
-static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
- int ret;
-
- snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware);
-
- ret = snd_pcm_hw_constraint_integer(runtime,
- SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0)
- return ret;
-
- prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL);
- if (prtd == NULL) {
- ret = -ENOMEM;
- goto out;
- }
-
- runtime->private_data = prtd;
-
- if (!dma_data)
- return -ENODEV;
-
- prtd->dma_params = dma_data;
-
- pr_debug("%s: Requesting dma channel (%s)\n", __func__,
- prtd->dma_params->name);
- ret = imx_dma_request_by_prio(prtd->dma_params->name, DMA_PRIO_HIGH);
- if (ret < 0) {
- printk(KERN_ERR "Error %d requesting dma channel\n", ret);
- return ret;
- }
- prtd->dma_ch = ret;
- imx_dma_config_burstlen(prtd->dma_ch,
- prtd->dma_params->watermark_level);
-
- ret = imx_dma_config_channel(prtd->dma_ch,
- prtd->dma_params->per_config,
- prtd->dma_params->mem_config,
- prtd->dma_params->event_id, 0);
-
- if (ret) {
- pr_debug(KERN_ERR "Error %d configuring dma channel %d\n",
- ret, prtd->dma_ch);
- return ret;
- }
-
- pr_debug("%s: Setting tx dma callback function\n", __func__);
- ret = imx_dma_setup_handlers(prtd->dma_ch,
- audio_dma_irq, NULL,
- (void *)substream);
- if (ret < 0) {
- printk(KERN_ERR "Error %d setting dma callback function\n", ret);
- return ret;
- }
- return 0;
-
- out:
- return ret;
-}
-
-static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct mx1_mx2_runtime_data *prtd = runtime->private_data;
-
- kfree(prtd);
-
- return 0;
-}
-
-static int mx1_mx2_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);
-}
-
-static struct snd_pcm_ops mx1_mx2_pcm_ops = {
- .open = mx1_mx2_pcm_open,
- .close = mx1_mx2_pcm_close,
- .ioctl = snd_pcm_lib_ioctl,
- .hw_params = mx1_mx2_pcm_hw_params,
- .hw_free = mx1_mx2_pcm_hw_free,
- .prepare = snd_mx1_mx2_prepare,
- .trigger = mx1_mx2_pcm_trigger,
- .pointer = mx1_mx2_pcm_pointer,
- .mmap = mx1_mx2_pcm_mmap,
-};
-
-static u64 mx1_mx2_pcm_dmamask = 0xffffffff;
-
-static int mx1_mx2_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 = mx1_mx2_pcm_hardware.buffer_bytes_max;
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
-
- /* Reserve uncached-buffered memory area for DMA */
- buf->area = dma_alloc_writecombine(pcm->card->dev, size,
- &buf->addr, GFP_KERNEL);
-
- pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
- __func__, (void *) buf->area, (void *) buf->addr, size);
-
- if (!buf->area)
- return -ENOMEM;
-
- buf->bytes = size;
- return 0;
-}
-
-static void mx1_mx2_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;
- }
-}
-
-static int mx1_mx2_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 = &mx1_mx2_pcm_dmamask;
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = 0xffffffff;
-
- if (dai->playback.channels_min) {
- ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_PLAYBACK);
- pr_debug("%s: preallocate playback buffer\n", __func__);
- if (ret)
- goto out;
- }
-
- if (dai->capture.channels_min) {
- ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_CAPTURE);
- pr_debug("%s: preallocate capture buffer\n", __func__);
- if (ret)
- goto out;
- }
- out:
- return ret;
-}
-
-struct snd_soc_platform mx1_mx2_soc_platform = {
- .name = "mx1_mx2-audio",
- .pcm_ops = &mx1_mx2_pcm_ops,
- .pcm_new = mx1_mx2_pcm_new,
- .pcm_free = mx1_mx2_pcm_free_dma_buffers,
-};
-EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform);
-
-static int __init mx1_mx2_soc_platform_init(void)
-{
- return snd_soc_register_platform(&mx1_mx2_soc_platform);
-}
-module_init(mx1_mx2_soc_platform_init);
-
-static void __exit mx1_mx2_soc_platform_exit(void)
-{
- snd_soc_unregister_platform(&mx1_mx2_soc_platform);
-}
-module_exit(mx1_mx2_soc_platform_exit);
-
-MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
-MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module");
-MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/mx1_mx2-pcm.h b/sound/soc/imx/mx1_mx2-pcm.h
deleted file mode 100644
index 2e52810..0000000
--- a/sound/soc/imx/mx1_mx2-pcm.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * mx1_mx2-pcm.h :- ASoC platform header for Freescale i.MX1x, i.MX2x
- *
- * 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 _MX1_MX2_PCM_H
-#define _MX1_MX2_PCM_H
-
-/* DMA information for mx1_mx2 platforms */
-struct mx1_mx2_pcm_dma_params {
- char *name; /* stream identifier */
- unsigned int transfer_type; /* READ or WRITE DMA transfer */
- dma_addr_t per_address; /* physical address of SSI fifo */
- int event_id; /* fixed DMA number for SSI fifo */
- int watermark_level; /* SSI fifo watermark level */
- int per_config; /* DMA Config flags for peripheral */
- int mem_config; /* DMA Config flags for RAM */
- };
-
-/* platform data */
-extern struct snd_soc_platform mx1_mx2_soc_platform;
-
-#endif
diff --git a/sound/soc/imx/mx27vis_wm8974.c b/sound/soc/imx/mx27vis_wm8974.c
deleted file mode 100644
index 07d2a24..0000000
--- a/sound/soc/imx/mx27vis_wm8974.c
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * mx27vis_wm8974.c -- SoC audio for mx27vis
- *
- * Copyright 2009 Vista Silicon S.L.
- * Author: Javier Martin
- * javier.martin@vista-silicon.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 <linux/i2c.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/soc.h>
-#include <sound/soc-dapm.h>
-
-
-#include "../codecs/wm8974.h"
-#include "mx1_mx2-pcm.h"
-#include "mxc-ssi.h"
-#include <mach/gpio.h>
-#include <mach/iomux.h>
-
-#define IGNORED_ARG 0
-
-
-static struct snd_soc_card mx27vis;
-
-/**
- * This function connects SSI1 (HPCR1) as slave to
- * SSI1 external signals (PPCR1)
- * As slave, HPCR1 must set TFSDIR and TCLKDIR as inputs from
- * port 4
- */
-void audmux_connect_1_4(void)
-{
- pr_debug("AUDMUX: normal operation mode\n");
- /* Reset HPCR1 and PPCR1 */
-
- DAM_HPCR1 = 0x00000000;
- DAM_PPCR1 = 0x00000000;
-
- /* set to synchronous */
- DAM_HPCR1 |= AUDMUX_HPCR_SYN;
- DAM_PPCR1 |= AUDMUX_PPCR_SYN;
-
-
- /* set Rx sources 1 <--> 4 */
- DAM_HPCR1 |= AUDMUX_HPCR_RXDSEL(3); /* port 4 */
- DAM_PPCR1 |= AUDMUX_PPCR_RXDSEL(0); /* port 1 */
-
- /* set Tx frame and Clock direction and source 4 --> 1 output */
- DAM_HPCR1 |= AUDMUX_HPCR_TFSDIR | AUDMUX_HPCR_TCLKDIR;
- DAM_HPCR1 |= AUDMUX_HPCR_TFCSEL(3); /* TxDS and TxCclk from port 4 */
-
- return;
-}
-
-static int mx27vis_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, fmt = 0, mclk = 0;
- int ret = 0;
-
- /*
- * The WM8974 is better at generating accurate audio clocks than the
- * MX27 SSI controller, so we will use it as master when we can.
- */
- switch (params_rate(params)) {
- case 8000:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- mclk = WM8974_MCLKDIV_12;
- pll_out = 24576000;
- break;
- case 16000:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- pll_out = 12288000;
- break;
- case 48000:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_4;
- pll_out = 12288000;
- break;
- case 96000:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_2;
- pll_out = 12288000;
- break;
- case 11025:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_16;
- pll_out = 11289600;
- break;
- case 22050:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_8;
- pll_out = 11289600;
- break;
- case 44100:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_4;
- mclk = WM8974_MCLKDIV_2;
- pll_out = 11289600;
- break;
- case 88200:
- fmt = SND_SOC_DAIFMT_CBM_CFM;
- bclk = WM8974_BCLKDIV_2;
- pll_out = 11289600;
- break;
- }
-
- /* set codec DAI configuration */
- ret = codec_dai->ops->set_fmt(codec_dai,
- SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
- SND_SOC_DAIFMT_SYNC | fmt);
- if (ret < 0) {
- printk(KERN_ERR "Error from codec DAI configuration\n");
- return ret;
- }
-
- /* set cpu DAI configuration */
- ret = cpu_dai->ops->set_fmt(cpu_dai,
- SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_SYNC | fmt);
- if (ret < 0) {
- printk(KERN_ERR "Error from cpu DAI configuration\n");
- return ret;
- }
-
- /* Put DC field of STCCR to 1 (not zero) */
- ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0, 2);
-
- /* set the SSI system clock as input */
- ret = cpu_dai->ops->set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
- SND_SOC_CLOCK_IN);
- if (ret < 0) {
- printk(KERN_ERR "Error when setting system SSI clk\n");
- return ret;
- }
-
- /* set codec BCLK division for sample rate */
- ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_BCLKDIV, bclk);
- if (ret < 0) {
- printk(KERN_ERR "Error when setting BCLK division\n");
- return ret;
- }
-
-
- /* codec PLL input is 25 MHz */
- ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
- 25000000, pll_out);
- if (ret < 0) {
- printk(KERN_ERR "Error when setting PLL input\n");
- return ret;
- }
-
- /*set codec MCLK division for sample rate */
- ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_MCLKDIV, mclk);
- if (ret < 0) {
- printk(KERN_ERR "Error when setting MCLK division\n");
- return ret;
- }
-
- return 0;
-}
-
-static int mx27vis_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;
-
- /* disable the PLL */
- return codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
- 0, 0);
-}
-
-/*
- * mx27vis WM8974 HiFi DAI opserations.
- */
-static struct snd_soc_ops mx27vis_hifi_ops = {
- .hw_params = mx27vis_hifi_hw_params,
- .hw_free = mx27vis_hifi_hw_free,
-};
-
-
-static int mx27vis_suspend(struct platform_device *pdev, pm_message_t state)
-{
- return 0;
-}
-
-static int mx27vis_resume(struct platform_device *pdev)
-{
- return 0;
-}
-
-static int mx27vis_probe(struct platform_device *pdev)
-{
- int ret = 0;
-
- ret = get_ssi_clk(0, &pdev->dev);
-
- if (ret < 0) {
- printk(KERN_ERR "%s: cant get ssi clock\n", __func__);
- return ret;
- }
-
-
- return 0;
-}
-
-static int mx27vis_remove(struct platform_device *pdev)
-{
- put_ssi_clk(0);
- return 0;
-}
-
-static struct snd_soc_dai_link mx27vis_dai[] = {
-{ /* Hifi Playback*/
- .name = "WM8974",
- .stream_name = "WM8974 HiFi",
- .cpu_dai = &imx_ssi_pcm_dai[0],
- .codec_dai = &wm8974_dai,
- .ops = &mx27vis_hifi_ops,
-},
-};
-
-static struct snd_soc_card mx27vis = {
- .name = "mx27vis",
- .platform = &mx1_mx2_soc_platform,
- .probe = mx27vis_probe,
- .remove = mx27vis_remove,
- .suspend_pre = mx27vis_suspend,
- .resume_post = mx27vis_resume,
- .dai_link = mx27vis_dai,
- .num_links = ARRAY_SIZE(mx27vis_dai),
-};
-
-static struct snd_soc_device mx27vis_snd_devdata = {
- .card = &mx27vis,
- .codec_dev = &soc_codec_dev_wm8974,
-};
-
-static struct platform_device *mx27vis_snd_device;
-
-/* Temporal definition of board specific behaviour */
-void gpio_ssi_active(int ssi_num)
-{
- int ret = 0;
-
- unsigned int ssi1_pins[] = {
- PC20_PF_SSI1_FS,
- PC21_PF_SSI1_RXD,
- PC22_PF_SSI1_TXD,
- PC23_PF_SSI1_CLK,
- };
- unsigned int ssi2_pins[] = {
- PC24_PF_SSI2_FS,
- PC25_PF_SSI2_RXD,
- PC26_PF_SSI2_TXD,
- PC27_PF_SSI2_CLK,
- };
- if (ssi_num == 0)
- ret = mxc_gpio_setup_multiple_pins(ssi1_pins,
- ARRAY_SIZE(ssi1_pins), "USB OTG");
- else
- ret = mxc_gpio_setup_multiple_pins(ssi2_pins,
- ARRAY_SIZE(ssi2_pins), "USB OTG");
- if (ret)
- printk(KERN_ERR "Error requesting ssi %x pins\n", ssi_num);
-}
-
-
-static int __init mx27vis_init(void)
-{
- int ret;
-
- mx27vis_snd_device = platform_device_alloc("soc-audio", -1);
- if (!mx27vis_snd_device)
- return -ENOMEM;
-
- platform_set_drvdata(mx27vis_snd_device, &mx27vis_snd_devdata);
- mx27vis_snd_devdata.dev = &mx27vis_snd_device->dev;
- ret = platform_device_add(mx27vis_snd_device);
-
- if (ret) {
- printk(KERN_ERR "ASoC: Platform device allocation failed\n");
- platform_device_put(mx27vis_snd_device);
- }
-
- /* WM8974 uses SSI1 (HPCR1) via AUDMUX port 4 for audio (PPCR1) */
- gpio_ssi_active(0);
- audmux_connect_1_4();
-
- return ret;
-}
-
-static void __exit mx27vis_exit(void)
-{
- /* We should call some "ssi_gpio_inactive()" properly */
-}
-
-module_init(mx27vis_init);
-module_exit(mx27vis_exit);
-
-
-MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
-MODULE_DESCRIPTION("ALSA SoC WM8974 mx27vis");
-MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/mxc-ssi.c b/sound/soc/imx/mxc-ssi.c
deleted file mode 100644
index ccdefe6..0000000
--- a/sound/soc/imx/mxc-ssi.c
+++ /dev/null
@@ -1,860 +0,0 @@
-/*
- * mxc-ssi.c -- SSI driver for Freescale IMX
- *
- * Copyright 2006 Wolfson Microelectronics PLC.
- * Author: Liam Girdwood
- * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
- *
- * Based on mxc-alsa-mc13783 (C) 2006 Freescale.
- *
- * 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:
- * Need to rework SSI register defs when new defs go into mainline.
- * Add support for TDM and FIFO 1.
- * Add support for i.mx3x DMA interface.
- *
- */
-
-
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-#include <linux/dma-mapping.h>
-#include <linux/clk.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include <sound/soc.h>
-#include <mach/dma-mx1-mx2.h>
-#include <asm/mach-types.h>
-
-#include "mxc-ssi.h"
-#include "mx1_mx2-pcm.h"
-
-#define SSI1_PORT 0
-#define SSI2_PORT 1
-
-static int ssi_active[2] = {0, 0};
-
-/* DMA information for mx1_mx2 platforms */
-static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out0 = {
- .name = "SSI1 PCM Stereo out 0",
- .transfer_type = DMA_MODE_WRITE,
- .per_address = SSI1_BASE_ADDR + STX0,
- .event_id = DMA_REQ_SSI1_TX0,
- .watermark_level = TXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out1 = {
- .name = "SSI1 PCM Stereo out 1",
- .transfer_type = DMA_MODE_WRITE,
- .per_address = SSI1_BASE_ADDR + STX1,
- .event_id = DMA_REQ_SSI1_TX1,
- .watermark_level = TXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in0 = {
- .name = "SSI1 PCM Stereo in 0",
- .transfer_type = DMA_MODE_READ,
- .per_address = SSI1_BASE_ADDR + SRX0,
- .event_id = DMA_REQ_SSI1_RX0,
- .watermark_level = RXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in1 = {
- .name = "SSI1 PCM Stereo in 1",
- .transfer_type = DMA_MODE_READ,
- .per_address = SSI1_BASE_ADDR + SRX1,
- .event_id = DMA_REQ_SSI1_RX1,
- .watermark_level = RXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out0 = {
- .name = "SSI2 PCM Stereo out 0",
- .transfer_type = DMA_MODE_WRITE,
- .per_address = SSI2_BASE_ADDR + STX0,
- .event_id = DMA_REQ_SSI2_TX0,
- .watermark_level = TXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out1 = {
- .name = "SSI2 PCM Stereo out 1",
- .transfer_type = DMA_MODE_WRITE,
- .per_address = SSI2_BASE_ADDR + STX1,
- .event_id = DMA_REQ_SSI2_TX1,
- .watermark_level = TXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in0 = {
- .name = "SSI2 PCM Stereo in 0",
- .transfer_type = DMA_MODE_READ,
- .per_address = SSI2_BASE_ADDR + SRX0,
- .event_id = DMA_REQ_SSI2_RX0,
- .watermark_level = RXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in1 = {
- .name = "SSI2 PCM Stereo in 1",
- .transfer_type = DMA_MODE_READ,
- .per_address = SSI2_BASE_ADDR + SRX1,
- .event_id = DMA_REQ_SSI2_RX1,
- .watermark_level = RXFIFO_WATERMARK,
- .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
- .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
-};
-
-static struct clk *ssi_clk0, *ssi_clk1;
-
-int get_ssi_clk(int ssi, struct device *dev)
-{
- switch (ssi) {
- case 0:
- ssi_clk0 = clk_get(dev, "ssi1");
- if (IS_ERR(ssi_clk0))
- return PTR_ERR(ssi_clk0);
- return 0;
- case 1:
- ssi_clk1 = clk_get(dev, "ssi2");
- if (IS_ERR(ssi_clk1))
- return PTR_ERR(ssi_clk1);
- return 0;
- default:
- return -EINVAL;
- }
-}
-EXPORT_SYMBOL(get_ssi_clk);
-
-void put_ssi_clk(int ssi)
-{
- switch (ssi) {
- case 0:
- clk_put(ssi_clk0);
- ssi_clk0 = NULL;
- break;
- case 1:
- clk_put(ssi_clk1);
- ssi_clk1 = NULL;
- break;
- }
-}
-EXPORT_SYMBOL(put_ssi_clk);
-
-/*
- * SSI system clock configuration.
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- */
-static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
- int clk_id, unsigned int freq, int dir)
-{
- u32 scr;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- scr = SSI1_SCR;
- pr_debug("%s: SCR for SSI1 is %x\n", __func__, scr);
- } else {
- scr = SSI2_SCR;
- pr_debug("%s: SCR for SSI2 is %x\n", __func__, scr);
- }
-
- if (scr & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
-
- switch (clk_id) {
- case IMX_SSP_SYS_CLK:
- if (dir == SND_SOC_CLOCK_OUT) {
- scr |= SSI_SCR_SYS_CLK_EN;
- pr_debug("%s: clk of is output\n", __func__);
- } else {
- scr &= ~SSI_SCR_SYS_CLK_EN;
- pr_debug("%s: clk of is input\n", __func__);
- }
- break;
- default:
- return -EINVAL;
- }
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- pr_debug("%s: writeback of SSI1_SCR\n", __func__);
- SSI1_SCR = scr;
- } else {
- pr_debug("%s: writeback of SSI2_SCR\n", __func__);
- SSI2_SCR = scr;
- }
-
- return 0;
-}
-
-/*
- * SSI Clock dividers
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- */
-static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
- int div_id, int div)
-{
- u32 stccr, srccr;
-
- pr_debug("%s\n", __func__);
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- if (SSI1_SCR & SSI_SCR_SSIEN)
- return 0;
- srccr = SSI1_STCCR;
- stccr = SSI1_STCCR;
- } else {
- if (SSI2_SCR & SSI_SCR_SSIEN)
- return 0;
- srccr = SSI2_STCCR;
- stccr = SSI2_STCCR;
- }
-
- switch (div_id) {
- case IMX_SSI_TX_DIV_2:
- stccr &= ~SSI_STCCR_DIV2;
- stccr |= div;
- break;
- case IMX_SSI_TX_DIV_PSR:
- stccr &= ~SSI_STCCR_PSR;
- stccr |= div;
- break;
- case IMX_SSI_TX_DIV_PM:
- stccr &= ~0xff;
- stccr |= SSI_STCCR_PM(div);
- break;
- case IMX_SSI_RX_DIV_2:
- stccr &= ~SSI_STCCR_DIV2;
- stccr |= div;
- break;
- case IMX_SSI_RX_DIV_PSR:
- stccr &= ~SSI_STCCR_PSR;
- stccr |= div;
- break;
- case IMX_SSI_RX_DIV_PM:
- stccr &= ~0xff;
- stccr |= SSI_STCCR_PM(div);
- break;
- default:
- return -EINVAL;
- }
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- SSI1_STCCR = stccr;
- SSI1_SRCCR = srccr;
- } else {
- SSI2_STCCR = stccr;
- SSI2_SRCCR = srccr;
- }
- return 0;
-}
-
-/*
- * SSI Network Mode or TDM slots configuration.
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- */
-static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
- unsigned int mask, int slots)
-{
- u32 stmsk, srmsk, stccr;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- if (SSI1_SCR & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
- stccr = SSI1_STCCR;
- } else {
- if (SSI2_SCR & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
- stccr = SSI2_STCCR;
- }
-
- stmsk = srmsk = mask;
- stccr &= ~SSI_STCCR_DC_MASK;
- stccr |= SSI_STCCR_DC(slots - 1);
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- SSI1_STMSK = stmsk;
- SSI1_SRMSK = srmsk;
- SSI1_SRCCR = SSI1_STCCR = stccr;
- } else {
- SSI2_STMSK = stmsk;
- SSI2_SRMSK = srmsk;
- SSI2_SRCCR = SSI2_STCCR = stccr;
- }
-
- return 0;
-}
-
-/*
- * SSI DAI format configuration.
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- * Note: We don't use the I2S modes but instead manually configure the
- * SSI for I2S.
- */
-static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai,
- unsigned int fmt)
-{
- u32 stcr = 0, srcr = 0, scr;
-
- /*
- * This is done to avoid this function to modify
- * previous set values in stcr
- */
- stcr = SSI1_STCR;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
- scr = SSI1_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
- else
- scr = SSI2_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
-
- if (scr & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
-
- /* DAI mode */
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S:
- /* data on rising edge of bclk, frame low 1clk before data */
- stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
- srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0;
- break;
- case SND_SOC_DAIFMT_LEFT_J:
- /* data on rising edge of bclk, frame high with data */
- stcr |= SSI_STCR_TXBIT0;
- srcr |= SSI_SRCR_RXBIT0;
- break;
- case SND_SOC_DAIFMT_DSP_B:
- /* data on rising edge of bclk, frame high with data */
- stcr |= SSI_STCR_TFSL;
- srcr |= SSI_SRCR_RFSL;
- break;
- case SND_SOC_DAIFMT_DSP_A:
- /* data on rising edge of bclk, frame high 1clk before data */
- stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
- srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS;
- break;
- }
-
- /* DAI clock inversion */
- switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
- case SND_SOC_DAIFMT_IB_IF:
- stcr |= SSI_STCR_TFSI;
- stcr &= ~SSI_STCR_TSCKP;
- srcr |= SSI_SRCR_RFSI;
- srcr &= ~SSI_SRCR_RSCKP;
- break;
- case SND_SOC_DAIFMT_IB_NF:
- stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
- srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI);
- break;
- case SND_SOC_DAIFMT_NB_IF:
- stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
- srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP;
- break;
- case SND_SOC_DAIFMT_NB_NF:
- stcr &= ~SSI_STCR_TFSI;
- stcr |= SSI_STCR_TSCKP;
- srcr &= ~SSI_SRCR_RFSI;
- srcr |= SSI_SRCR_RSCKP;
- break;
- }
-
- /* DAI clock master masks */
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
- stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
- srcr |= SSI_SRCR_RFDIR | SSI_SRCR_RXDIR;
- break;
- case SND_SOC_DAIFMT_CBM_CFS:
- stcr |= SSI_STCR_TFDIR;
- srcr |= SSI_SRCR_RFDIR;
- break;
- case SND_SOC_DAIFMT_CBS_CFM:
- stcr |= SSI_STCR_TXDIR;
- srcr |= SSI_SRCR_RXDIR;
- break;
- }
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- SSI1_STCR = stcr;
- SSI1_SRCR = srcr;
- SSI1_SCR = scr;
- } else {
- SSI2_STCR = stcr;
- SSI2_SRCR = srcr;
- SSI2_SCR = scr;
- }
-
- return 0;
-}
-
-static int imx_ssi_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- 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) {
- /* set up TX DMA params */
- switch (cpu_dai->id) {
- case IMX_DAI_SSI0:
- cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out0;
- break;
- case IMX_DAI_SSI1:
- cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out1;
- break;
- case IMX_DAI_SSI2:
- cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out0;
- break;
- case IMX_DAI_SSI3:
- cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out1;
- }
- pr_debug("%s: (playback)\n", __func__);
- } else {
- /* set up RX DMA params */
- switch (cpu_dai->id) {
- case IMX_DAI_SSI0:
- cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in0;
- break;
- case IMX_DAI_SSI1:
- cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in1;
- break;
- case IMX_DAI_SSI2:
- cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in0;
- break;
- case IMX_DAI_SSI3:
- cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in1;
- }
- pr_debug("%s: (capture)\n", __func__);
- }
-
- /*
- * we cant really change any SSI values after SSI is enabled
- * need to fix in software for max flexibility - lrg
- */
- if (cpu_dai->active) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
-
- /* reset the SSI port - Sect 45.4.4 */
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
-
- if (!ssi_clk0)
- return -EINVAL;
-
- if (ssi_active[SSI1_PORT]++) {
- pr_debug("%s: exit before reset\n", __func__);
- return 0;
- }
-
- /* SSI1 Reset */
- SSI1_SCR = 0;
-
- SSI1_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
- SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
- SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
- SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
- } else {
-
- if (!ssi_clk1)
- return -EINVAL;
-
- if (ssi_active[SSI2_PORT]++) {
- pr_debug("%s: exit before reset\n", __func__);
- return 0;
- }
-
- /* SSI2 Reset */
- SSI2_SCR = 0;
-
- SSI2_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
- SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
- SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
- SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
- }
-
- return 0;
-}
-
-int imx_ssi_hw_tx_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;
- u32 stccr, stcr, sier;
-
- pr_debug("%s\n", __func__);
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- stccr = SSI1_STCCR & ~SSI_STCCR_WL_MASK;
- stcr = SSI1_STCR;
- sier = SSI1_SIER;
- } else {
- stccr = SSI2_STCCR & ~SSI_STCCR_WL_MASK;
- stcr = SSI2_STCR;
- sier = SSI2_SIER;
- }
-
- /* DAI data (word) size */
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
- stccr |= SSI_STCCR_WL(16);
- break;
- case SNDRV_PCM_FORMAT_S20_3LE:
- stccr |= SSI_STCCR_WL(20);
- break;
- case SNDRV_PCM_FORMAT_S24_LE:
- stccr |= SSI_STCCR_WL(24);
- break;
- }
-
- /* enable interrupts */
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
- stcr |= SSI_STCR_TFEN0;
- else
- stcr |= SSI_STCR_TFEN1;
- sier |= SSI_SIER_TDMAE;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- SSI1_STCR = stcr;
- SSI1_STCCR = stccr;
- SSI1_SIER = sier;
- } else {
- SSI2_STCR = stcr;
- SSI2_STCCR = stccr;
- SSI2_SIER = sier;
- }
-
- return 0;
-}
-
-int imx_ssi_hw_rx_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;
- u32 srccr, srcr, sier;
-
- pr_debug("%s\n", __func__);
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- srccr = SSI1_SRCCR & ~SSI_SRCCR_WL_MASK;
- srcr = SSI1_SRCR;
- sier = SSI1_SIER;
- } else {
- srccr = SSI2_SRCCR & ~SSI_SRCCR_WL_MASK;
- srcr = SSI2_SRCR;
- sier = SSI2_SIER;
- }
-
- /* DAI data (word) size */
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
- srccr |= SSI_SRCCR_WL(16);
- break;
- case SNDRV_PCM_FORMAT_S20_3LE:
- srccr |= SSI_SRCCR_WL(20);
- break;
- case SNDRV_PCM_FORMAT_S24_LE:
- srccr |= SSI_SRCCR_WL(24);
- break;
- }
-
- /* enable interrupts */
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
- srcr |= SSI_SRCR_RFEN0;
- else
- srcr |= SSI_SRCR_RFEN1;
- sier |= SSI_SIER_RDMAE;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- SSI1_SRCR = srcr;
- SSI1_SRCCR = srccr;
- SSI1_SIER = sier;
- } else {
- SSI2_SRCR = srcr;
- SSI2_SRCCR = srccr;
- SSI2_SIER = sier;
- }
-
- return 0;
-}
-
-/*
- * Should only be called when port is inactive (i.e. SSIEN = 0),
- * although can be called multiple times by upper layers.
- */
-int imx_ssi_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
-
- int ret;
-
- /* cant change any parameters when SSI is running */
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- if (SSI1_SCR & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
- } else {
- if (SSI2_SCR & SSI_SCR_SSIEN) {
- printk(KERN_WARNING "Warning ssi already enabled\n");
- return 0;
- }
- }
-
- /*
- * Configure both tx and rx params with the same settings. This is
- * really a harware restriction because SSI must be disabled until
- * we can change those values. If there is an active audio stream in
- * one direction, enabling the other direction with different
- * settings would mean disturbing the running one.
- */
- ret = imx_ssi_hw_tx_params(substream, params);
- if (ret < 0)
- return ret;
- return imx_ssi_hw_rx_params(substream, params);
-}
-
-int imx_ssi_prepare(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
- int ret;
-
- pr_debug("%s\n", __func__);
-
- /* Enable clks here to follow SSI recommended init sequence */
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
- ret = clk_enable(ssi_clk0);
- if (ret < 0)
- printk(KERN_ERR "Unable to enable ssi_clk0\n");
- } else {
- ret = clk_enable(ssi_clk1);
- if (ret < 0)
- printk(KERN_ERR "Unable to enable ssi_clk1\n");
- }
-
- return 0;
-}
-
-static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
- u32 scr;
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
- scr = SSI1_SCR;
- else
- scr = SSI2_SCR;
-
- 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)
- scr |= SSI_SCR_TE | SSI_SCR_SSIEN;
- else
- scr |= SSI_SCR_RE | SSI_SCR_SSIEN;
- break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- scr &= ~SSI_SCR_TE;
- else
- scr &= ~SSI_SCR_RE;
- break;
- default:
- return -EINVAL;
- }
-
- if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
- SSI1_SCR = scr;
- else
- SSI2_SCR = scr;
-
- return 0;
-}
-
-static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
-
- /* shutdown SSI if neither Tx or Rx is active */
- if (!cpu_dai->active) {
-
- if (cpu_dai->id == IMX_DAI_SSI0 ||
- cpu_dai->id == IMX_DAI_SSI2) {
-
- if (--ssi_active[SSI1_PORT] > 1)
- return;
-
- SSI1_SCR = 0;
- clk_disable(ssi_clk0);
- } else {
- if (--ssi_active[SSI2_PORT])
- return;
- SSI2_SCR = 0;
- clk_disable(ssi_clk1);
- }
- }
-}
-
-#ifdef CONFIG_PM
-static int imx_ssi_suspend(struct platform_device *dev,
- struct snd_soc_dai *dai)
-{
- return 0;
-}
-
-static int imx_ssi_resume(struct platform_device *pdev,
- struct snd_soc_dai *dai)
-{
- return 0;
-}
-
-#else
-#define imx_ssi_suspend NULL
-#define imx_ssi_resume NULL
-#endif
-
-#define IMX_SSI_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 IMX_SSI_BITS \
- (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE)
-
-static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
- .startup = imx_ssi_startup,
- .shutdown = imx_ssi_shutdown,
- .trigger = imx_ssi_trigger,
- .prepare = imx_ssi_prepare,
- .hw_params = imx_ssi_hw_params,
- .set_sysclk = imx_ssi_set_dai_sysclk,
- .set_clkdiv = imx_ssi_set_dai_clkdiv,
- .set_fmt = imx_ssi_set_dai_fmt,
- .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
-};
-
-struct snd_soc_dai imx_ssi_pcm_dai[] = {
-{
- .name = "imx-i2s-1-0",
- .id = IMX_DAI_SSI0,
- .suspend = imx_ssi_suspend,
- .resume = imx_ssi_resume,
- .playback = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .capture = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .ops = &imx_ssi_pcm_dai_ops,
-},
-{
- .name = "imx-i2s-2-0",
- .id = IMX_DAI_SSI1,
- .playback = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .capture = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .ops = &imx_ssi_pcm_dai_ops,
-},
-{
- .name = "imx-i2s-1-1",
- .id = IMX_DAI_SSI2,
- .suspend = imx_ssi_suspend,
- .resume = imx_ssi_resume,
- .playback = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .capture = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .ops = &imx_ssi_pcm_dai_ops,
-},
-{
- .name = "imx-i2s-2-1",
- .id = IMX_DAI_SSI3,
- .playback = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .capture = {
- .channels_min = 1,
- .channels_max = 2,
- .formats = IMX_SSI_BITS,
- .rates = IMX_SSI_RATES,},
- .ops = &imx_ssi_pcm_dai_ops,
-},
-};
-EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
-
-static int __init imx_ssi_init(void)
-{
- return snd_soc_register_dais(imx_ssi_pcm_dai,
- ARRAY_SIZE(imx_ssi_pcm_dai));
-}
-
-static void __exit imx_ssi_exit(void)
-{
- snd_soc_unregister_dais(imx_ssi_pcm_dai,
- ARRAY_SIZE(imx_ssi_pcm_dai));
-}
-
-module_init(imx_ssi_init);
-module_exit(imx_ssi_exit);
-MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com");
-MODULE_DESCRIPTION("i.MX ASoC I2S driver");
-MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/mxc-ssi.h b/sound/soc/imx/mxc-ssi.h
deleted file mode 100644
index 12bbdc9..0000000
--- a/sound/soc/imx/mxc-ssi.h
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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 _IMX_SSI_H
-#define _IMX_SSI_H
-
-#include <mach/hardware.h>
-
-/* SSI regs definition - MOVE to /arch/arm/plat-mxc/include/mach/ when stable */
-#define SSI1_IO_BASE_ADDR IO_ADDRESS(SSI1_BASE_ADDR)
-#define SSI2_IO_BASE_ADDR IO_ADDRESS(SSI2_BASE_ADDR)
-
-#define STX0 0x00
-#define STX1 0x04
-#define SRX0 0x08
-#define SRX1 0x0c
-#define SCR 0x10
-#define SISR 0x14
-#define SIER 0x18
-#define STCR 0x1c
-#define SRCR 0x20
-#define STCCR 0x24
-#define SRCCR 0x28
-#define SFCSR 0x2c
-#define STR 0x30
-#define SOR 0x34
-#define SACNT 0x38
-#define SACADD 0x3c
-#define SACDAT 0x40
-#define SATAG 0x44
-#define STMSK 0x48
-#define SRMSK 0x4c
-
-#define SSI1_STX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX0)))
-#define SSI1_STX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX1)))
-#define SSI1_SRX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX0)))
-#define SSI1_SRX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX1)))
-#define SSI1_SCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SCR)))
-#define SSI1_SISR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SISR)))
-#define SSI1_SIER (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SIER)))
-#define SSI1_STCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCR)))
-#define SSI1_SRCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCR)))
-#define SSI1_STCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCCR)))
-#define SSI1_SRCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCCR)))
-#define SSI1_SFCSR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SFCSR)))
-#define SSI1_STR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STR)))
-#define SSI1_SOR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SOR)))
-#define SSI1_SACNT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACNT)))
-#define SSI1_SACADD (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACADD)))
-#define SSI1_SACDAT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACDAT)))
-#define SSI1_SATAG (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SATAG)))
-#define SSI1_STMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STMSK)))
-#define SSI1_SRMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRMSK)))
-
-
-#define SSI2_STX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX0)))
-#define SSI2_STX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX1)))
-#define SSI2_SRX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX0)))
-#define SSI2_SRX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX1)))
-#define SSI2_SCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SCR)))
-#define SSI2_SISR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SISR)))
-#define SSI2_SIER (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SIER)))
-#define SSI2_STCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCR)))
-#define SSI2_SRCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCR)))
-#define SSI2_STCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCCR)))
-#define SSI2_SRCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCCR)))
-#define SSI2_SFCSR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SFCSR)))
-#define SSI2_STR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STR)))
-#define SSI2_SOR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SOR)))
-#define SSI2_SACNT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACNT)))
-#define SSI2_SACADD (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACADD)))
-#define SSI2_SACDAT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACDAT)))
-#define SSI2_SATAG (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SATAG)))
-#define SSI2_STMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STMSK)))
-#define SSI2_SRMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRMSK)))
-
-#define SSI_SCR_CLK_IST (1 << 9)
-#define SSI_SCR_TCH_EN (1 << 8)
-#define SSI_SCR_SYS_CLK_EN (1 << 7)
-#define SSI_SCR_I2S_MODE_NORM (0 << 5)
-#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
-#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
-#define SSI_SCR_SYN (1 << 4)
-#define SSI_SCR_NET (1 << 3)
-#define SSI_SCR_RE (1 << 2)
-#define SSI_SCR_TE (1 << 1)
-#define SSI_SCR_SSIEN (1 << 0)
-
-#define SSI_SISR_CMDAU (1 << 18)
-#define SSI_SISR_CMDDU (1 << 17)
-#define SSI_SISR_RXT (1 << 16)
-#define SSI_SISR_RDR1 (1 << 15)
-#define SSI_SISR_RDR0 (1 << 14)
-#define SSI_SISR_TDE1 (1 << 13)
-#define SSI_SISR_TDE0 (1 << 12)
-#define SSI_SISR_ROE1 (1 << 11)
-#define SSI_SISR_ROE0 (1 << 10)
-#define SSI_SISR_TUE1 (1 << 9)
-#define SSI_SISR_TUE0 (1 << 8)
-#define SSI_SISR_TFS (1 << 7)
-#define SSI_SISR_RFS (1 << 6)
-#define SSI_SISR_TLS (1 << 5)
-#define SSI_SISR_RLS (1 << 4)
-#define SSI_SISR_RFF1 (1 << 3)
-#define SSI_SISR_RFF0 (1 << 2)
-#define SSI_SISR_TFE1 (1 << 1)
-#define SSI_SISR_TFE0 (1 << 0)
-
-#define SSI_SIER_RDMAE (1 << 22)
-#define SSI_SIER_RIE (1 << 21)
-#define SSI_SIER_TDMAE (1 << 20)
-#define SSI_SIER_TIE (1 << 19)
-#define SSI_SIER_CMDAU_EN (1 << 18)
-#define SSI_SIER_CMDDU_EN (1 << 17)
-#define SSI_SIER_RXT_EN (1 << 16)
-#define SSI_SIER_RDR1_EN (1 << 15)
-#define SSI_SIER_RDR0_EN (1 << 14)
-#define SSI_SIER_TDE1_EN (1 << 13)
-#define SSI_SIER_TDE0_EN (1 << 12)
-#define SSI_SIER_ROE1_EN (1 << 11)
-#define SSI_SIER_ROE0_EN (1 << 10)
-#define SSI_SIER_TUE1_EN (1 << 9)
-#define SSI_SIER_TUE0_EN (1 << 8)
-#define SSI_SIER_TFS_EN (1 << 7)
-#define SSI_SIER_RFS_EN (1 << 6)
-#define SSI_SIER_TLS_EN (1 << 5)
-#define SSI_SIER_RLS_EN (1 << 4)
-#define SSI_SIER_RFF1_EN (1 << 3)
-#define SSI_SIER_RFF0_EN (1 << 2)
-#define SSI_SIER_TFE1_EN (1 << 1)
-#define SSI_SIER_TFE0_EN (1 << 0)
-
-#define SSI_STCR_TXBIT0 (1 << 9)
-#define SSI_STCR_TFEN1 (1 << 8)
-#define SSI_STCR_TFEN0 (1 << 7)
-#define SSI_STCR_TFDIR (1 << 6)
-#define SSI_STCR_TXDIR (1 << 5)
-#define SSI_STCR_TSHFD (1 << 4)
-#define SSI_STCR_TSCKP (1 << 3)
-#define SSI_STCR_TFSI (1 << 2)
-#define SSI_STCR_TFSL (1 << 1)
-#define SSI_STCR_TEFS (1 << 0)
-
-#define SSI_SRCR_RXBIT0 (1 << 9)
-#define SSI_SRCR_RFEN1 (1 << 8)
-#define SSI_SRCR_RFEN0 (1 << 7)
-#define SSI_SRCR_RFDIR (1 << 6)
-#define SSI_SRCR_RXDIR (1 << 5)
-#define SSI_SRCR_RSHFD (1 << 4)
-#define SSI_SRCR_RSCKP (1 << 3)
-#define SSI_SRCR_RFSI (1 << 2)
-#define SSI_SRCR_RFSL (1 << 1)
-#define SSI_SRCR_REFS (1 << 0)
-
-#define SSI_STCCR_DIV2 (1 << 18)
-#define SSI_STCCR_PSR (1 << 15)
-#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
-#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
-#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
-#define SSI_STCCR_WL_MASK (0xf << 13)
-#define SSI_STCCR_DC_MASK (0x1f << 8)
-#define SSI_STCCR_PM_MASK (0xff << 0)
-
-#define SSI_SRCCR_DIV2 (1 << 18)
-#define SSI_SRCCR_PSR (1 << 15)
-#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
-#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
-#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
-#define SSI_SRCCR_WL_MASK (0xf << 13)
-#define SSI_SRCCR_DC_MASK (0x1f << 8)
-#define SSI_SRCCR_PM_MASK (0xff << 0)
-
-
-#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
-#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
-#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
-#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
-#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
-#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
-#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
-#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
-
-#define SSI_STR_TEST (1 << 15)
-#define SSI_STR_RCK2TCK (1 << 14)
-#define SSI_STR_RFS2TFS (1 << 13)
-#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
-#define SSI_STR_TXD2RXD (1 << 7)
-#define SSI_STR_TCK2RCK (1 << 6)
-#define SSI_STR_TFS2RFS (1 << 5)
-#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
-
-#define SSI_SOR_CLKOFF (1 << 6)
-#define SSI_SOR_RX_CLR (1 << 5)
-#define SSI_SOR_TX_CLR (1 << 4)
-#define SSI_SOR_INIT (1 << 3)
-#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
-#define SSI_SOR_SYNRST (1 << 0)
-
-#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
-#define SSI_SACNT_WR (x << 4)
-#define SSI_SACNT_RD (x << 3)
-#define SSI_SACNT_TIF (x << 2)
-#define SSI_SACNT_FV (x << 1)
-#define SSI_SACNT_AC97EN (x << 0)
-
-/* Watermarks for FIFO's */
-#define TXFIFO_WATERMARK 0x4
-#define RXFIFO_WATERMARK 0x4
-
-/* i.MX DAI SSP ID's */
-#define IMX_DAI_SSI0 0 /* SSI1 FIFO 0 */
-#define IMX_DAI_SSI1 1 /* SSI1 FIFO 1 */
-#define IMX_DAI_SSI2 2 /* SSI2 FIFO 0 */
-#define IMX_DAI_SSI3 3 /* SSI2 FIFO 1 */
-
-/* SSI clock sources */
-#define IMX_SSP_SYS_CLK 0
-
-/* SSI audio dividers */
-#define IMX_SSI_TX_DIV_2 0
-#define IMX_SSI_TX_DIV_PSR 1
-#define IMX_SSI_TX_DIV_PM 2
-#define IMX_SSI_RX_DIV_2 3
-#define IMX_SSI_RX_DIV_PSR 4
-#define IMX_SSI_RX_DIV_PM 5
-
-
-/* SSI Div 2 */
-#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2)
-#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2
-
-extern struct snd_soc_dai imx_ssi_pcm_dai[4];
-extern int get_ssi_clk(int ssi, struct device *dev);
-extern void put_ssi_clk(int ssi);
-#endif
diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c
index acfce1c..7e3f416 100644
--- a/sound/soc/pxa/raumfeld.c
+++ b/sound/soc/pxa/raumfeld.c
@@ -41,7 +41,9 @@
};
#define MAX9485_MCLK_FREQ_112896 0x22
-#define MAX9485_MCLK_FREQ_122880 0x23
+#define MAX9485_MCLK_FREQ_122880 0x23
+#define MAX9485_MCLK_FREQ_225792 0x32
+#define MAX9485_MCLK_FREQ_245760 0x33
static void set_max9485_clk(char clk)
{
@@ -71,9 +73,17 @@
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
- set_max9485_clk(MAX9485_MCLK_FREQ_112896);
+ /* set freq to 0 to enable all possible codec sample rates */
+ return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
+}
- return snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, 0);
+static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+
+ /* set freq to 0 to enable all possible codec sample rates */
+ snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
}
static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
@@ -86,20 +96,24 @@
int ret = 0;
switch (params_rate(params)) {
- case 8000:
- case 16000:
- case 48000:
- case 96000:
- set_max9485_clk(MAX9485_MCLK_FREQ_122880);
- clk = 12288000;
- break;
- case 11025:
- case 22050:
case 44100:
- case 88200:
set_max9485_clk(MAX9485_MCLK_FREQ_112896);
clk = 11289600;
break;
+ case 48000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+ clk = 12288000;
+ break;
+ case 88200:
+ set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+ clk = 22579200;
+ break;
+ case 96000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+ clk = 24576000;
+ break;
+ default:
+ return -EINVAL;
}
fmt = SND_SOC_DAIFMT_I2S |
@@ -128,7 +142,7 @@
if (ret < 0)
return ret;
- ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, 0, 1);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
if (ret < 0)
return ret;
@@ -137,6 +151,7 @@
static struct snd_soc_ops raumfeld_cs4270_ops = {
.startup = raumfeld_cs4270_startup,
+ .shutdown = raumfeld_cs4270_shutdown,
.hw_params = raumfeld_cs4270_hw_params,
};
@@ -181,20 +196,24 @@
int fmt, ret = 0, clk = 0;
switch (params_rate(params)) {
- case 8000:
- case 16000:
- case 48000:
- case 96000:
- set_max9485_clk(MAX9485_MCLK_FREQ_122880);
- clk = 12288000;
- break;
- case 11025:
- case 22050:
case 44100:
- case 88200:
set_max9485_clk(MAX9485_MCLK_FREQ_112896);
clk = 11289600;
break;
+ case 48000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+ clk = 12288000;
+ break;
+ case 88200:
+ set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+ clk = 22579200;
+ break;
+ case 96000:
+ set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+ clk = 24576000;
+ break;
+ default:
+ return -EINVAL;
}
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
@@ -217,7 +236,7 @@
if (ret < 0)
return ret;
- ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, 0, 1);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
if (ret < 0)
return ret;
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig
index b489f1a..15fe57e 100644
--- a/sound/soc/s3c24xx/Kconfig
+++ b/sound/soc/s3c24xx/Kconfig
@@ -27,12 +27,10 @@
config SND_S3C_SOC_PCM
tristate
-config SND_S3C2443_SOC_AC97
+config SND_S3C_SOC_AC97
tristate
- select S3C2410_DMA
- 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
@@ -71,8 +69,10 @@
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 S3C2410_DMA
+ select AC97_BUS
select SND_SOC_AC97_CODEC
+ select SND_S3C_SOC_AC97
help
Say Y if you want to add support for SoC audio on smdk2443
with the WM9710.
@@ -80,8 +80,10 @@
config SND_S3C24XX_SOC_LN2440SBC_ALC650
tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
depends on SND_S3C24XX_SOC && ARCH_S3C2410
- select SND_S3C2443_SOC_AC97
+ select S3C2410_DMA
+ select AC97_BUS
select SND_SOC_AC97_CODEC
+ select SND_S3C_SOC_AC97
help
Say Y if you want to add support for SoC audio on ln2440sbc
with the ALC650.
@@ -111,3 +113,11 @@
select SND_S3C24XX_SOC_I2S
select SND_SOC_TLV320AIC3X
select SND_S3C24XX_SOC_SIMTEC
+
+config SND_SOC_SMDK_WM9713
+ tristate "SoC AC97 Audio support for SMDK with WM9713"
+ depends on SND_S3C24XX_SOC && MACH_SMDK6410
+ select SND_SOC_WM9713
+ select SND_S3C_SOC_AC97
+ help
+ Sat Y if you want to add support for SoC audio on the SMDK.
diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile
index b744657..df071a3 100644
--- a/sound/soc/s3c24xx/Makefile
+++ b/sound/soc/s3c24xx/Makefile
@@ -3,13 +3,13 @@
snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o
-snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o
+snd-soc-s3c-ac97-objs := s3c-ac97.o
snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
snd-soc-s3c-pcm-objs := s3c-pcm.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_S3C_SOC_AC97) += snd-soc-s3c-ac97.o
obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o
obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
@@ -26,6 +26,7 @@
snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o
+snd-soc-smdk-wm9713-objs := smdk_wm9713.o
obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o
obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
@@ -37,4 +38,4 @@
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
-
+obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o
diff --git a/sound/soc/s3c24xx/ln2440sbc_alc650.c b/sound/soc/s3c24xx/ln2440sbc_alc650.c
index d00d359..ffa954f 100644
--- a/sound/soc/s3c24xx/ln2440sbc_alc650.c
+++ b/sound/soc/s3c24xx/ln2440sbc_alc650.c
@@ -25,7 +25,7 @@
#include "../codecs/ac97.h"
#include "s3c-dma.h"
-#include "s3c24xx-ac97.h"
+#include "s3c-ac97.h"
static struct snd_soc_card ln2440sbc;
@@ -33,7 +33,7 @@
{
.name = "AC97",
.stream_name = "AC97 HiFi",
- .cpu_dai = &s3c2443_ac97_dai[0],
+ .cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
.codec_dai = &ac97_dai,
},
};
diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c
new file mode 100644
index 0000000..ee8ed9d
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c-ac97.c
@@ -0,0 +1,518 @@
+/* sound/soc/s3c24xx/s3c-ac97.c
+ *
+ * ALSA SoC Audio Layer - S3C AC97 Controller driver
+ * Evolved from s3c2443-ac97.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * Credits: Graeme Gregory, Sean Choi
+ *
+ * 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/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+
+#include <plat/regs-ac97.h>
+#include <mach/dma.h>
+#include <plat/audio.h>
+
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+#define AC_CMD_ADDR(x) (x << 16)
+#define AC_CMD_DATA(x) (x & 0xffff)
+
+struct s3c_ac97_info {
+ unsigned state;
+ struct clk *ac97_clk;
+ void __iomem *regs;
+ struct mutex lock;
+ struct completion done;
+};
+static struct s3c_ac97_info s3c_ac97;
+
+static struct s3c2410_dma_client s3c_dma_client_out = {
+ .name = "AC97 PCMOut"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_in = {
+ .name = "AC97 PCMIn"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_micin = {
+ .name = "AC97 MicIn"
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_out = {
+ .client = &s3c_dma_client_out,
+ .dma_size = 4,
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_in = {
+ .client = &s3c_dma_client_in,
+ .dma_size = 4,
+};
+
+static struct s3c_dma_params s3c_ac97_mic_in = {
+ .client = &s3c_dma_client_micin,
+ .dma_size = 4,
+};
+
+static void s3c_ac97_activate(struct snd_ac97 *ac97)
+{
+ u32 ac_glbctrl, stat;
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+ if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+ return; /* Return if already active */
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ printk(KERN_ERR "AC97: Unable to activate!");
+}
+
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ u32 ac_glbctrl, ac_codec_cmd;
+ u32 stat, addr, data;
+
+ mutex_lock(&s3c_ac97.lock);
+
+ s3c_ac97_activate(ac97);
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ printk(KERN_ERR "AC97: Unable to read!");
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
+ addr = (stat >> 16) & 0x7f;
+ data = (stat & 0xffff);
+
+ if (addr != reg)
+ printk(KERN_ERR "s3c-ac97: req addr = %02x, rep addr = %02x\n", reg, addr);
+
+ mutex_unlock(&s3c_ac97.lock);
+
+ return (unsigned short)data;
+}
+
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ u32 ac_glbctrl, ac_codec_cmd;
+
+ mutex_lock(&s3c_ac97.lock);
+
+ s3c_ac97_activate(ac97);
+
+ INIT_COMPLETION(s3c_ac97.done);
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ udelay(50);
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+ printk(KERN_ERR "AC97: Unable to write!");
+
+ ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+ ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
+ writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+ mutex_unlock(&s3c_ac97.lock);
+}
+
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+ writel(S3C_AC97_GLBCTRL_COLDRESET,
+ s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+}
+
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+ u32 stat;
+
+ stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+ if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+ return; /* Return if already active */
+
+ writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ msleep(1);
+
+ s3c_ac97_activate(ac97);
+}
+
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
+{
+ u32 ac_glbctrl, ac_glbstat;
+
+ ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
+
+ if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ complete(&s3c_ac97.done);
+ }
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl |= (1<<30); /* Clear interrupt */
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = s3c_ac97_read,
+ .write = s3c_ac97_write,
+ .warm_reset = s3c_ac97_warm_reset,
+ .reset = s3c_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int s3c_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ cpu_dai->dma_data = &s3c_ac97_pcm_out;
+ else
+ cpu_dai->dma_data = &s3c_ac97_pcm_in;
+
+ return 0;
+}
+
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ u32 ac_glbctrl;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ int channel = ((struct s3c_dma_params *)
+ rtd->dai->cpu_dai->dma_data)->channel;
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
+ else
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
+
+ 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:
+ break;
+ }
+
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
+
+ return 0;
+}
+
+static int s3c_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return -ENODEV;
+ else
+ cpu_dai->dma_data = &s3c_ac97_mic_in;
+
+ return 0;
+}
+
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ u32 ac_glbctrl;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ int channel = ((struct s3c_dma_params *)
+ rtd->dai->cpu_dai->dma_data)->channel;
+
+ ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+ ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ }
+
+ writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+ s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
+ .hw_params = s3c_ac97_hw_params,
+ .trigger = s3c_ac97_trigger,
+};
+
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
+ .hw_params = s3c_ac97_hw_mic_params,
+ .trigger = s3c_ac97_mic_trigger,
+};
+
+struct snd_soc_dai s3c_ac97_dai[] = {
+ [S3C_AC97_DAI_PCM] = {
+ .name = "s3c-ac97",
+ .id = S3C_AC97_DAI_PCM,
+ .ac97_control = 1,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c_ac97_dai_ops,
+ },
+ [S3C_AC97_DAI_MIC] = {
+ .name = "s3c-ac97-mic",
+ .id = S3C_AC97_DAI_MIC,
+ .ac97_control = 1,
+ .capture = {
+ .stream_name = "AC97 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c_ac97_mic_dai_ops,
+ },
+};
+EXPORT_SYMBOL_GPL(s3c_ac97_dai);
+
+static __devinit int s3c_ac97_probe(struct platform_device *pdev)
+{
+ struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
+ struct s3c_audio_pdata *ac97_pdata;
+ int ret;
+
+ ac97_pdata = pdev->dev.platform_data;
+ if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
+ dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
+ return -EINVAL;
+ }
+
+ /* Check for availability of necessary resource */
+ dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmatx_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
+ return -ENXIO;
+ }
+
+ dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!dmarx_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
+ return -ENXIO;
+ }
+
+ dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
+ if (!dmamic_res) {
+ dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
+ return -ENXIO;
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_res) {
+ dev_err(&pdev->dev, "Unable to get register resource\n");
+ return -ENXIO;
+ }
+
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!irq_res) {
+ dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
+ return -ENXIO;
+ }
+
+ if (!request_mem_region(mem_res->start,
+ resource_size(mem_res), "s3c-ac97")) {
+ dev_err(&pdev->dev, "Unable to request register region\n");
+ return -EBUSY;
+ }
+
+ s3c_ac97_pcm_out.channel = dmatx_res->start;
+ s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+ s3c_ac97_pcm_in.channel = dmarx_res->start;
+ s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+ s3c_ac97_mic_in.channel = dmamic_res->start;
+ s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
+
+ init_completion(&s3c_ac97.done);
+ mutex_init(&s3c_ac97.lock);
+
+ s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
+ if (s3c_ac97.regs == NULL) {
+ dev_err(&pdev->dev, "Unable to ioremap register region\n");
+ ret = -ENXIO;
+ goto err1;
+ }
+
+ s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
+ if (IS_ERR(s3c_ac97.ac97_clk)) {
+ dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
+ ret = -ENODEV;
+ goto err2;
+ }
+ clk_enable(s3c_ac97.ac97_clk);
+
+ if (ac97_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ ret = -EINVAL;
+ goto err3;
+ }
+
+ ret = request_irq(irq_res->start, s3c_ac97_irq,
+ IRQF_DISABLED, "AC97", NULL);
+ if (ret < 0) {
+ printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
+ goto err4;
+ }
+
+ s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
+ s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
+
+ ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
+ if (ret)
+ goto err5;
+
+ return 0;
+
+err5:
+ free_irq(irq_res->start, NULL);
+err4:
+err3:
+ clk_disable(s3c_ac97.ac97_clk);
+ clk_put(s3c_ac97.ac97_clk);
+err2:
+ iounmap(s3c_ac97.regs);
+err1:
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ return ret;
+}
+
+static __devexit int s3c_ac97_remove(struct platform_device *pdev)
+{
+ struct resource *mem_res, *irq_res;
+
+ snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
+
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (irq_res)
+ free_irq(irq_res->start, NULL);
+
+ clk_disable(s3c_ac97.ac97_clk);
+ clk_put(s3c_ac97.ac97_clk);
+
+ iounmap(s3c_ac97.regs);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mem_res)
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ return 0;
+}
+
+static struct platform_driver s3c_ac97_driver = {
+ .probe = s3c_ac97_probe,
+ .remove = s3c_ac97_remove,
+ .driver = {
+ .name = "s3c-ac97",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c_ac97_init(void)
+{
+ return platform_driver_register(&s3c_ac97_driver);
+}
+module_init(s3c_ac97_init);
+
+static void __exit s3c_ac97_exit(void)
+{
+ platform_driver_unregister(&s3c_ac97_driver);
+}
+module_exit(s3c_ac97_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h
new file mode 100644
index 0000000..2781983
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c-ac97.h
@@ -0,0 +1,23 @@
+/* sound/soc/s3c24xx/s3c-ac97.h
+ *
+ * ALSA SoC Audio Layer - S3C AC97 Controller driver
+ * Evolved from s3c2443-ac97.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * Credits: Graeme Gregory, Sean Choi
+ *
+ * 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 __S3C_AC97_H_
+#define __S3C_AC97_H_
+
+#define S3C_AC97_DAI_PCM 0
+#define S3C_AC97_DAI_MIC 1
+
+extern struct snd_soc_dai s3c_ac97_dai[];
+
+#endif /* __S3C_AC97_H_ */
diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c
deleted file mode 100644
index 0191e3a..0000000
--- a/sound/soc/s3c24xx/s3c2443-ac97.c
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * 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/gpio.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 <plat/regs-ac97.h>
-#include <mach/regs-gpio.h>
-#include <mach/regs-clock.h>
-#include <asm/dma.h>
-#include <mach/dma.h>
-
-#include "s3c-dma.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 DEFINE_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;
-
- mutex_lock(&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);
-
- mutex_unlock(&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;
-
- mutex_lock(&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);
-
- mutex_unlock(&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 s3c_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 s3c_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 s3c_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_dai *dai)
-{
- 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,
- struct snd_soc_dai *dai)
-{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
-
- 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);
-
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
-
- return 0;
-}
-
-static int s3c2443_ac97_hw_mic_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_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, struct snd_soc_dai *dai)
-{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
-
- 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);
-
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
-
- 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)
-
-static struct snd_soc_dai_ops s3c2443_ac97_dai_ops = {
- .hw_params = s3c2443_ac97_hw_params,
- .trigger = s3c2443_ac97_trigger,
-};
-
-static struct snd_soc_dai_ops s3c2443_ac97_mic_dai_ops = {
- .hw_params = s3c2443_ac97_hw_mic_params,
- .trigger = s3c2443_ac97_mic_trigger,
-};
-
-struct snd_soc_dai s3c2443_ac97_dai[] = {
-{
- .name = "s3c2443-ac97",
- .id = 0,
- .ac97_control = 1,
- .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 = &s3c2443_ac97_dai_ops,
-},
-{
- .name = "pxa2xx-ac97-mic",
- .id = 1,
- .ac97_control = 1,
- .capture = {
- .stream_name = "AC97 Mic Capture",
- .channels_min = 1,
- .channels_max = 1,
- .rates = s3c2443_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c2443_ac97_mic_dai_ops,
-},
-};
-EXPORT_SYMBOL_GPL(s3c2443_ac97_dai);
-EXPORT_SYMBOL_GPL(soc_ac97_ops);
-
-static int __init s3c2443_ac97_init(void)
-{
- return snd_soc_register_dais(s3c2443_ac97_dai,
- ARRAY_SIZE(s3c2443_ac97_dai));
-}
-module_init(s3c2443_ac97_init);
-
-static void __exit s3c2443_ac97_exit(void)
-{
- snd_soc_unregister_dais(s3c2443_ac97_dai,
- ARRAY_SIZE(s3c2443_ac97_dai));
-}
-module_exit(s3c2443_ac97_exit);
-
-
-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
deleted file mode 100644
index e96f941..0000000
--- a/sound/soc/s3c24xx/s3c24xx-ac97.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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)
-
-extern struct snd_soc_dai s3c2443_ac97_dai[];
-
-#endif /*S3C24XXAC97_H_*/
diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c
index cc7edb5..93ed3aa 100644
--- a/sound/soc/s3c24xx/s3c64xx-i2s.c
+++ b/sound/soc/s3c24xx/s3c64xx-i2s.c
@@ -15,16 +15,10 @@
#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 <linux/gpio.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 <plat/regs-s3c2412-iis.h>
@@ -38,6 +32,11 @@
#include "s3c-dma.h"
#include "s3c64xx-i2s.h"
+/* The value should be set to maximum of the total number
+ * of I2Sv3 controllers that any supported SoC has.
+ */
+#define MAX_I2SV3 2
+
static struct s3c2410_dma_client s3c64xx_dma_client_out = {
.name = "I2S PCM Stereo out"
};
@@ -46,37 +45,12 @@
.name = "I2S PCM Stereo in"
};
-static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[2] = {
- [0] = {
- .channel = DMACH_I2S0_OUT,
- .client = &s3c64xx_dma_client_out,
- .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD,
- .dma_size = 4,
- },
- [1] = {
- .channel = DMACH_I2S1_OUT,
- .client = &s3c64xx_dma_client_out,
- .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD,
- .dma_size = 4,
- },
-};
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[MAX_I2SV3];
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[MAX_I2SV3];
+static struct s3c_i2sv2_info s3c64xx_i2s[MAX_I2SV3];
-static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[2] = {
- [0] = {
- .channel = DMACH_I2S0_IN,
- .client = &s3c64xx_dma_client_in,
- .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD,
- .dma_size = 4,
- },
- [1] = {
- .channel = DMACH_I2S1_IN,
- .client = &s3c64xx_dma_client_in,
- .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD,
- .dma_size = 4,
- },
-};
-
-static struct s3c_i2sv2_info s3c64xx_i2s[2];
+struct snd_soc_dai s3c64xx_i2s_dai[MAX_I2SV3];
+EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai);
static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
{
@@ -169,55 +143,13 @@
.set_sysclk = s3c64xx_i2s_set_sysclk,
};
-struct snd_soc_dai s3c64xx_i2s_dai[] = {
- {
- .name = "s3c64xx-i2s",
- .id = 0,
- .probe = s3c64xx_i2s_probe,
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C64XX_I2S_RATES,
- .formats = S3C64XX_I2S_FMTS,
- },
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C64XX_I2S_RATES,
- .formats = S3C64XX_I2S_FMTS,
- },
- .ops = &s3c64xx_i2s_dai_ops,
- .symmetric_rates = 1,
- },
- {
- .name = "s3c64xx-i2s",
- .id = 1,
- .probe = s3c64xx_i2s_probe,
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C64XX_I2S_RATES,
- .formats = S3C64XX_I2S_FMTS,
- },
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C64XX_I2S_RATES,
- .formats = S3C64XX_I2S_FMTS,
- },
- .ops = &s3c64xx_i2s_dai_ops,
- .symmetric_rates = 1,
- },
-};
-EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai);
-
static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev)
{
struct s3c_i2sv2_info *i2s;
struct snd_soc_dai *dai;
int ret;
- if (pdev->id >= ARRAY_SIZE(s3c64xx_i2s)) {
+ if (pdev->id >= MAX_I2SV3) {
dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
return -EINVAL;
}
@@ -225,10 +157,40 @@
i2s = &s3c64xx_i2s[pdev->id];
dai = &s3c64xx_i2s_dai[pdev->id];
dai->dev = &pdev->dev;
+ dai->name = "s3c64xx-i2s";
+ dai->id = pdev->id;
+ dai->symmetric_rates = 1;
+ dai->playback.channels_min = 2;
+ dai->playback.channels_max = 2;
+ dai->playback.rates = S3C64XX_I2S_RATES;
+ dai->playback.formats = S3C64XX_I2S_FMTS;
+ dai->capture.channels_min = 2;
+ dai->capture.channels_max = 2;
+ dai->capture.rates = S3C64XX_I2S_RATES;
+ dai->capture.formats = S3C64XX_I2S_FMTS;
+ dai->probe = s3c64xx_i2s_probe;
+ dai->ops = &s3c64xx_i2s_dai_ops;
i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
+ if (pdev->id == 0) {
+ i2s->dma_capture->channel = DMACH_I2S0_IN;
+ i2s->dma_capture->dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD;
+ i2s->dma_playback->channel = DMACH_I2S0_OUT;
+ i2s->dma_playback->dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD;
+ } else {
+ i2s->dma_capture->channel = DMACH_I2S1_IN;
+ i2s->dma_capture->dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD;
+ i2s->dma_playback->channel = DMACH_I2S1_OUT;
+ i2s->dma_playback->dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD;
+ }
+
+ i2s->dma_capture->client = &s3c64xx_dma_client_in;
+ i2s->dma_capture->dma_size = 4;
+ i2s->dma_playback->client = &s3c64xx_dma_client_out;
+ i2s->dma_playback->dma_size = 4;
+
i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus");
if (IS_ERR(i2s->iis_cclk)) {
dev_err(&pdev->dev, "failed to get audio-bus\n");
diff --git a/sound/soc/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c
index 12b783b..3622588 100644
--- a/sound/soc/s3c24xx/smdk2443_wm9710.c
+++ b/sound/soc/s3c24xx/smdk2443_wm9710.c
@@ -21,7 +21,7 @@
#include "../codecs/ac97.h"
#include "s3c-dma.h"
-#include "s3c24xx-ac97.h"
+#include "s3c-ac97.h"
static struct snd_soc_card smdk2443;
@@ -29,7 +29,7 @@
{
.name = "AC97",
.stream_name = "AC97 HiFi",
- .cpu_dai = &s3c2443_ac97_dai[0],
+ .cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
.codec_dai = &ac97_dai,
},
};
diff --git a/sound/soc/s3c24xx/smdk_wm9713.c b/sound/soc/s3c24xx/smdk_wm9713.c
new file mode 100644
index 0000000..24fd39f
--- /dev/null
+++ b/sound/soc/s3c24xx/smdk_wm9713.c
@@ -0,0 +1,94 @@
+/*
+ * smdk_wm9713.c -- SoC audio for SMDK
+ *
+ * Copyright 2010 Samsung Electronics Co. Ltd.
+ * Author: Jaswinder Singh Brar <jassi.brar@samsung.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/soc.h>
+
+#include "../codecs/wm9713.h"
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+static struct snd_soc_card smdk;
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
+ */
+
+/*
+ Playback (HeadPhone):-
+ $ amixer sset 'Headphone' unmute
+ $ amixer sset 'Right Headphone Out Mux' 'Headphone'
+ $ amixer sset 'Left Headphone Out Mux' 'Headphone'
+ $ amixer sset 'Right HP Mixer PCM' unmute
+ $ amixer sset 'Left HP Mixer PCM' unmute
+
+ Capture (LineIn):-
+ $ amixer sset 'Right Capture Source' 'Line'
+ $ amixer sset 'Left Capture Source' 'Line'
+*/
+
+static struct snd_soc_dai_link smdk_dai = {
+ .name = "AC97",
+ .stream_name = "AC97 PCM",
+ .cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM],
+ .codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI],
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK",
+ .platform = &s3c24xx_soc_platform,
+ .dai_link = &smdk_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device smdk_snd_ac97_devdata = {
+ .card = &smdk,
+ .codec_dev = &soc_codec_dev_wm9713,
+};
+
+static struct platform_device *smdk_snd_ac97_device;
+
+static int __init smdk_init(void)
+{
+ int ret;
+
+ smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_ac97_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk_snd_ac97_device,
+ &smdk_snd_ac97_devdata);
+ smdk_snd_ac97_devdata.dev = &smdk_snd_ac97_device->dev;
+
+ ret = platform_device_add(smdk_snd_ac97_device);
+ if (ret)
+ platform_device_put(smdk_snd_ac97_device);
+
+ return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+ platform_device_unregister(smdk_snd_ac97_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh Brar, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 9e69765..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -26,6 +26,12 @@
help
This option enables FSI sound support
+config SND_SOC_SH4_SIU
+ tristate
+ depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK
+ select DMADEVICES
+ select SH_DMAE
+
##
## Boards
##
@@ -47,4 +53,20 @@
This option enables generic sound support for the
FSI - AK4642 unit
+config SND_FSI_DA7210
+ bool "FSI-DA7210 sound support"
+ depends on SND_SOC_SH4_FSI
+ select SND_SOC_DA7210
+ help
+ This option enables generic sound support for the
+ FSI - DA7210 unit
+
+config SND_SIU_MIGOR
+ tristate "SIU sound support on Migo-R"
+ depends on SH_MIGOR
+ select SND_SOC_SH4_SIU
+ select SND_SOC_WM8978
+ help
+ This option enables sound support for the SH7722 Migo-R board
+
endmenu
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
index a699787..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,13 +6,19 @@
snd-soc-hac-objs := hac.o
snd-soc-ssi-objs := ssi.o
snd-soc-fsi-objs := fsi.o
+snd-soc-siu-objs := siu_pcm.o siu_dai.o
obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o
obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o
obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o
+obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o
## boards
snd-soc-sh7760-ac97-objs := sh7760-ac97.o
snd-soc-fsi-ak4642-objs := fsi-ak4642.o
+snd-soc-fsi-da7210-objs := fsi-da7210.o
+snd-soc-migor-objs := migor.o
obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o
obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o
+obj-$(CONFIG_SND_FSI_DA7210) += snd-soc-fsi-da7210.o
+obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o
diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c
new file mode 100644
index 0000000..33b4d17
--- /dev/null
+++ b/sound/soc/sh/fsi-da7210.c
@@ -0,0 +1,83 @@
+/*
+ * fsi-da7210.c
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.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/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/i2c.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/sh_fsi.h>
+#include "../codecs/da7210.h"
+
+static int fsi_da7210_init(struct snd_soc_codec *codec)
+{
+ return snd_soc_dai_set_fmt(&da7210_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+}
+
+static struct snd_soc_dai_link fsi_da7210_dai = {
+ .name = "DA7210",
+ .stream_name = "DA7210",
+ .cpu_dai = &fsi_soc_dai[1], /* FSI B */
+ .codec_dai = &da7210_dai,
+ .init = fsi_da7210_init,
+};
+
+static struct snd_soc_card fsi_soc_card = {
+ .name = "FSI",
+ .platform = &fsi_soc_platform,
+ .dai_link = &fsi_da7210_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device fsi_da7210_snd_devdata = {
+ .card = &fsi_soc_card,
+ .codec_dev = &soc_codec_dev_da7210,
+};
+
+static struct platform_device *fsi_da7210_snd_device;
+
+static int __init fsi_da7210_sound_init(void)
+{
+ int ret;
+
+ fsi_da7210_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!fsi_da7210_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(fsi_da7210_snd_device, &fsi_da7210_snd_devdata);
+ fsi_da7210_snd_devdata.dev = &fsi_da7210_snd_device->dev;
+ ret = platform_device_add(fsi_da7210_snd_device);
+ if (ret)
+ platform_device_put(fsi_da7210_snd_device);
+
+ return ret;
+}
+
+static void __exit fsi_da7210_sound_exit(void)
+{
+ platform_device_unregister(fsi_da7210_snd_device);
+}
+
+module_init(fsi_da7210_sound_init);
+module_exit(fsi_da7210_sound_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC FSI DA2710");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c
index 42813b8..ebf3588 100644
--- a/sound/soc/sh/fsi.c
+++ b/sound/soc/sh/fsi.c
@@ -67,6 +67,7 @@
/* DOFF_ST */
#define ERR_OVER 0x00000010
#define ERR_UNDER 0x00000001
+#define ST_ERR (ERR_OVER | ERR_UNDER)
/* CLK_RST */
#define B_CLK 0x00000010
@@ -92,6 +93,7 @@
struct fsi_priv {
void __iomem *base;
struct snd_pcm_substream *substream;
+ struct fsi_master *master;
int fifo_max;
int chan;
@@ -108,10 +110,9 @@
struct fsi_priv fsia;
struct fsi_priv fsib;
struct sh_fsi_platform_info *info;
+ spinlock_t lock;
};
-static struct fsi_master *master;
-
/************************************************************************
@@ -166,31 +167,53 @@
return __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data);
}
-static int fsi_master_write(u32 reg, u32 data)
+static int fsi_master_write(struct fsi_master *master, u32 reg, u32 data)
{
+ int ret;
+ unsigned long flags;
+
if ((reg < MREG_START) ||
(reg > MREG_END))
return -1;
- return __fsi_reg_write((u32)(master->base + reg), data);
+ spin_lock_irqsave(&master->lock, flags);
+ ret = __fsi_reg_write((u32)(master->base + reg), data);
+ spin_unlock_irqrestore(&master->lock, flags);
+
+ return ret;
}
-static u32 fsi_master_read(u32 reg)
+static u32 fsi_master_read(struct fsi_master *master, u32 reg)
{
+ u32 ret;
+ unsigned long flags;
+
if ((reg < MREG_START) ||
(reg > MREG_END))
return 0;
- return __fsi_reg_read((u32)(master->base + reg));
+ spin_lock_irqsave(&master->lock, flags);
+ ret = __fsi_reg_read((u32)(master->base + reg));
+ spin_unlock_irqrestore(&master->lock, flags);
+
+ return ret;
}
-static int fsi_master_mask_set(u32 reg, u32 mask, u32 data)
+static int fsi_master_mask_set(struct fsi_master *master,
+ u32 reg, u32 mask, u32 data)
{
+ int ret;
+ unsigned long flags;
+
if ((reg < MREG_START) ||
(reg > MREG_END))
return -1;
- return __fsi_reg_mask_set((u32)(master->base + reg), mask, data);
+ spin_lock_irqsave(&master->lock, flags);
+ ret = __fsi_reg_mask_set((u32)(master->base + reg), mask, data);
+ spin_unlock_irqrestore(&master->lock, flags);
+
+ return ret;
}
/************************************************************************
@@ -200,43 +223,35 @@
************************************************************************/
-static struct fsi_priv *fsi_get(struct snd_pcm_substream *substream)
+static struct fsi_master *fsi_get_master(struct fsi_priv *fsi)
{
- struct snd_soc_pcm_runtime *rtd;
- struct fsi_priv *fsi = NULL;
-
- if (!substream || !master)
- return NULL;
-
- rtd = substream->private_data;
- switch (rtd->dai->cpu_dai->id) {
- case 0:
- fsi = &master->fsia;
- break;
- case 1:
- fsi = &master->fsib;
- break;
- }
-
- return fsi;
+ return fsi->master;
}
static int fsi_is_port_a(struct fsi_priv *fsi)
{
- /* return
- * 1 : port a
- * 0 : port b
- */
+ return fsi->master->base == fsi->base;
+}
- if (fsi == &master->fsia)
- return 1;
+static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
- return 0;
+ return machine->cpu_dai;
+}
+
+static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_dai *dai = fsi_get_dai(substream);
+
+ return dai->private_data;
}
static u32 fsi_get_info_flags(struct fsi_priv *fsi)
{
int is_porta = fsi_is_port_a(fsi);
+ struct fsi_master *master = fsi_get_master(fsi);
return is_porta ? master->info->porta_flags :
master->info->portb_flags;
@@ -314,27 +329,30 @@
static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
{
u32 data = fsi_port_ab_io_bit(fsi, is_play);
+ struct fsi_master *master = fsi_get_master(fsi);
- fsi_master_mask_set(IMSK, data, data);
- fsi_master_mask_set(IEMSK, data, data);
+ fsi_master_mask_set(master, IMSK, data, data);
+ fsi_master_mask_set(master, IEMSK, data, data);
}
static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
{
u32 data = fsi_port_ab_io_bit(fsi, is_play);
+ struct fsi_master *master = fsi_get_master(fsi);
- fsi_master_mask_set(IMSK, data, 0);
- fsi_master_mask_set(IEMSK, data, 0);
+ fsi_master_mask_set(master, IMSK, data, 0);
+ fsi_master_mask_set(master, IEMSK, data, 0);
}
static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable)
{
u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4);
+ struct fsi_master *master = fsi_get_master(fsi);
if (enable)
- fsi_master_mask_set(CLK_RST, val, val);
+ fsi_master_mask_set(master, CLK_RST, val, val);
else
- fsi_master_mask_set(CLK_RST, val, 0);
+ fsi_master_mask_set(master, CLK_RST, val, 0);
}
static void fsi_irq_init(struct fsi_priv *fsi, int is_play)
@@ -355,23 +373,23 @@
fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR);
/* clear interrupt factor */
- fsi_master_mask_set(INT_ST, data, 0);
+ fsi_master_mask_set(fsi_get_master(fsi), INT_ST, data, 0);
}
-static void fsi_soft_all_reset(void)
+static void fsi_soft_all_reset(struct fsi_master *master)
{
- u32 status = fsi_master_read(SOFT_RST);
+ u32 status = fsi_master_read(master, SOFT_RST);
/* port AB reset */
status &= 0x000000ff;
- fsi_master_write(SOFT_RST, status);
+ fsi_master_write(master, SOFT_RST, status);
mdelay(10);
/* soft reset */
status &= 0x000000f0;
- fsi_master_write(SOFT_RST, status);
+ fsi_master_write(master, SOFT_RST, status);
status |= 0x00000001;
- fsi_master_write(SOFT_RST, status);
+ fsi_master_write(master, SOFT_RST, status);
mdelay(10);
}
@@ -380,18 +398,21 @@
{
struct snd_pcm_runtime *runtime;
struct snd_pcm_substream *substream = NULL;
+ u32 status;
int send;
int fifo_free;
int width;
u8 *start;
- int i;
+ int i, ret, over_period;
if (!fsi ||
!fsi->substream ||
!fsi->substream->runtime)
return -EINVAL;
- runtime = fsi->substream->runtime;
+ over_period = 0;
+ substream = fsi->substream;
+ runtime = substream->runtime;
/* FSI FIFO has limit.
* So, this driver can not send periods data at a time
@@ -399,7 +420,7 @@
if (fsi->byte_offset >=
fsi->period_len * (fsi->periods + 1)) {
- substream = fsi->substream;
+ over_period = 1;
fsi->periods = (fsi->periods + 1) % runtime->periods;
if (0 == fsi->periods)
@@ -438,30 +459,42 @@
fsi->byte_offset += send * width;
+ ret = 0;
+ status = fsi_reg_read(fsi, DOFF_ST);
+ if (status & ERR_OVER) {
+ struct snd_soc_dai *dai = fsi_get_dai(substream);
+ dev_err(dai->dev, "over run error\n");
+ fsi_reg_write(fsi, DOFF_ST, status & ~ST_ERR);
+ ret = -EIO;
+ }
+
fsi_irq_enable(fsi, 1);
- if (substream)
+ if (over_period)
snd_pcm_period_elapsed(substream);
- return 0;
+ return ret;
}
static int fsi_data_pop(struct fsi_priv *fsi)
{
struct snd_pcm_runtime *runtime;
struct snd_pcm_substream *substream = NULL;
+ u32 status;
int free;
int fifo_fill;
int width;
u8 *start;
- int i;
+ int i, ret, over_period;
if (!fsi ||
!fsi->substream ||
!fsi->substream->runtime)
return -EINVAL;
- runtime = fsi->substream->runtime;
+ over_period = 0;
+ substream = fsi->substream;
+ runtime = substream->runtime;
/* FSI FIFO has limit.
* So, this driver can not send periods data at a time
@@ -469,7 +502,7 @@
if (fsi->byte_offset >=
fsi->period_len * (fsi->periods + 1)) {
- substream = fsi->substream;
+ over_period = 1;
fsi->periods = (fsi->periods + 1) % runtime->periods;
if (0 == fsi->periods)
@@ -507,22 +540,32 @@
fsi->byte_offset += fifo_fill * width;
+ ret = 0;
+ status = fsi_reg_read(fsi, DIFF_ST);
+ if (status & ERR_UNDER) {
+ struct snd_soc_dai *dai = fsi_get_dai(substream);
+ dev_err(dai->dev, "under run error\n");
+ fsi_reg_write(fsi, DIFF_ST, status & ~ST_ERR);
+ ret = -EIO;
+ }
+
fsi_irq_enable(fsi, 0);
- if (substream)
+ if (over_period)
snd_pcm_period_elapsed(substream);
- return 0;
+ return ret;
}
static irqreturn_t fsi_interrupt(int irq, void *data)
{
- u32 status = fsi_master_read(SOFT_RST) & ~0x00000010;
- u32 int_st = fsi_master_read(INT_ST);
+ struct fsi_master *master = data;
+ u32 status = fsi_master_read(master, SOFT_RST) & ~0x00000010;
+ u32 int_st = fsi_master_read(master, INT_ST);
/* clear irq status */
- fsi_master_write(SOFT_RST, status);
- fsi_master_write(SOFT_RST, status | 0x00000010);
+ fsi_master_write(master, SOFT_RST, status);
+ fsi_master_write(master, SOFT_RST, status | 0x00000010);
if (int_st & INT_A_OUT)
fsi_data_push(&master->fsia);
@@ -533,7 +576,7 @@
if (int_st & INT_B_IN)
fsi_data_pop(&master->fsib);
- fsi_master_write(INT_ST, 0x0000000);
+ fsi_master_write(master, INT_ST, 0x0000000);
return IRQ_HANDLED;
}
@@ -548,7 +591,7 @@
static int fsi_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct fsi_priv *fsi = fsi_get(substream);
+ struct fsi_priv *fsi = fsi_get_priv(substream);
const char *msg;
u32 flags = fsi_get_info_flags(fsi);
u32 fmt;
@@ -667,7 +710,7 @@
static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct fsi_priv *fsi = fsi_get(substream);
+ struct fsi_priv *fsi = fsi_get_priv(substream);
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
fsi_irq_disable(fsi, is_play);
@@ -679,7 +722,7 @@
static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
- struct fsi_priv *fsi = fsi_get(substream);
+ struct fsi_priv *fsi = fsi_get_priv(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
@@ -760,7 +803,7 @@
static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
- struct fsi_priv *fsi = fsi_get(substream);
+ struct fsi_priv *fsi = fsi_get_priv(substream);
long location;
location = (fsi->byte_offset - 1);
@@ -870,10 +913,16 @@
************************************************************************/
static int fsi_probe(struct platform_device *pdev)
{
+ struct fsi_master *master;
struct resource *res;
unsigned int irq;
int ret;
+ if (0 != pdev->id) {
+ dev_err(&pdev->dev, "current fsi support id 0 only now\n");
+ return -ENODEV;
+ }
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!res || (int)irq <= 0) {
@@ -899,15 +948,20 @@
master->irq = irq;
master->info = pdev->dev.platform_data;
master->fsia.base = master->base;
+ master->fsia.master = master;
master->fsib.base = master->base + 0x40;
+ master->fsib.master = master;
+ spin_lock_init(&master->lock);
pm_runtime_enable(&pdev->dev);
pm_runtime_resume(&pdev->dev);
fsi_soc_dai[0].dev = &pdev->dev;
+ fsi_soc_dai[0].private_data = &master->fsia;
fsi_soc_dai[1].dev = &pdev->dev;
+ fsi_soc_dai[1].private_data = &master->fsib;
- fsi_soft_all_reset();
+ fsi_soft_all_reset(master);
ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master);
if (ret) {
@@ -937,6 +991,10 @@
static int fsi_remove(struct platform_device *pdev)
{
+ struct fsi_master *master;
+
+ master = fsi_get_master(fsi_soc_dai[0].private_data);
+
snd_soc_unregister_dais(fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai));
snd_soc_unregister_platform(&fsi_soc_platform);
@@ -946,7 +1004,12 @@
iounmap(master->base);
kfree(master);
- master = NULL;
+
+ fsi_soc_dai[0].dev = NULL;
+ fsi_soc_dai[0].private_data = NULL;
+ fsi_soc_dai[1].dev = NULL;
+ fsi_soc_dai[1].private_data = NULL;
+
return 0;
}
diff --git a/sound/soc/sh/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 0000000..3ccd9b3
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,222 @@
+/*
+ * ALSA SoC driver for Migo-R
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * 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/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include <asm/clock.h>
+
+#include <cpu/sh7722.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/wm8978.h"
+#include "siu.h"
+
+/* Default 8000Hz sampling frequency */
+static unsigned long codec_freq = 8000 * 512;
+
+static unsigned int use_count;
+
+/* External clock, sourced from the codec at the SIUMCKB pin */
+static unsigned long siumckb_recalc(struct clk *clk)
+{
+ return codec_freq;
+}
+
+static struct clk_ops siumckb_clk_ops = {
+ .recalc = siumckb_recalc,
+};
+
+static struct clk siumckb_clk = {
+ .name = "siumckb_clk",
+ .id = -1,
+ .ops = &siumckb_clk_ops,
+ .rate = 0, /* initialised at run-time */
+};
+
+static int migor_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;
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_DACCLK, 8);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(rtd->dai->cpu_dai, SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0)
+ return ret;
+
+ codec_freq = rate * 512;
+ /*
+ * This propagates the parent frequency change to children and
+ * recalculates the frequency table
+ */
+ clk_set_rate(&siumckb_clk, codec_freq);
+ dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq);
+
+ ret = snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, SIU_CLKB_EXT,
+ codec_freq / 2, SND_SOC_CLOCK_IN);
+
+ if (!ret)
+ use_count++;
+
+ return ret;
+}
+
+static int migor_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;
+
+ if (use_count) {
+ use_count--;
+
+ if (!use_count)
+ snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0,
+ SND_SOC_CLOCK_IN);
+ } else {
+ dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n");
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+ .hw_params = migor_hw_params,
+ .hw_free = migor_hw_free,
+};
+
+static const struct snd_soc_dapm_widget migor_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Onboard Microphone", NULL),
+ SND_SOC_DAPM_MIC("External Microphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */
+ { "Headphone", NULL, "OUT4 VMID" },
+ { "OUT4 VMID", NULL, "LHP" },
+ { "OUT4 VMID", NULL, "RHP" },
+
+ /* On-board microphone */
+ { "RMICN", NULL, "Mic Bias" },
+ { "RMICP", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "Onboard Microphone" },
+
+ /* External microphone */
+ { "LMICN", NULL, "Mic Bias" },
+ { "LMICP", NULL, "Mic Bias" },
+ { "Mic Bias", NULL, "External Microphone" },
+};
+
+static int migor_dai_init(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, migor_dapm_widgets,
+ ARRAY_SIZE(migor_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ return 0;
+}
+
+/* migor digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link migor_dai = {
+ .name = "wm8978",
+ .stream_name = "WM8978",
+ .cpu_dai = &siu_i2s_dai,
+ .codec_dai = &wm8978_dai,
+ .ops = &migor_dai_ops,
+ .init = migor_dai_init,
+};
+
+/* migor audio machine driver */
+static struct snd_soc_card snd_soc_migor = {
+ .name = "Migo-R",
+ .platform = &siu_platform,
+ .dai_link = &migor_dai,
+ .num_links = 1,
+};
+
+/* migor audio subsystem */
+static struct snd_soc_device migor_snd_devdata = {
+ .card = &snd_soc_migor,
+ .codec_dev = &soc_codec_dev_wm8978,
+};
+
+static struct platform_device *migor_snd_device;
+
+static int __init migor_init(void)
+{
+ int ret;
+
+ ret = clk_register(&siumckb_clk);
+ if (ret < 0)
+ return ret;
+
+ /* Port number used on this machine: port B */
+ migor_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!migor_snd_device) {
+ ret = -ENOMEM;
+ goto epdevalloc;
+ }
+
+ platform_set_drvdata(migor_snd_device, &migor_snd_devdata);
+
+ migor_snd_devdata.dev = &migor_snd_device->dev;
+
+ ret = platform_device_add(migor_snd_device);
+ if (ret)
+ goto epdevadd;
+
+ return 0;
+
+epdevadd:
+ platform_device_put(migor_snd_device);
+epdevalloc:
+ clk_unregister(&siumckb_clk);
+ return ret;
+}
+
+static void __exit migor_exit(void)
+{
+ clk_unregister(&siumckb_clk);
+ platform_device_unregister(migor_snd_device);
+}
+
+module_init(migor_init);
+module_exit(migor_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_DESCRIPTION("ALSA SoC Migor");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/sh/siu.h b/sound/soc/sh/siu.h
new file mode 100644
index 0000000..9cc04ab
--- /dev/null
+++ b/sound/soc/sh/siu.h
@@ -0,0 +1,193 @@
+/*
+ * siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef SIU_H
+#define SIU_H
+
+/* Common kernel and user-space firmware-building defines and types */
+
+#define YRAM0_SIZE (0x0040 / 4) /* 16 */
+#define YRAM1_SIZE (0x0080 / 4) /* 32 */
+#define YRAM2_SIZE (0x0040 / 4) /* 16 */
+#define YRAM3_SIZE (0x0080 / 4) /* 32 */
+#define YRAM4_SIZE (0x0080 / 4) /* 32 */
+#define YRAM_DEF_SIZE (YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \
+ YRAM3_SIZE + YRAM4_SIZE)
+#define YRAM_FIR_SIZE (0x0400 / 4) /* 256 */
+#define YRAM_IIR_SIZE (0x0200 / 4) /* 128 */
+
+#define XRAM0_SIZE (0x0400 / 4) /* 256 */
+#define XRAM1_SIZE (0x0200 / 4) /* 128 */
+#define XRAM2_SIZE (0x0200 / 4) /* 128 */
+
+/* PRAM program array size */
+#define PRAM0_SIZE (0x0100 / 4) /* 64 */
+#define PRAM1_SIZE ((0x2000 - 0x0100) / 4) /* 1984 */
+
+#include <linux/types.h>
+
+struct siu_spb_param {
+ __u32 ab1a; /* input FIFO address */
+ __u32 ab0a; /* output FIFO address */
+ __u32 dir; /* 0=the ather except CPUOUTPUT, 1=CPUINPUT */
+ __u32 event; /* SPB program starting conditions */
+ __u32 stfifo; /* STFIFO register setting value */
+ __u32 trdat; /* TRDAT register setting value */
+};
+
+struct siu_firmware {
+ __u32 yram_fir_coeff[YRAM_FIR_SIZE];
+ __u32 pram0[PRAM0_SIZE];
+ __u32 pram1[PRAM1_SIZE];
+ __u32 yram0[YRAM0_SIZE];
+ __u32 yram1[YRAM1_SIZE];
+ __u32 yram2[YRAM2_SIZE];
+ __u32 yram3[YRAM3_SIZE];
+ __u32 yram4[YRAM4_SIZE];
+ __u32 spbpar_num;
+ struct siu_spb_param spbpar[32];
+};
+
+#ifdef __KERNEL__
+
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include <asm/dma-sh.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc-dai.h>
+
+#define SIU_PERIOD_BYTES_MAX 8192 /* DMA transfer/period size */
+#define SIU_PERIOD_BYTES_MIN 256 /* DMA transfer/period size */
+#define SIU_PERIODS_MAX 64 /* Max periods in buffer */
+#define SIU_PERIODS_MIN 4 /* Min periods in buffer */
+#define SIU_BUFFER_BYTES_MAX (SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX)
+
+/* SIU ports: only one can be used at a time */
+enum {
+ SIU_PORT_A,
+ SIU_PORT_B,
+ SIU_PORT_NUM,
+};
+
+/* SIU clock configuration */
+enum {
+ SIU_CLKA_PLL,
+ SIU_CLKA_EXT,
+ SIU_CLKB_PLL,
+ SIU_CLKB_EXT
+};
+
+struct siu_info {
+ int port_id;
+ u32 __iomem *pram;
+ u32 __iomem *xram;
+ u32 __iomem *yram;
+ u32 __iomem *reg;
+ struct siu_firmware fw;
+};
+
+struct siu_stream {
+ struct tasklet_struct tasklet;
+ struct snd_pcm_substream *substream;
+ snd_pcm_format_t format;
+ size_t buf_bytes;
+ size_t period_bytes;
+ int cur_period; /* Period currently in dma */
+ u32 volume;
+ snd_pcm_sframes_t xfer_cnt; /* Number of frames */
+ u8 rw_flg; /* transfer status */
+ /* DMA status */
+ struct dma_chan *chan; /* DMA channel */
+ struct dma_async_tx_descriptor *tx_desc;
+ dma_cookie_t cookie;
+ struct sh_dmae_slave param;
+};
+
+struct siu_port {
+ unsigned long play_cap; /* Used to track full duplex */
+ struct snd_pcm *pcm;
+ struct siu_stream playback;
+ struct siu_stream capture;
+ u32 stfifo; /* STFIFO value from firmware */
+ u32 trdat; /* TRDAT value from firmware */
+};
+
+extern struct siu_port *siu_ports[SIU_PORT_NUM];
+
+static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream)
+{
+ struct platform_device *pdev =
+ to_platform_device(substream->pcm->card->dev);
+ return siu_ports[pdev->id];
+}
+
+/* Register access */
+static inline void siu_write32(u32 __iomem *addr, u32 val)
+{
+ __raw_writel(val, addr);
+}
+
+static inline u32 siu_read32(u32 __iomem *addr)
+{
+ return __raw_readl(addr);
+}
+
+/* SIU registers */
+#define SIU_IFCTL (0x000 / sizeof(u32))
+#define SIU_SRCTL (0x004 / sizeof(u32))
+#define SIU_SFORM (0x008 / sizeof(u32))
+#define SIU_CKCTL (0x00c / sizeof(u32))
+#define SIU_TRDAT (0x010 / sizeof(u32))
+#define SIU_STFIFO (0x014 / sizeof(u32))
+#define SIU_DPAK (0x01c / sizeof(u32))
+#define SIU_CKREV (0x020 / sizeof(u32))
+#define SIU_EVNTC (0x028 / sizeof(u32))
+#define SIU_SBCTL (0x040 / sizeof(u32))
+#define SIU_SBPSET (0x044 / sizeof(u32))
+#define SIU_SBFSTS (0x068 / sizeof(u32))
+#define SIU_SBDVCA (0x06c / sizeof(u32))
+#define SIU_SBDVCB (0x070 / sizeof(u32))
+#define SIU_SBACTIV (0x074 / sizeof(u32))
+#define SIU_DMAIA (0x090 / sizeof(u32))
+#define SIU_DMAIB (0x094 / sizeof(u32))
+#define SIU_DMAOA (0x098 / sizeof(u32))
+#define SIU_DMAOB (0x09c / sizeof(u32))
+#define SIU_DMAML (0x0a0 / sizeof(u32))
+#define SIU_SPSTS (0x0cc / sizeof(u32))
+#define SIU_SPCTL (0x0d0 / sizeof(u32))
+#define SIU_BRGASEL (0x100 / sizeof(u32))
+#define SIU_BRRA (0x104 / sizeof(u32))
+#define SIU_BRGBSEL (0x108 / sizeof(u32))
+#define SIU_BRRB (0x10c / sizeof(u32))
+
+extern struct snd_soc_platform siu_platform;
+extern struct snd_soc_dai siu_i2s_dai;
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card);
+void siu_free_port(struct siu_port *port_info);
+
+#endif
+
+#endif /* SIU_H */
diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c
new file mode 100644
index 0000000..5452d19
--- /dev/null
+++ b/sound/soc/sh/siu_dai.c
@@ -0,0 +1,847 @@
+/*
+ * siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+
+#include <asm/clock.h>
+#include <asm/siu.h>
+
+#include <sound/control.h>
+#include <sound/soc-dai.h>
+
+#include "siu.h"
+
+/* Board specifics */
+#if defined(CONFIG_CPU_SUBTYPE_SH7722)
+# define SIU_MAX_VOLUME 0x1000
+#else
+# define SIU_MAX_VOLUME 0x7fff
+#endif
+
+#define PRAM_SIZE 0x2000
+#define XRAM_SIZE 0x800
+#define YRAM_SIZE 0x800
+
+#define XRAM_OFFSET 0x4000
+#define YRAM_OFFSET 0x6000
+#define REG_OFFSET 0xc000
+
+#define PLAYBACK_ENABLED 1
+#define CAPTURE_ENABLED 2
+
+#define VOLUME_CAPTURE 0
+#define VOLUME_PLAYBACK 1
+#define DFLT_VOLUME_LEVEL 0x08000800
+
+/*
+ * SPDIF is only available on port A and on some SIU implementations it is only
+ * available for input. Due to the lack of hardware to test it, SPDIF is left
+ * disabled in this driver version
+ */
+struct format_flag {
+ u32 i2s;
+ u32 pcm;
+ u32 spdif;
+ u32 mask;
+};
+
+struct port_flag {
+ struct format_flag playback;
+ struct format_flag capture;
+};
+
+static struct port_flag siu_flags[SIU_PORT_NUM] = {
+ [SIU_PORT_A] = {
+ .playback = {
+ .i2s = 0x50000000,
+ .pcm = 0x40000000,
+ .spdif = 0x80000000, /* not on all SIU versions */
+ .mask = 0xd0000000,
+ },
+ .capture = {
+ .i2s = 0x05000000,
+ .pcm = 0x04000000,
+ .spdif = 0x08000000,
+ .mask = 0x0d000000,
+ },
+ },
+ [SIU_PORT_B] = {
+ .playback = {
+ .i2s = 0x00500000,
+ .pcm = 0x00400000,
+ .spdif = 0, /* impossible - turn off */
+ .mask = 0x00500000,
+ },
+ .capture = {
+ .i2s = 0x00050000,
+ .pcm = 0x00040000,
+ .spdif = 0, /* impossible - turn off */
+ .mask = 0x00050000,
+ },
+ },
+};
+
+static void siu_dai_start(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+
+ dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+ /* Turn on SIU clock */
+ pm_runtime_get_sync(siu_i2s_dai.dev);
+
+ /* Issue software reset to siu */
+ siu_write32(base + SIU_SRCTL, 0);
+
+ /* Wait for the reset to take effect */
+ udelay(1);
+
+ port_info->stfifo = 0;
+ port_info->trdat = 0;
+
+ /* portA, portB, SIU operate */
+ siu_write32(base + SIU_SRCTL, 0x301);
+
+ /* portA=256fs, portB=256fs */
+ siu_write32(base + SIU_CKCTL, 0x40400000);
+
+ /* portA's BRG does not divide SIUCKA */
+ siu_write32(base + SIU_BRGASEL, 0);
+ siu_write32(base + SIU_BRRA, 0);
+
+ /* portB's BRG divides SIUCKB by half */
+ siu_write32(base + SIU_BRGBSEL, 1);
+ siu_write32(base + SIU_BRRB, 0);
+
+ siu_write32(base + SIU_IFCTL, 0x44440000);
+
+ /* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
+ siu_write32(base + SIU_SFORM, 0x0c0c0000);
+
+ /*
+ * Volume levels: looks like the DSP firmware implements volume controls
+ * differently from what's described in the datasheet
+ */
+ siu_write32(base + SIU_SBDVCA, port_info->playback.volume);
+ siu_write32(base + SIU_SBDVCB, port_info->capture.volume);
+}
+
+static void siu_dai_stop(void)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+
+ /* SIU software reset */
+ siu_write32(base + SIU_SRCTL, 0);
+
+ /* Turn off SIU clock */
+ pm_runtime_put_sync(siu_i2s_dai.dev);
+}
+
+static void siu_dai_spbAselect(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ u32 idx;
+
+ /* path A use */
+ if (!info->port_id)
+ idx = 1; /* portA */
+ else
+ idx = 2; /* portB */
+
+ ydef[0] = (fw->spbpar[idx].ab1a << 16) |
+ (fw->spbpar[idx].ab0a << 8) |
+ (fw->spbpar[idx].dir << 7) | 3;
+ ydef[1] = fw->yram0[1]; /* 0x03000300 */
+ ydef[2] = (16 / 2) << 24;
+ ydef[3] = fw->yram0[3]; /* 0 */
+ ydef[4] = fw->yram0[4]; /* 0 */
+ ydef[7] = fw->spbpar[idx].event;
+ port_info->stfifo |= fw->spbpar[idx].stfifo;
+ port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_spbBselect(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ u32 idx;
+
+ /* path B use */
+ if (!info->port_id)
+ idx = 7; /* portA */
+ else
+ idx = 8; /* portB */
+
+ ydef[5] = (fw->spbpar[idx].ab1a << 16) |
+ (fw->spbpar[idx].ab0a << 8) | 1;
+ ydef[6] = fw->spbpar[idx].event;
+ port_info->stfifo |= fw->spbpar[idx].stfifo;
+ port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_open(struct siu_stream *siu_stream)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ u32 srctl, ifctl;
+
+ srctl = siu_read32(base + SIU_SRCTL);
+ ifctl = siu_read32(base + SIU_IFCTL);
+
+ switch (info->port_id) {
+ case SIU_PORT_A:
+ /* portA operates */
+ srctl |= 0x200;
+ ifctl &= ~0xc2;
+ break;
+ case SIU_PORT_B:
+ /* portB operates */
+ srctl |= 0x100;
+ ifctl &= ~0x31;
+ break;
+ }
+
+ siu_write32(base + SIU_SRCTL, srctl);
+ /* Unmute and configure portA */
+ siu_write32(base + SIU_IFCTL, ifctl);
+}
+
+/*
+ * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
+ * packing is supported
+ */
+static void siu_dai_pcmdatapack(struct siu_stream *siu_stream)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ u32 dpak;
+
+ dpak = siu_read32(base + SIU_DPAK);
+
+ switch (info->port_id) {
+ case SIU_PORT_A:
+ dpak &= ~0xc0000000;
+ break;
+ case SIU_PORT_B:
+ dpak &= ~0x00c00000;
+ break;
+ }
+
+ siu_write32(base + SIU_DPAK, dpak);
+}
+
+static int siu_dai_spbstart(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_firmware *fw = &info->fw;
+ u32 *ydef = fw->yram0;
+ int cnt;
+ u32 __iomem *add;
+ u32 *ptr;
+
+ /* Load SPB Program in PRAM */
+ ptr = fw->pram0;
+ add = info->pram;
+ for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++)
+ siu_write32(add, *ptr);
+
+ ptr = fw->pram1;
+ add = info->pram + (0x0100 / sizeof(u32));
+ for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++)
+ siu_write32(add, *ptr);
+
+ /* XRAM initialization */
+ add = info->xram;
+ for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++)
+ siu_write32(add, 0);
+
+ /* YRAM variable area initialization */
+ add = info->yram;
+ for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++)
+ siu_write32(add, ydef[cnt]);
+
+ /* YRAM FIR coefficient area initialization */
+ add = info->yram + (0x0200 / sizeof(u32));
+ for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++)
+ siu_write32(add, fw->yram_fir_coeff[cnt]);
+
+ /* YRAM IIR coefficient area initialization */
+ add = info->yram + (0x0600 / sizeof(u32));
+ for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++)
+ siu_write32(add, 0);
+
+ siu_write32(base + SIU_TRDAT, port_info->trdat);
+ port_info->trdat = 0x0;
+
+
+ /* SPB start condition: software */
+ siu_write32(base + SIU_SBACTIV, 0);
+ /* Start SPB */
+ siu_write32(base + SIU_SBCTL, 0xc0000000);
+ /* Wait for program to halt */
+ cnt = 0x10000;
+ while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000)
+ cpu_relax();
+
+ if (!cnt)
+ return -EBUSY;
+
+ /* SPB program start address setting */
+ siu_write32(base + SIU_SBPSET, 0x00400000);
+ /* SPB hardware start(FIFOCTL source) */
+ siu_write32(base + SIU_SBACTIV, 0xc0000000);
+
+ return 0;
+}
+
+static void siu_dai_spbstop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+
+ siu_write32(base + SIU_SBACTIV, 0);
+ /* SPB stop */
+ siu_write32(base + SIU_SBCTL, 0);
+
+ port_info->stfifo = 0;
+}
+
+/* API functions */
+
+/* Playback and capture hardware properties are identical */
+static struct snd_pcm_hardware siu_dai_pcm_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = SIU_BUFFER_BYTES_MAX,
+ .period_bytes_min = SIU_PERIOD_BYTES_MIN,
+ .period_bytes_max = SIU_PERIOD_BYTES_MAX,
+ .periods_min = SIU_PERIODS_MIN,
+ .periods_max = SIU_PERIODS_MAX,
+};
+
+static int siu_dai_info_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+
+ dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SIU_MAX_VOLUME;
+
+ return 0;
+}
+
+static int siu_dai_get_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+ struct device *dev = port_info->pcm->card->dev;
+ u32 vol;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ switch (kctrl->private_value) {
+ case VOLUME_PLAYBACK:
+ /* Playback is always on port 0 */
+ vol = port_info->playback.volume;
+ ucontrol->value.integer.value[0] = vol & 0xffff;
+ ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+ break;
+ case VOLUME_CAPTURE:
+ /* Capture is always on port 1 */
+ vol = port_info->capture.volume;
+ ucontrol->value.integer.value[0] = vol & 0xffff;
+ ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+ break;
+ default:
+ dev_err(dev, "%s() invalid private_value=%ld\n",
+ __func__, kctrl->private_value);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int siu_dai_put_volume(struct snd_kcontrol *kctrl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+ struct device *dev = port_info->pcm->card->dev;
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ u32 new_vol;
+ u32 cur_vol;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (ucontrol->value.integer.value[0] < 0 ||
+ ucontrol->value.integer.value[0] > SIU_MAX_VOLUME ||
+ ucontrol->value.integer.value[1] < 0 ||
+ ucontrol->value.integer.value[1] > SIU_MAX_VOLUME)
+ return -EINVAL;
+
+ new_vol = ucontrol->value.integer.value[0] |
+ ucontrol->value.integer.value[1] << 16;
+
+ /* See comment above - DSP firmware implementation */
+ switch (kctrl->private_value) {
+ case VOLUME_PLAYBACK:
+ /* Playback is always on port 0 */
+ cur_vol = port_info->playback.volume;
+ siu_write32(base + SIU_SBDVCA, new_vol);
+ port_info->playback.volume = new_vol;
+ break;
+ case VOLUME_CAPTURE:
+ /* Capture is always on port 1 */
+ cur_vol = port_info->capture.volume;
+ siu_write32(base + SIU_SBDVCB, new_vol);
+ port_info->capture.volume = new_vol;
+ break;
+ default:
+ dev_err(dev, "%s() invalid private_value=%ld\n",
+ __func__, kctrl->private_value);
+ return -EINVAL;
+ }
+
+ if (cur_vol != new_vol)
+ return 1;
+
+ return 0;
+}
+
+static struct snd_kcontrol_new playback_controls = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .index = 0,
+ .info = siu_dai_info_volume,
+ .get = siu_dai_get_volume,
+ .put = siu_dai_put_volume,
+ .private_value = VOLUME_PLAYBACK,
+};
+
+static struct snd_kcontrol_new capture_controls = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Volume",
+ .index = 0,
+ .info = siu_dai_info_volume,
+ .get = siu_dai_get_volume,
+ .put = siu_dai_put_volume,
+ .private_value = VOLUME_CAPTURE,
+};
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card)
+{
+ struct device *dev = card->dev;
+ struct snd_kcontrol *kctrl;
+ int ret;
+
+ *port_info = kzalloc(sizeof(**port_info), GFP_KERNEL);
+ if (!*port_info)
+ return -ENOMEM;
+
+ dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info);
+
+ (*port_info)->playback.volume = DFLT_VOLUME_LEVEL;
+ (*port_info)->capture.volume = DFLT_VOLUME_LEVEL;
+
+ /*
+ * Add mixer support. The SPB is used to change the volume. Both
+ * ports use the same SPB. Therefore, we only register one
+ * control instance since it will be used by both channels.
+ * In error case we continue without controls.
+ */
+ kctrl = snd_ctl_new1(&playback_controls, *port_info);
+ ret = snd_ctl_add(card, kctrl);
+ if (ret < 0)
+ dev_err(dev,
+ "failed to add playback controls %p port=%d err=%d\n",
+ kctrl, port, ret);
+
+ kctrl = snd_ctl_new1(&capture_controls, *port_info);
+ ret = snd_ctl_add(card, kctrl);
+ if (ret < 0)
+ dev_err(dev,
+ "failed to add capture controls %p port=%d err=%d\n",
+ kctrl, port, ret);
+
+ return 0;
+}
+
+void siu_free_port(struct siu_port *port_info)
+{
+ kfree(port_info);
+}
+
+static int siu_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+ int ret;
+
+ dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+ info->port_id, port_info);
+
+ snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw);
+
+ ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (unlikely(ret < 0))
+ return ret;
+
+ siu_dai_start(port_info);
+
+ return 0;
+}
+
+static void siu_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_port *port_info = siu_port_info(substream);
+
+ dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+ info->port_id, port_info);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ port_info->play_cap &= ~PLAYBACK_ENABLED;
+ else
+ port_info->play_cap &= ~CAPTURE_ENABLED;
+
+ /* Stop the siu if the other stream is not using it */
+ if (!port_info->play_cap) {
+ /* during stmread or stmwrite ? */
+ BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg);
+ siu_dai_spbstop(port_info);
+ siu_dai_stop();
+ }
+}
+
+/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */
+static int siu_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+ struct siu_stream *siu_stream;
+ int self, ret;
+
+ dev_dbg(substream->pcm->card->dev,
+ "%s: port %d, active streams %lx, %d channels\n",
+ __func__, info->port_id, port_info->play_cap, rt->channels);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ self = PLAYBACK_ENABLED;
+ siu_stream = &port_info->playback;
+ } else {
+ self = CAPTURE_ENABLED;
+ siu_stream = &port_info->capture;
+ }
+
+ /* Set up the siu if not already done */
+ if (!port_info->play_cap) {
+ siu_stream->rw_flg = 0; /* stream-data transfer flag */
+
+ siu_dai_spbAselect(port_info);
+ siu_dai_spbBselect(port_info);
+
+ siu_dai_open(siu_stream);
+
+ siu_dai_pcmdatapack(siu_stream);
+
+ ret = siu_dai_spbstart(port_info);
+ if (ret < 0)
+ goto fail;
+ }
+
+ port_info->play_cap |= self;
+
+fail:
+ return ret;
+}
+
+/*
+ * SIU can set bus format to I2S / PCM / SPDIF independently for playback and
+ * capture, however, the current API sets the bus format globally for a DAI.
+ */
+static int siu_dai_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ u32 ifctl;
+
+ dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n",
+ __func__, fmt, info->port_id);
+
+ if (info->port_id < 0)
+ return -ENODEV;
+
+ /* Here select between I2S / PCM / SPDIF */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ifctl = siu_flags[info->port_id].playback.i2s |
+ siu_flags[info->port_id].capture.i2s;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ifctl = siu_flags[info->port_id].playback.pcm |
+ siu_flags[info->port_id].capture.pcm;
+ break;
+ /* SPDIF disabled - see comment at the top */
+ default:
+ return -EINVAL;
+ }
+
+ ifctl |= ~(siu_flags[info->port_id].playback.mask |
+ siu_flags[info->port_id].capture.mask) &
+ siu_read32(base + SIU_IFCTL);
+ siu_write32(base + SIU_IFCTL, ifctl);
+
+ return 0;
+}
+
+static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct clk *siu_clk, *parent_clk;
+ char *siu_name, *parent_name;
+ int ret;
+
+ if (dir != SND_SOC_CLOCK_IN)
+ return -EINVAL;
+
+ dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id);
+
+ switch (clk_id) {
+ case SIU_CLKA_PLL:
+ siu_name = "siua_clk";
+ parent_name = "pll_clk";
+ break;
+ case SIU_CLKA_EXT:
+ siu_name = "siua_clk";
+ parent_name = "siumcka_clk";
+ break;
+ case SIU_CLKB_PLL:
+ siu_name = "siub_clk";
+ parent_name = "pll_clk";
+ break;
+ case SIU_CLKB_EXT:
+ siu_name = "siub_clk";
+ parent_name = "siumckb_clk";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ siu_clk = clk_get(siu_i2s_dai.dev, siu_name);
+ if (IS_ERR(siu_clk))
+ return PTR_ERR(siu_clk);
+
+ parent_clk = clk_get(siu_i2s_dai.dev, parent_name);
+ if (!IS_ERR(parent_clk)) {
+ ret = clk_set_parent(siu_clk, parent_clk);
+ if (!ret)
+ clk_set_rate(siu_clk, freq);
+ clk_put(parent_clk);
+ }
+
+ clk_put(siu_clk);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops siu_dai_ops = {
+ .startup = siu_dai_startup,
+ .shutdown = siu_dai_shutdown,
+ .prepare = siu_dai_prepare,
+ .set_sysclk = siu_dai_set_sysclk,
+ .set_fmt = siu_dai_set_fmt,
+};
+
+struct snd_soc_dai siu_i2s_dai = {
+ .name = "sh-siu",
+ .id = 0,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .formats = SNDRV_PCM_FMTBIT_S16,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ },
+ .ops = &siu_dai_ops,
+};
+EXPORT_SYMBOL_GPL(siu_i2s_dai);
+
+static int __devinit siu_probe(struct platform_device *pdev)
+{
+ const struct firmware *fw_entry;
+ struct resource *res, *region;
+ struct siu_info *info;
+ int ret;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev);
+ if (ret)
+ goto ereqfw;
+
+ /*
+ * Loaded firmware is "const" - read only, but we have to modify it in
+ * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect()
+ */
+ memcpy(&info->fw, fw_entry->data, fw_entry->size);
+
+ release_firmware(fw_entry);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto egetres;
+ }
+
+ region = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "SIU region already claimed\n");
+ ret = -EBUSY;
+ goto ereqmemreg;
+ }
+
+ ret = -ENOMEM;
+ info->pram = ioremap(res->start, PRAM_SIZE);
+ if (!info->pram)
+ goto emappram;
+ info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE);
+ if (!info->xram)
+ goto emapxram;
+ info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE);
+ if (!info->yram)
+ goto emapyram;
+ info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) -
+ REG_OFFSET);
+ if (!info->reg)
+ goto emapreg;
+
+ siu_i2s_dai.dev = &pdev->dev;
+ siu_i2s_dai.private_data = info;
+
+ ret = snd_soc_register_dais(&siu_i2s_dai, 1);
+ if (ret < 0)
+ goto edaiinit;
+
+ ret = snd_soc_register_platform(&siu_platform);
+ if (ret < 0)
+ goto esocregp;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return ret;
+
+esocregp:
+ snd_soc_unregister_dais(&siu_i2s_dai, 1);
+edaiinit:
+ iounmap(info->reg);
+emapreg:
+ iounmap(info->yram);
+emapyram:
+ iounmap(info->xram);
+emapxram:
+ iounmap(info->pram);
+emappram:
+ release_mem_region(res->start, resource_size(res));
+ereqmemreg:
+egetres:
+ereqfw:
+ kfree(info);
+
+ return ret;
+}
+
+static int __devexit siu_remove(struct platform_device *pdev)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct resource *res;
+
+ pm_runtime_disable(&pdev->dev);
+
+ snd_soc_unregister_platform(&siu_platform);
+ snd_soc_unregister_dais(&siu_i2s_dai, 1);
+
+ iounmap(info->reg);
+ iounmap(info->yram);
+ iounmap(info->xram);
+ iounmap(info->pram);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+ kfree(info);
+
+ return 0;
+}
+
+static struct platform_driver siu_driver = {
+ .driver = {
+ .name = "sh_siu",
+ },
+ .probe = siu_probe,
+ .remove = __devexit_p(siu_remove),
+};
+
+static int __init siu_init(void)
+{
+ return platform_driver_register(&siu_driver);
+}
+
+static void __exit siu_exit(void)
+{
+ platform_driver_unregister(&siu_driver);
+}
+
+module_init(siu_init)
+module_exit(siu_exit)
+
+MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>");
+MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c
new file mode 100644
index 0000000..c5efc30
--- /dev/null
+++ b/sound/soc/sh/siu_pcm.c
@@ -0,0 +1,616 @@
+/*
+ * siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.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.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dai.h>
+
+#include <asm/dma-sh.h>
+#include <asm/siu.h>
+
+#include "siu.h"
+
+#define GET_MAX_PERIODS(buf_bytes, period_bytes) \
+ ((buf_bytes) / (period_bytes))
+#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \
+ ((buf_addr) + ((period_num) * (period_bytes)))
+
+#define RWF_STM_RD 0x01 /* Read in progress */
+#define RWF_STM_WT 0x02 /* Write in progress */
+
+struct siu_port *siu_ports[SIU_PORT_NUM];
+
+/* transfersize is number of u32 dma transfers per period */
+static int siu_pcm_stmwrite_stop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->playback;
+ u32 stfifo;
+
+ if (!siu_stream->rw_flg)
+ return -EPERM;
+
+ /* output FIFO disable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18);
+ pr_debug("%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo & ~0x0c180c18);
+
+ /* during stmwrite clear */
+ siu_stream->rw_flg = 0;
+
+ return 0;
+}
+
+static int siu_pcm_stmwrite_start(struct siu_port *port_info)
+{
+ struct siu_stream *siu_stream = &port_info->playback;
+
+ if (siu_stream->rw_flg)
+ return -EPERM;
+
+ /* Current period in buffer */
+ port_info->playback.cur_period = 0;
+
+ /* during stmwrite flag set */
+ siu_stream->rw_flg = RWF_STM_WT;
+
+ /* DMA transfer start */
+ tasklet_schedule(&siu_stream->tasklet);
+
+ return 0;
+}
+
+static void siu_dma_tx_complete(void *arg)
+{
+ struct siu_stream *siu_stream = arg;
+
+ if (!siu_stream->rw_flg)
+ return;
+
+ /* Update completed period count */
+ if (++siu_stream->cur_period >=
+ GET_MAX_PERIODS(siu_stream->buf_bytes,
+ siu_stream->period_bytes))
+ siu_stream->cur_period = 0;
+
+ pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n",
+ __func__, siu_stream->cur_period,
+ siu_stream->cur_period * siu_stream->period_bytes,
+ siu_stream->buf_bytes, siu_stream->cookie);
+
+ tasklet_schedule(&siu_stream->tasklet);
+
+ /* Notify alsa: a period is done */
+ snd_pcm_period_elapsed(siu_stream->substream);
+}
+
+static int siu_pcm_wr_set(struct siu_port *port_info,
+ dma_addr_t buff, u32 size)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->playback;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+ struct scatterlist sg;
+ u32 stfifo;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+ size, offset_in_page(buff));
+ sg_dma_address(&sg) = buff;
+
+ desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+ &sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(dev, "Failed to allocate a dma descriptor\n");
+ return -ENOMEM;
+ }
+
+ desc->callback = siu_dma_tx_complete;
+ desc->callback_param = siu_stream;
+ cookie = desc->tx_submit(desc);
+ if (cookie < 0) {
+ dev_err(dev, "Failed to submit a dma transfer\n");
+ return cookie;
+ }
+
+ siu_stream->tx_desc = desc;
+ siu_stream->cookie = cookie;
+
+ dma_async_issue_pending(siu_stream->chan);
+
+ /* only output FIFO enable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18));
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo | (port_info->stfifo & 0x0c180c18));
+
+ return 0;
+}
+
+static int siu_pcm_rd_set(struct siu_port *port_info,
+ dma_addr_t buff, size_t size)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->capture;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+ struct scatterlist sg;
+ u32 stfifo;
+
+ dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff);
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+ size, offset_in_page(buff));
+ sg_dma_address(&sg) = buff;
+
+ desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+ &sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(dev, "Failed to allocate dma descriptor\n");
+ return -ENOMEM;
+ }
+
+ desc->callback = siu_dma_tx_complete;
+ desc->callback_param = siu_stream;
+ cookie = desc->tx_submit(desc);
+ if (cookie < 0) {
+ dev_err(dev, "Failed to submit dma descriptor\n");
+ return cookie;
+ }
+
+ siu_stream->tx_desc = desc;
+ siu_stream->cookie = cookie;
+
+ dma_async_issue_pending(siu_stream->chan);
+
+ /* only input FIFO enable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) |
+ (port_info->stfifo & 0x13071307));
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo | (port_info->stfifo & 0x13071307));
+
+ return 0;
+}
+
+static void siu_io_tasklet(unsigned long data)
+{
+ struct siu_stream *siu_stream = (struct siu_stream *)data;
+ struct snd_pcm_substream *substream = siu_stream->substream;
+ struct device *dev = substream->pcm->card->dev;
+ struct snd_pcm_runtime *rt = substream->runtime;
+ struct siu_port *port_info = siu_port_info(substream);
+
+ dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg);
+
+ if (!siu_stream->rw_flg) {
+ dev_dbg(dev, "%s: stream inactive\n", __func__);
+ return;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ dma_addr_t buff;
+ size_t count;
+ u8 *virt;
+
+ buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes);
+ virt = PERIOD_OFFSET(rt->dma_area,
+ siu_stream->cur_period,
+ siu_stream->period_bytes);
+ count = siu_stream->period_bytes;
+
+ /* DMA transfer start */
+ siu_pcm_rd_set(port_info, buff, count);
+ } else {
+ siu_pcm_wr_set(port_info,
+ (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes),
+ siu_stream->period_bytes);
+ }
+}
+
+/* Capture */
+static int siu_pcm_stmread_start(struct siu_port *port_info)
+{
+ struct siu_stream *siu_stream = &port_info->capture;
+
+ if (siu_stream->xfer_cnt > 0x1000000)
+ return -EINVAL;
+ if (siu_stream->rw_flg)
+ return -EPERM;
+
+ /* Current period in buffer */
+ siu_stream->cur_period = 0;
+
+ /* during stmread flag set */
+ siu_stream->rw_flg = RWF_STM_RD;
+
+ tasklet_schedule(&siu_stream->tasklet);
+
+ return 0;
+}
+
+static int siu_pcm_stmread_stop(struct siu_port *port_info)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_stream *siu_stream = &port_info->capture;
+ struct device *dev = siu_stream->substream->pcm->card->dev;
+ u32 stfifo;
+
+ if (!siu_stream->rw_flg)
+ return -EPERM;
+
+ /* input FIFO disable */
+ stfifo = siu_read32(base + SIU_STFIFO);
+ siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307);
+ dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+ stfifo, stfifo & ~0x13071307);
+
+ /* during stmread flag clear */
+ siu_stream->rw_flg = 0;
+
+ return 0;
+}
+
+static int siu_pcm_hw_params(struct snd_pcm_substream *ss,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct device *dev = ss->pcm->card->dev;
+ int ret;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
+ if (ret < 0)
+ dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n");
+
+ return ret;
+}
+
+static int siu_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_stream *siu_stream;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ return snd_pcm_lib_free_pages(ss);
+}
+
+static bool filter(struct dma_chan *chan, void *slave)
+{
+ struct sh_dmae_slave *param = slave;
+
+ pr_debug("%s: slave ID %d\n", __func__, param->slave_id);
+
+ if (unlikely(param->dma_dev != chan->device->dev))
+ return false;
+
+ chan->private = param;
+ return true;
+}
+
+static int siu_pcm_open(struct snd_pcm_substream *ss)
+{
+ /* Playback / Capture */
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct siu_stream *siu_stream;
+ u32 port = info->port_id;
+ struct siu_platform *pdata = siu_i2s_dai.dev->platform_data;
+ struct device *dev = ss->pcm->card->dev;
+ dma_cap_mask_t mask;
+ struct sh_dmae_slave *param;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info);
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ siu_stream = &port_info->playback;
+ param = &siu_stream->param;
+ param->slave_id = port ? SHDMA_SLAVE_SIUB_TX :
+ SHDMA_SLAVE_SIUA_TX;
+ } else {
+ siu_stream = &port_info->capture;
+ param = &siu_stream->param;
+ param->slave_id = port ? SHDMA_SLAVE_SIUB_RX :
+ SHDMA_SLAVE_SIUA_RX;
+ }
+
+ param->dma_dev = pdata->dma_dev;
+ /* Get DMA channel */
+ siu_stream->chan = dma_request_channel(mask, filter, param);
+ if (!siu_stream->chan) {
+ dev_err(dev, "DMA channel allocation failed!\n");
+ return -EBUSY;
+ }
+
+ siu_stream->substream = ss;
+
+ return 0;
+}
+
+static int siu_pcm_close(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct siu_stream *siu_stream;
+
+ dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ dma_release_channel(siu_stream->chan);
+ siu_stream->chan = NULL;
+
+ siu_stream->substream = NULL;
+
+ return 0;
+}
+
+static int siu_pcm_prepare(struct snd_pcm_substream *ss)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct device *dev = ss->pcm->card->dev;
+ struct snd_pcm_runtime *rt = ss->runtime;
+ struct siu_stream *siu_stream;
+ snd_pcm_sframes_t xfer_cnt;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ rt = siu_stream->substream->runtime;
+
+ siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss);
+ siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss);
+
+ dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__,
+ info->port_id, rt->channels, siu_stream->period_bytes);
+
+ /* We only support buffers that are multiples of the period */
+ if (siu_stream->buf_bytes % siu_stream->period_bytes) {
+ dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n",
+ __func__, siu_stream->buf_bytes,
+ siu_stream->period_bytes);
+ return -EINVAL;
+ }
+
+ xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes);
+ if (!xfer_cnt || xfer_cnt > 0x1000000)
+ return -EINVAL;
+
+ siu_stream->format = rt->format;
+ siu_stream->xfer_cnt = xfer_cnt;
+
+ dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d "
+ "format=%d channels=%d xfer_cnt=%d\n", info->port_id,
+ (unsigned long)rt->dma_addr, siu_stream->buf_bytes,
+ siu_stream->period_bytes,
+ siu_stream->format, rt->channels, (int)xfer_cnt);
+
+ return 0;
+}
+
+static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_port *port_info = siu_port_info(ss);
+ int ret;
+
+ dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__,
+ info->port_id, port_info, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = siu_pcm_stmwrite_start(port_info);
+ else
+ ret = siu_pcm_stmread_start(port_info);
+
+ if (ret < 0)
+ dev_warn(dev, "%s: start failed on port=%d\n",
+ __func__, info->port_id);
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_pcm_stmwrite_stop(port_info);
+ else
+ siu_pcm_stmread_stop(port_info);
+ ret = 0;
+
+ break;
+ default:
+ dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * So far only resolution of one period is supported, subject to extending the
+ * dmangine API
+ */
+static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss)
+{
+ struct device *dev = ss->pcm->card->dev;
+ struct siu_info *info = siu_i2s_dai.private_data;
+ u32 __iomem *base = info->reg;
+ struct siu_port *port_info = siu_port_info(ss);
+ struct snd_pcm_runtime *rt = ss->runtime;
+ size_t ptr;
+ struct siu_stream *siu_stream;
+
+ if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ siu_stream = &port_info->playback;
+ else
+ siu_stream = &port_info->capture;
+
+ /*
+ * ptr is the offset into the buffer where the dma is currently at. We
+ * check if the dma buffer has just wrapped.
+ */
+ ptr = PERIOD_OFFSET(rt->dma_addr,
+ siu_stream->cur_period,
+ siu_stream->period_bytes) - rt->dma_addr;
+
+ dev_dbg(dev,
+ "%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n",
+ __func__, info->port_id, siu_read32(base + SIU_EVNTC),
+ siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes,
+ siu_stream->cookie);
+
+ if (ptr >= siu_stream->buf_bytes)
+ ptr = 0;
+
+ return bytes_to_frames(ss->runtime, ptr);
+}
+
+static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ /* card->dev == socdev->dev, see snd_soc_new_pcms() */
+ struct siu_info *info = siu_i2s_dai.private_data;
+ struct platform_device *pdev = to_platform_device(card->dev);
+ int ret;
+ int i;
+
+ /* pdev->id selects between SIUA and SIUB */
+ if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM)
+ return -EINVAL;
+
+ info->port_id = pdev->id;
+
+ /*
+ * While the siu has 2 ports, only one port can be on at a time (only 1
+ * SPB). So far all the boards using the siu had only one of the ports
+ * wired to a codec. To simplify things, we only register one port with
+ * alsa. In case both ports are needed, it should be changed here
+ */
+ for (i = pdev->id; i < pdev->id + 1; i++) {
+ struct siu_port **port_info = &siu_ports[i];
+
+ ret = siu_init_port(i, port_info, card);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV, NULL,
+ SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX);
+ if (ret < 0) {
+ dev_err(card->dev,
+ "snd_pcm_lib_preallocate_pages_for_all() err=%d",
+ ret);
+ goto fail;
+ }
+
+ (*port_info)->pcm = pcm;
+
+ /* IO tasklets */
+ tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet,
+ (unsigned long)&(*port_info)->playback);
+ tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet,
+ (unsigned long)&(*port_info)->capture);
+ }
+
+ dev_info(card->dev, "SuperH SIU driver initialized.\n");
+ return 0;
+
+fail:
+ siu_free_port(siu_ports[pdev->id]);
+ dev_err(card->dev, "SIU: failed to initialize.\n");
+ return ret;
+}
+
+static void siu_pcm_free(struct snd_pcm *pcm)
+{
+ struct platform_device *pdev = to_platform_device(pcm->card->dev);
+ struct siu_port *port_info = siu_ports[pdev->id];
+
+ tasklet_kill(&port_info->capture.tasklet);
+ tasklet_kill(&port_info->playback.tasklet);
+
+ siu_free_port(port_info);
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+
+ dev_dbg(pcm->card->dev, "%s\n", __func__);
+}
+
+static struct snd_pcm_ops siu_pcm_ops = {
+ .open = siu_pcm_open,
+ .close = siu_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = siu_pcm_hw_params,
+ .hw_free = siu_pcm_hw_free,
+ .prepare = siu_pcm_prepare,
+ .trigger = siu_pcm_trigger,
+ .pointer = siu_pcm_pointer_dma,
+};
+
+struct snd_soc_platform siu_platform = {
+ .name = "siu-audio",
+ .pcm_ops = &siu_pcm_ops,
+ .pcm_new = siu_pcm_new,
+ .pcm_free = siu_pcm_free,
+};
+EXPORT_SYMBOL_GPL(siu_platform);
diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c
index d2505e8..097e335 100644
--- a/sound/soc/soc-cache.c
+++ b/sound/soc/soc-cache.c
@@ -15,6 +15,68 @@
#include <linux/spi/spi.h>
#include <sound/soc.h>
+static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= codec->reg_cache_size)
+ return -1;
+ return cache[reg];
+}
+
+static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ u8 data[2];
+ int ret;
+
+ BUG_ON(codec->volatile_register);
+
+ data[0] = (reg << 4) | ((value >> 8) & 0x000f);
+ data[1] = value & 0x00ff;
+
+ if (reg < codec->reg_cache_size)
+ cache[reg] = value;
+ ret = codec->hw_write(codec->control_data, data, 2);
+ if (ret == 2)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_4_12_spi_write(void *control_data, const char *data,
+ int len)
+{
+ struct spi_device *spi = control_data;
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[2];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[1];
+ msg[1] = data[0];
+
+ 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;
+}
+#else
+#define snd_soc_4_12_spi_write NULL
+#endif
+
static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
unsigned int reg)
{
@@ -171,6 +233,108 @@
#define snd_soc_8_16_read_i2c NULL
#endif
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
+ unsigned int r)
+{
+ struct i2c_msg xfer[2];
+ u16 reg = r;
+ u8 data;
+ int ret;
+ struct i2c_client *client = codec->control_data;
+
+ /* Write register */
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 2;
+ xfer[0].buf = (u8 *)®
+
+ /* Read data */
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 1;
+ xfer[1].buf = &data;
+
+ ret = i2c_transfer(client->adapter, xfer, 2);
+ if (ret != 2) {
+ dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+ return 0;
+ }
+
+ return data;
+}
+#else
+#define snd_soc_16_8_read_i2c NULL
+#endif
+
+static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ reg &= 0xff;
+ if (reg >= codec->reg_cache_size)
+ return -1;
+ return cache[reg];
+}
+
+static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ u8 data[3];
+ int ret;
+
+ BUG_ON(codec->volatile_register);
+
+ data[0] = (reg >> 8) & 0xff;
+ data[1] = reg & 0xff;
+ data[2] = value;
+
+ reg &= 0xff;
+ if (reg < codec->reg_cache_size)
+ cache[reg] = value;
+ ret = codec->hw_write(codec->control_data, data, 3);
+ if (ret == 3)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_16_8_spi_write(void *control_data, const char *data,
+ int len)
+{
+ struct spi_device *spi = control_data;
+ struct spi_transfer t;
+ struct spi_message m;
+ u8 msg[3];
+
+ if (len <= 0)
+ return 0;
+
+ msg[0] = data[0];
+ msg[1] = data[1];
+ msg[2] = data[2];
+
+ 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;
+}
+#else
+#define snd_soc_16_8_spi_write NULL
+#endif
+
+
static struct {
int addr_bits;
int data_bits;
@@ -180,9 +344,14 @@
unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
} io_types[] = {
{
+ .addr_bits = 4, .data_bits = 12,
+ .write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
+ .spi_write = snd_soc_4_12_spi_write,
+ },
+ {
.addr_bits = 7, .data_bits = 9,
.write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
- .spi_write = snd_soc_7_9_spi_write
+ .spi_write = snd_soc_7_9_spi_write,
},
{
.addr_bits = 8, .data_bits = 8,
@@ -193,6 +362,12 @@
.write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
.i2c_read = snd_soc_8_16_read_i2c,
},
+ {
+ .addr_bits = 16, .data_bits = 8,
+ .write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
+ .i2c_read = snd_soc_16_8_read_i2c,
+ .spi_write = snd_soc_16_8_spi_write,
+ },
};
/**
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 0a6440c..ca89c78 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1276,8 +1276,8 @@
codec_dai->codec = card->codec;
/* check client and interface hw capabilities */
- sprintf(new_name, "%s %s-%d", dai_link->stream_name, codec_dai->name,
- num);
+ snprintf(new_name, sizeof(new_name), "%s %s-%d",
+ dai_link->stream_name, codec_dai->name, num);
if (codec_dai->playback.channels_min)
playback = 1;
@@ -1368,6 +1368,7 @@
codec->ac97->bus->ops = ops;
codec->ac97->num = num;
+ codec->dev = &codec->ac97->dev;
mutex_unlock(&codec->mutex);
return 0;
}
@@ -1427,9 +1428,9 @@
*
* Returns 1 for change else 0.
*/
-static int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
- unsigned short reg, unsigned int mask,
- unsigned int value)
+int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
+ unsigned short reg, unsigned int mask,
+ unsigned int value)
{
int change;
@@ -1439,6 +1440,7 @@
return change;
}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits_locked);
/**
* snd_soc_test_bits - test register for change
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 0d294ef..6c33510 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -44,13 +44,6 @@
#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] = 0,
@@ -739,6 +732,8 @@
struct snd_soc_dapm_widget *b,
int sort[])
{
+ if (a->codec != b->codec)
+ return (unsigned long)a - (unsigned long)b;
if (sort[a->id] != sort[b->id])
return sort[a->id] - sort[b->id];
if (a->reg != b->reg)
@@ -1017,13 +1012,28 @@
sys_power = 0;
break;
case SND_SOC_DAPM_STREAM_NOP:
- sys_power = codec->bias_level != SND_SOC_BIAS_STANDBY;
+ switch (codec->bias_level) {
+ case SND_SOC_BIAS_STANDBY:
+ case SND_SOC_BIAS_OFF:
+ sys_power = 0;
+ break;
+ default:
+ sys_power = 1;
+ break;
+ }
break;
default:
break;
}
}
+ if (sys_power && codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_dapm_set_bias_level(socdev,
+ SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ pr_err("Failed to turn on bias: %d\n", ret);
+ }
+
/* If we're changing to all on or all off then prepare */
if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) ||
(!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) {
@@ -1047,6 +1057,14 @@
pr_err("Failed to apply standby bias: %d\n", ret);
}
+ /* If we're in standby and can support bias off then do that */
+ if (codec->bias_level == SND_SOC_BIAS_STANDBY &&
+ codec->idle_bias_off) {
+ ret = snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_OFF);
+ if (ret != 0)
+ pr_err("Failed to turn off bias: %d\n", ret);
+ }
+
/* If we just powered up then move to active bias */
if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) {
ret = snd_soc_dapm_set_bias_level(socdev,
@@ -1061,66 +1079,6 @@
return 0;
}
-#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_value_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:
- case snd_soc_dapm_mixer_named_ctl:
- case snd_soc_dapm_supply:
- case snd_soc_dapm_aif_in:
- case snd_soc_dapm_aif_out:
- 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
-
#ifdef CONFIG_DEBUG_FS
static int dapm_widget_power_open_file(struct inode *inode, struct file *file)
{
@@ -1147,9 +1105,16 @@
out = is_connected_output_ep(w);
dapm_clear_walk(w->codec);
- ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d\n",
+ ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d",
w->name, w->power ? "On" : "Off", in, out);
+ if (w->reg >= 0)
+ ret += snprintf(buf + ret, PAGE_SIZE - ret,
+ " - R%d(0x%x) bit %d",
+ w->reg, w->reg, w->shift);
+
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
if (w->sname)
ret += snprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n",
w->sname,
@@ -1245,18 +1210,15 @@
path->connect = 0; /* old connection must be powered down */
}
- if (found) {
+ 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_kcontrol *kcontrol, int connect)
{
struct snd_soc_dapm_path *path;
int found = 0;
@@ -1266,9 +1228,6 @@
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)
@@ -1276,19 +1235,12 @@
/* 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;
+ path->connect = connect;
break;
}
- if (found) {
+ if (found)
dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
- dump_dapm(widget->codec, "mixer power update");
- }
return 0;
}
@@ -1404,9 +1356,7 @@
*/
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;
+ return dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
@@ -1688,6 +1638,7 @@
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int val, val2, val_mask;
+ int connect;
int ret;
val = (ucontrol->value.integer.value[0] & mask);
@@ -1714,7 +1665,17 @@
return 1;
}
- dapm_mixer_update_power(widget, kcontrol, reg, val_mask, val, invert);
+ if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+ if (val)
+ /* new connection */
+ connect = invert ? 0:1;
+ else
+ /* old connection must be powered down */
+ connect = invert ? 1:0;
+
+ dapm_mixer_update_power(widget, kcontrol, connect);
+ }
+
if (widget->event) {
if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
ret = widget->event(widget, kcontrol,
@@ -2152,7 +2113,6 @@
dapm_power_widgets(codec, event);
mutex_unlock(&codec->mutex);
- dump_dapm(codec, __func__);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);