All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio
@ 2010-01-19  8:08 ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:08 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Hi

This patch series adds support for the Sound Interface Unit (SIU), found 
on several SuperH SoCs, including sh7722, sh7343, sh7354, sh7367. The 
driver supports playback and capture at various bitrates, mono and stereo, 
so far only tested with an I2S codec, support for the PCM 
(SND_SOC_DAIFMT_LEFT_J) is also included. The hardware also supports 
S/PDIF, but including support for it would require more work and testing 
on a suitable hardware platform.

The SIU engine includes a DSP, that has to be programmed separately. The 
driver uses the standard request_firmware() API to load the program and 
data.

This patch series also requires the earlier

[PATCH] sh: support SIU sourcing from external clock on sh7722
(http://www.spinics.net/lists/linux-sh/msg04075.html)

patch for proper functionality.

Comments welcome.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support
@ 2010-01-19  8:08 ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:08 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Hi

This patch series adds support for the Sound Interface Unit (SIU), found 
on several SuperH SoCs, including sh7722, sh7343, sh7354, sh7367. The 
driver supports playback and capture at various bitrates, mono and stereo, 
so far only tested with an I2S codec, support for the PCM 
(SND_SOC_DAIFMT_LEFT_J) is also included. The hardware also supports 
S/PDIF, but including support for it would require more work and testing 
on a suitable hardware platform.

The SIU engine includes a DSP, that has to be programmed separately. The 
driver uses the standard request_firmware() API to load the program and 
data.

This patch series also requires the earlier

[PATCH] sh: support SIU sourcing from external clock on sh7722
(http://www.spinics.net/lists/linux-sh/msg04075.html)

patch for proper functionality.

Comments welcome.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-19  8:08 ` [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support Guennadi Liakhovetski
@ 2010-01-19  8:08   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:08 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
is stereo and also has some differences in pin configuration and internal
signal routing. This driver is based on wm8974 and takes the differences into
account.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

I know there is a driver for this codec in wm git-tree and I did have a 
look at it. But, although both drivers have identical roots and look 
similar in many places, the other one implements much less functionality, 
doesn't seem to have been very intensively tested, and would require a 
substantial amount of work to bring it into shape. Whereas this driver has 
been tested, implements a few audio controls, and uses current ALSA / ASoC 
APIs. The only part, that's missing from this version, that is present in 
the wm driver is support for SPI, which can be added as required.

 sound/soc/codecs/Kconfig  |    4 +
 sound/soc/codecs/Makefile |    2 +
 sound/soc/codecs/wm8978.c |  919 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm8978.h |   84 ++++
 4 files changed, 1009 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/wm8978.c
 create mode 100644 sound/soc/codecs/wm8978.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 62ff26a..0aad72f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
 	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
@@ -230,6 +231,9 @@ config SND_SOC_WM8971
 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 ea98354..fbd290e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -44,6 +44,7 @@ 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
@@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..0f91d16
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,919 @@
+/*
+ * 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, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
+	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
+	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
+	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, 0x0110, 0x0110, 0x0100,	/* 0x2c...0x2f */ /* 0x2d and 0x2e contain the VU bit */
+	0x0100, 0x0002, 0x0001, 0x0001,	/* 0x30...0x33 */
+	0x0139, 0x0139, 0x0139, 0x0139,	/* 0x34...0x37 */ /* all contain the VU bit */
+	0x0001,	0x0001,			/* 0x38...0x3b */
+};
+
+/* codec private data */
+struct wm8978_priv {
+	struct snd_soc_codec codec;
+	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" };
+
+#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
+						ARRAY_SIZE(xtexts), xtexts)
+
+static const struct soc_enum wm8978_enum[] = {
+	/* adc */
+	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
+	/* dac */
+	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 3, wm8978_companding),
+	ARRAY_SINGLE(WM8978_EQ1, 8, wm8978_eqmode),
+
+	ARRAY_SINGLE(WM8978_EQ1, 5, wm8978_eq1),
+	ARRAY_SINGLE(WM8978_EQ2, 8, wm8978_bw),
+	ARRAY_SINGLE(WM8978_EQ2, 5, wm8978_eq2),
+	ARRAY_SINGLE(WM8978_EQ3, 8, wm8978_bw),
+
+	ARRAY_SINGLE(WM8978_EQ3, 5, wm8978_eq3),
+	ARRAY_SINGLE(WM8978_EQ4, 8, wm8978_bw),
+	ARRAY_SINGLE(WM8978_EQ4, 5, wm8978_eq4),
+	ARRAY_SINGLE(WM8978_EQ5, 8, wm8978_bw),
+
+	ARRAY_SINGLE(WM8978_EQ5, 5, wm8978_eq5),
+	ARRAY_SINGLE(WM8978_ALC_CONTROL_3, 8, wm8978_alc3),
+	ARRAY_SINGLE(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 struct snd_kcontrol_new wm8978_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8978_COMPANDING_CONTROL, 0, 1, 0),
+
+SOC_ENUM("ADC Companding", wm8978_enum[0]),
+SOC_ENUM("DAC Companding", wm8978_enum[1]),
+
+SOC_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
+
+SOC_SINGLE_TLV("Left PCM Volume",
+	       WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
+SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
+
+SOC_SINGLE_TLV("Left Capture Volume",
+	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Right Capture Volume",
+	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+
+SOC_ENUM("Equaliser Function", wm8978_enum[2]),
+SOC_ENUM("EQ1 Cut Off", wm8978_enum[3]),
+SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ2 Bandwith", wm8978_enum[4]),
+SOC_ENUM("EQ2 Cut Off", wm8978_enum[5]),
+SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ3 Bandwith", wm8978_enum[6]),
+SOC_ENUM("EQ3 Cut Off", wm8978_enum[7]),
+SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ4 Bandwith", wm8978_enum[8]),
+SOC_ENUM("EQ4 Cut Off", wm8978_enum[9]),
+SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ5 Bandwith", wm8978_enum[10]),
+SOC_ENUM("EQ5 Cut Off", wm8978_enum[11]),
+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", wm8978_enum[13]),
+SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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", wm8978_enum[12]),
+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_SINGLE("Left Capture PGA ZC Switch",
+	   WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
+SOC_SINGLE_TLV("Left Capture PGA Volume",
+	       WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+SOC_SINGLE("Right Capture PGA ZC Switch",
+	   WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
+SOC_SINGLE_TLV("Right Capture PGA Volume",
+	       WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+
+/* OUT1 - HeadPhones */
+SOC_SINGLE("Left HeadPhone Playback ZC Switch",
+	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
+SOC_SINGLE("Left HeadPhone Playback Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Left HeadPhone Playback Volume",
+	       WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Right HeadPhone Playback ZC Switch",
+	   WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+SOC_SINGLE("Right HeadPhone Playback Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Right HeadPhone Playback Volume",
+	       WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+/* OUT2 - Speakers */
+SOC_SINGLE("Left Speaker Playback ZC Switch",
+	   WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
+SOC_SINGLE("Left Speaker Playback Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Left Speaker Playback Volume",
+	       WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Right Speaker Playback ZC Switch",
+	   WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+SOC_SINGLE("Right Speaker Playback Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Right Speaker Playback Volume",
+	       WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Left Capture Boost(+20dB)",
+	   WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
+SOC_SINGLE("Right Capture Boost(+20dB)",
+	   WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
+
+/* OUT3/4 - Line Output */
+SOC_SINGLE("Left Line Playback Switch", WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
+SOC_SINGLE("Right Line Playback Switch", WM8978_OUT4_MIXER_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, 1),
+};
+
+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, 1),
+};
+
+/* OUT3/OUT4 Mixer not implemented */
+
+/* Mixer #2: Input PGA Mute */
+static const struct snd_kcontrol_new wm8978_left_inpga[] = {
+SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("Left MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("Left MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_inpga[] = {
+SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
+SOC_DAPM_SINGLE("Right MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
+SOC_DAPM_SINGLE("Right MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
+};
+
+/* Mixer #3: Boost (Input) mixer */
+static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
+SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
+SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
+};
+
+/* AUX Input boost vol */
+static const struct snd_kcontrol_new wm8978_aux_boost_controls[] = {
+SOC_DAPM_SINGLE("Left Aux Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0),
+SOC_DAPM_SINGLE("Right Aux Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0),
+};
+
+/* Mic Input boost vol */
+static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
+SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
+SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
+};
+
+#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
+							m, ARRAY_SIZE(m))
+
+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),
+
+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_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),
+
+/* Mixer #1: OUT1,2 */
+MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_out_mixer),
+MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_right_out_mixer),
+
+MIXER_ARRAY("Left Input PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_inpga),
+MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
+		   wm8978_right_inpga),
+
+MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
+		   wm8978_left_boost_mixer),
+MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
+		   wm8978_right_boost_mixer),
+
+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 Input PGA"},
+	{"Right Boost Mixer", NULL, "R2"},
+
+	{"Left ADC", NULL, "Left Boost Mixer"},
+
+	{"Left Boost Mixer", NULL, "LAUX"},
+	{"Left Boost Mixer", NULL, "Left Input PGA"},
+	{"Left Boost Mixer", NULL, "L2"},
+
+	/* Input PGA */
+	{"Right Input PGA", "R2 Switch", "R2"},
+	{"Right Input PGA", "Right MicN Switch", "RMICN"},
+	{"Right Input PGA", "Right MicP Switch", "RMICP"},
+
+	{"Left Input PGA", "L2 Switch", "L2"},
+	{"Left Input PGA", "Left MicN Switch", "LMICN"},
+	{"Left Input PGA", "Left 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 Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		Ndiv = target / source;
+	} else {
+		pll_div->div2 = 0;
+	}
+
+	if (Ndiv < 6 || Ndiv > 12)
+		dev_warn(wm8978_codec->dev,
+			 "WM8978 N value exceeds recommended range! N = %u\n",
+			 Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target - source * Ndiv;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod + source / 2;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	pll_div->k = K;
+}
+
+static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_pll_div pll_div;
+	u16 reg;
+
+	if (freq_in = 0 || freq_out = 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8978_CLOCKING);
+		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
+		return 0;
+	}
+
+	pll_factors(&pll_div, freq_out, freq_in);
+
+	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);
+
+	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);
+	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
+	/* Output PLL to GPIO1 */
+	snd_soc_write(codec, WM8978_GPIO_CONTROL,
+		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8978_CLOCKING);
+	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
+
+	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;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8978_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x1cf;
+		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
+		break;
+	case WM8978_MCLKDIV:
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x11f;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	case WM8978_ADCCLK:
+		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & 0x1f7;
+		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
+		break;
+	case WM8978_DACCLK:
+		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & 0x1f7;
+		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
+		break;
+	case WM8978_BCLKDIV:
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x1e3;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "%s: ID %d, value %x\n",
+		__func__, div_id, reg | div);
+
+	return 0;
+}
+
+/*
+ * 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;
+	u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
+	u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
+
+	/* 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;
+}
+
+/*
+ * 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;
+	u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
+	u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
+
+	dev_dbg(codec->dev, "%s: fmt %d, rate %u\n", __func__,
+		params_format(params), params_rate(params));
+
+	/* 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;
+	}
+
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+	/* Mic bias */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
+		      (snd_soc_read(codec, 1) & ~4) | 0x10);
+
+	/* Out-1 enabled, left/right input channel enabled */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
+
+	/* Out-2 disabled, right/left output channel enabled, dac enabled */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
+
+	return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
+
+	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+	if (mute)
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
+	else
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
+
+	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:
+		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:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\n", __func__, level, power1);
+
+	codec->bias_level = level;
+	return 0;
+}
+
+/* Also supports 12kHz */
+#define WM8978_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \
+	SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define 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_pll	= wm8978_set_dai_pll,
+};
+
+struct snd_soc_dai wm8978_dai = {
+	.name = "WM8978 HiFi",
+	.id = 1,
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8978_RATES,
+		.formats = WM8978_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8978_RATES,
+		.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;
+
+	/* we only need to suspend if we are a valid card */
+	if (!codec->card)
+		return 0;
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	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;
+	int i;
+	u16 data;
+	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(wm8978_reg); i++) {
+		if (i = WM8978_RESET)
+			continue;
+		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
+		codec->hw_write(codec->control_data, (char *)&data, 2);
+	}
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	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);
+
+static __devinit int wm8978_register(struct wm8978_priv *wm8978)
+{
+	int ret;
+	struct snd_soc_codec *codec = &wm8978->codec;
+
+	if (wm8978_codec) {
+		dev_err(codec->dev, "Another WM8978 is registered\n");
+		return -EINVAL;
+	}
+
+	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));
+
+	/* 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..61e39c0
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,84 @@
+/*
+ * 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_OPCLKDIV,
+	WM8978_MCLKDIV,
+	WM8978_ADCCLK,
+	WM8978_DACCLK,
+	WM8978_BCLKDIV,
+};
+
+extern struct snd_soc_dai wm8978_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8978;
+
+#endif	/* __WM8978_H__ */
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-19  8:08   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:08 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
is stereo and also has some differences in pin configuration and internal
signal routing. This driver is based on wm8974 and takes the differences into
account.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

I know there is a driver for this codec in wm git-tree and I did have a 
look at it. But, although both drivers have identical roots and look 
similar in many places, the other one implements much less functionality, 
doesn't seem to have been very intensively tested, and would require a 
substantial amount of work to bring it into shape. Whereas this driver has 
been tested, implements a few audio controls, and uses current ALSA / ASoC 
APIs. The only part, that's missing from this version, that is present in 
the wm driver is support for SPI, which can be added as required.

 sound/soc/codecs/Kconfig  |    4 +
 sound/soc/codecs/Makefile |    2 +
 sound/soc/codecs/wm8978.c |  919 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm8978.h |   84 ++++
 4 files changed, 1009 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/wm8978.c
 create mode 100644 sound/soc/codecs/wm8978.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 62ff26a..0aad72f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
 	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
@@ -230,6 +231,9 @@ config SND_SOC_WM8971
 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 ea98354..fbd290e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -44,6 +44,7 @@ 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
@@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..0f91d16
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,919 @@
+/*
+ * 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, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
+	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
+	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
+	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, 0x0110, 0x0110, 0x0100,	/* 0x2c...0x2f */ /* 0x2d and 0x2e contain the VU bit */
+	0x0100, 0x0002, 0x0001, 0x0001,	/* 0x30...0x33 */
+	0x0139, 0x0139, 0x0139, 0x0139,	/* 0x34...0x37 */ /* all contain the VU bit */
+	0x0001,	0x0001,			/* 0x38...0x3b */
+};
+
+/* codec private data */
+struct wm8978_priv {
+	struct snd_soc_codec codec;
+	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" };
+
+#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
+						ARRAY_SIZE(xtexts), xtexts)
+
+static const struct soc_enum wm8978_enum[] = {
+	/* adc */
+	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
+	/* dac */
+	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 3, wm8978_companding),
+	ARRAY_SINGLE(WM8978_EQ1, 8, wm8978_eqmode),
+
+	ARRAY_SINGLE(WM8978_EQ1, 5, wm8978_eq1),
+	ARRAY_SINGLE(WM8978_EQ2, 8, wm8978_bw),
+	ARRAY_SINGLE(WM8978_EQ2, 5, wm8978_eq2),
+	ARRAY_SINGLE(WM8978_EQ3, 8, wm8978_bw),
+
+	ARRAY_SINGLE(WM8978_EQ3, 5, wm8978_eq3),
+	ARRAY_SINGLE(WM8978_EQ4, 8, wm8978_bw),
+	ARRAY_SINGLE(WM8978_EQ4, 5, wm8978_eq4),
+	ARRAY_SINGLE(WM8978_EQ5, 8, wm8978_bw),
+
+	ARRAY_SINGLE(WM8978_EQ5, 5, wm8978_eq5),
+	ARRAY_SINGLE(WM8978_ALC_CONTROL_3, 8, wm8978_alc3),
+	ARRAY_SINGLE(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 struct snd_kcontrol_new wm8978_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8978_COMPANDING_CONTROL, 0, 1, 0),
+
+SOC_ENUM("ADC Companding", wm8978_enum[0]),
+SOC_ENUM("DAC Companding", wm8978_enum[1]),
+
+SOC_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
+
+SOC_SINGLE_TLV("Left PCM Volume",
+	       WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
+SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
+
+SOC_SINGLE_TLV("Left Capture Volume",
+	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Right Capture Volume",
+	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+
+SOC_ENUM("Equaliser Function", wm8978_enum[2]),
+SOC_ENUM("EQ1 Cut Off", wm8978_enum[3]),
+SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ2 Bandwith", wm8978_enum[4]),
+SOC_ENUM("EQ2 Cut Off", wm8978_enum[5]),
+SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ3 Bandwith", wm8978_enum[6]),
+SOC_ENUM("EQ3 Cut Off", wm8978_enum[7]),
+SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ4 Bandwith", wm8978_enum[8]),
+SOC_ENUM("EQ4 Cut Off", wm8978_enum[9]),
+SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ5 Bandwith", wm8978_enum[10]),
+SOC_ENUM("EQ5 Cut Off", wm8978_enum[11]),
+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", wm8978_enum[13]),
+SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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", wm8978_enum[12]),
+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_SINGLE("Left Capture PGA ZC Switch",
+	   WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
+SOC_SINGLE_TLV("Left Capture PGA Volume",
+	       WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+SOC_SINGLE("Right Capture PGA ZC Switch",
+	   WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
+SOC_SINGLE_TLV("Right Capture PGA Volume",
+	       WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+
+/* OUT1 - HeadPhones */
+SOC_SINGLE("Left HeadPhone Playback ZC Switch",
+	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
+SOC_SINGLE("Left HeadPhone Playback Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Left HeadPhone Playback Volume",
+	       WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Right HeadPhone Playback ZC Switch",
+	   WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+SOC_SINGLE("Right HeadPhone Playback Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Right HeadPhone Playback Volume",
+	       WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+/* OUT2 - Speakers */
+SOC_SINGLE("Left Speaker Playback ZC Switch",
+	   WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
+SOC_SINGLE("Left Speaker Playback Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Left Speaker Playback Volume",
+	       WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Right Speaker Playback ZC Switch",
+	   WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+SOC_SINGLE("Right Speaker Playback Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+SOC_SINGLE_TLV("Right Speaker Playback Volume",
+	       WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+SOC_SINGLE("Left Capture Boost(+20dB)",
+	   WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
+SOC_SINGLE("Right Capture Boost(+20dB)",
+	   WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
+
+/* OUT3/4 - Line Output */
+SOC_SINGLE("Left Line Playback Switch", WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
+SOC_SINGLE("Right Line Playback Switch", WM8978_OUT4_MIXER_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, 1),
+};
+
+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, 1),
+};
+
+/* OUT3/OUT4 Mixer not implemented */
+
+/* Mixer #2: Input PGA Mute */
+static const struct snd_kcontrol_new wm8978_left_inpga[] = {
+SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("Left MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("Left MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_inpga[] = {
+SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
+SOC_DAPM_SINGLE("Right MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
+SOC_DAPM_SINGLE("Right MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
+};
+
+/* Mixer #3: Boost (Input) mixer */
+static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
+SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
+SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
+};
+
+/* AUX Input boost vol */
+static const struct snd_kcontrol_new wm8978_aux_boost_controls[] = {
+SOC_DAPM_SINGLE("Left Aux Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0),
+SOC_DAPM_SINGLE("Right Aux Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0),
+};
+
+/* Mic Input boost vol */
+static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
+SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
+SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
+};
+
+#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
+							m, ARRAY_SIZE(m))
+
+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),
+
+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_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),
+
+/* Mixer #1: OUT1,2 */
+MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_out_mixer),
+MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_right_out_mixer),
+
+MIXER_ARRAY("Left Input PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_inpga),
+MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
+		   wm8978_right_inpga),
+
+MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
+		   wm8978_left_boost_mixer),
+MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
+		   wm8978_right_boost_mixer),
+
+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 Input PGA"},
+	{"Right Boost Mixer", NULL, "R2"},
+
+	{"Left ADC", NULL, "Left Boost Mixer"},
+
+	{"Left Boost Mixer", NULL, "LAUX"},
+	{"Left Boost Mixer", NULL, "Left Input PGA"},
+	{"Left Boost Mixer", NULL, "L2"},
+
+	/* Input PGA */
+	{"Right Input PGA", "R2 Switch", "R2"},
+	{"Right Input PGA", "Right MicN Switch", "RMICN"},
+	{"Right Input PGA", "Right MicP Switch", "RMICP"},
+
+	{"Left Input PGA", "L2 Switch", "L2"},
+	{"Left Input PGA", "Left MicN Switch", "LMICN"},
+	{"Left Input PGA", "Left 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 Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		Ndiv = target / source;
+	} else {
+		pll_div->div2 = 0;
+	}
+
+	if (Ndiv < 6 || Ndiv > 12)
+		dev_warn(wm8978_codec->dev,
+			 "WM8978 N value exceeds recommended range! N = %u\n",
+			 Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target - source * Ndiv;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod + source / 2;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	pll_div->k = K;
+}
+
+static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_pll_div pll_div;
+	u16 reg;
+
+	if (freq_in == 0 || freq_out == 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8978_CLOCKING);
+		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
+		return 0;
+	}
+
+	pll_factors(&pll_div, freq_out, freq_in);
+
+	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);
+
+	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);
+	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
+	/* Output PLL to GPIO1 */
+	snd_soc_write(codec, WM8978_GPIO_CONTROL,
+		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8978_CLOCKING);
+	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
+
+	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;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8978_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x1cf;
+		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
+		break;
+	case WM8978_MCLKDIV:
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x11f;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	case WM8978_ADCCLK:
+		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & 0x1f7;
+		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
+		break;
+	case WM8978_DACCLK:
+		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & 0x1f7;
+		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
+		break;
+	case WM8978_BCLKDIV:
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x1e3;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "%s: ID %d, value %x\n",
+		__func__, div_id, reg | div);
+
+	return 0;
+}
+
+/*
+ * 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;
+	u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
+	u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
+
+	/* 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;
+}
+
+/*
+ * 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;
+	u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
+	u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
+
+	dev_dbg(codec->dev, "%s: fmt %d, rate %u\n", __func__,
+		params_format(params), params_rate(params));
+
+	/* 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;
+	}
+
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+	/* Mic bias */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
+		      (snd_soc_read(codec, 1) & ~4) | 0x10);
+
+	/* Out-1 enabled, left/right input channel enabled */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
+
+	/* Out-2 disabled, right/left output channel enabled, dac enabled */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
+
+	return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
+
+	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+	if (mute)
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
+	else
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
+
+	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:
+		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:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\n", __func__, level, power1);
+
+	codec->bias_level = level;
+	return 0;
+}
+
+/* Also supports 12kHz */
+#define WM8978_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \
+	SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define 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_pll	= wm8978_set_dai_pll,
+};
+
+struct snd_soc_dai wm8978_dai = {
+	.name = "WM8978 HiFi",
+	.id = 1,
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8978_RATES,
+		.formats = WM8978_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8978_RATES,
+		.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;
+
+	/* we only need to suspend if we are a valid card */
+	if (!codec->card)
+		return 0;
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	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;
+	int i;
+	u16 data;
+	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(wm8978_reg); i++) {
+		if (i == WM8978_RESET)
+			continue;
+		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
+		codec->hw_write(codec->control_data, (char *)&data, 2);
+	}
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	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);
+
+static __devinit int wm8978_register(struct wm8978_priv *wm8978)
+{
+	int ret;
+	struct snd_soc_codec *codec = &wm8978->codec;
+
+	if (wm8978_codec) {
+		dev_err(codec->dev, "Another WM8978 is registered\n");
+		return -EINVAL;
+	}
+
+	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));
+
+	/* 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..61e39c0
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,84 @@
+/*
+ * 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_OPCLKDIV,
+	WM8978_MCLKDIV,
+	WM8978_ADCCLK,
+	WM8978_DACCLK,
+	WM8978_BCLKDIV,
+};
+
+extern struct snd_soc_dai wm8978_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8978;
+
+#endif	/* __WM8978_H__ */
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support
  2010-01-19  8:08 ` [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support Guennadi Liakhovetski
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
Sound Interface Unit (SIU). This patch adds drivers for this interface and
support for the sh7722 Migo-R board.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

As mentioned in the introduction mail, this driver requires firmware to be 
loaded from user-space using the standard hotplug functionality.

 arch/sh/include/asm/siu.h |   26 ++
 sound/soc/sh/Kconfig      |   16 +
 sound/soc/sh/Makefile     |    4 +
 sound/soc/sh/migor.c      |  261 ++++++++++++++
 sound/soc/sh/siu.h        |  217 ++++++++++++
 sound/soc/sh/siu_dai.c    |  833 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/sh/siu_pcm.c    |  716 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 2073 insertions(+), 0 deletions(-)
 create mode 100644 arch/sh/include/asm/siu.h
 create mode 100644 sound/soc/sh/migor.c
 create mode 100644 sound/soc/sh/siu.h
 create mode 100644 sound/soc/sh/siu_dai.c
 create mode 100644 sound/soc/sh/siu_pcm.c

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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..eec6fe5 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -26,6 +26,14 @@ config SND_SOC_SH4_FSI
 	help
 	  This option enables FSI sound support
 
+config SND_SOC_SH4_SIU
+	tristate "SH4 SIU support"
+	depends on CPU_SUBTYPE_SH7722
+	select DMADEVICES
+	select SH_DMAE
+	help
+	  This option enables SIU sound support
+
 ##
 ## Boards
 ##
@@ -55,4 +63,12 @@ config SND_FSI_DA7210
 	  This option enables generic sound support for the
 	  FSI - DA7210 unit
 
+config SND_SIU_MIGOR
+	tristate "SIU sound support on Migo-R"
+	depends on SND_SOC_SH4_SIU && SH_MIGOR
+	select SND_SOC_WM8978
+	help
+	  This option enables generic sound support for the
+	  SH7722 Migo-R board
+
 endmenu
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
index 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,15 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 0000000..507e59e
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,261 @@
+/*
+ * 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 = 49152350 / 12;
+
+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};
+
+/* 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;
+	unsigned int mclk_div, opclk_div, f2;
+	int ret, mclk_idx;
+	unsigned int rate = params_rate(params);
+
+	switch (rate) {
+	case 48000:
+		mclk_div = 0x40;
+		opclk_div = 0;
+		/* f2 = 98304000, was 98304050 */
+		break;
+	case 44100:
+		mclk_div = 0x40;
+		opclk_div = 0;
+		/* f2 = 90316800, was 90317500 */
+		break;
+	case 32000:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 131072000, was 131072500 */
+		break;
+	case 24000:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 98304000, was 98304700 */
+		break;
+	case 22050:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 90316800, was 90317500 */
+		break;
+	case 16000:
+		mclk_div = 0xa0;
+		opclk_div = 0x020;
+		/* f2 = 98304000, was 98304700 */
+		break;
+	case 11025:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 45158400, was 45158752 */
+		break;
+	default:
+	case 8000:
+		mclk_div = 0xa0;
+		opclk_div = 0x020;
+		/* f2 = 49152000, was 49152350 */
+		break;
+	}
+
+	mclk_idx = mclk_div >> 5;
+	/*
+	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
+	 * in WM8978 datasheet
+	 */
+	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
+		mclk_denominator[mclk_idx];
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
+				     mclk_div & 0xe0);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
+	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_pll(codec_dai, 0, 0, 13000000, f2);
+	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;
+
+	/* See Figure 40 */
+	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;
+	/*
+	 * 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);
+
+	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, CLKB_EXT, codec_freq / 2,
+			       SND_SOC_CLOCK_IN);
+
+	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;
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);
+}
+
+static int migor_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	int ret;
+
+	/* Activate DAC output routes */
+	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
+	if (ret < 0) {
+		dev_warn(socdev->dev, "Left err %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_dapm_enable_pin(codec, "Right Speaker Out");
+	if (ret < 0) {
+		dev_warn(socdev->dev, "Right err %d\n", ret);
+		return ret;
+	}
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+	.hw_params = migor_hw_params,
+	.hw_free = migor_hw_free,
+	.startup = migor_startup,
+};
+
+/* 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,
+};
+
+/* 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..e7cba83
--- /dev/null
+++ b/sound/soc/sh/siu.h
@@ -0,0 +1,217 @@
+/*
+ * 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_PORTA		0		/* port A */
+#define SIU_PORTB		1		/* port B */
+#define MAX_SIU_PORTS		2
+
+/* SIU clock configuration */
+enum {CLKA_PLL, CLKA_EXT, CLKB_PLL, CLKB_EXT};
+
+/* Board specifics */
+#if defined(CONFIG_CPU_SUBTYPE_SH7722)
+# define MAX_VOLUME		0x1000
+#else
+# define MAX_VOLUME		0x7fff
+#endif
+
+struct siu_info {
+	int			port_id;
+	u32 __iomem		*pram;
+	u32 __iomem		*xram;
+	u32 __iomem		*yram;
+	u32 __iomem		*reg;
+	struct siu_firmware	fw;
+};
+
+#define PRAM_SIZE	0x2000
+#define XRAM_SIZE	0x800
+#define YRAM_SIZE	0x800
+
+#define XRAM_OFFSET	0x4000
+#define YRAM_OFFSET	0x6000
+#define REG_OFFSET	0xc000
+
+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;
+	void				*mono_buf;	/* Mono buffer */
+	size_t				mono_size;	/* and its size in bytes */
+	snd_pcm_sframes_t		xfer_cnt;	/* Number of frames */
+	u8				rw_flg;		/* transfer status */
+	/* DMA status */
+	dma_addr_t			mono_dma;
+	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[MAX_SIU_PORTS];
+
+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];
+}
+
+#define PLAYBACK_ENABLED	1
+#define CAPTURE_ENABLED		2
+
+#define VOLUME_CAPTURE		0
+#define VOLUME_PLAYBACK		1
+#define DFLT_VOLUME_LEVEL	0x08000800
+
+#define PERIOD_BYTES_MAX	8192		/* DMA transfer/period size */
+#define PERIOD_BYTES_MIN	256		/* DMA transfer/period size */
+#define PERIODS_MAX		64		/* Max periods in buffer */
+#define PERIODS_MIN		4		/* Min periods in buffer */
+#define BUFFER_BYTES_MAX	(PERIOD_BYTES_MAX * PERIODS_MAX)
+#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 */
+
+/* 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 IFCTL		(0x000 / sizeof(u32))
+#define SRCTL		(0x004 / sizeof(u32))
+#define SFORM		(0x008 / sizeof(u32))
+#define CKCTL		(0x00c / sizeof(u32))
+#define TRDAT		(0x010 / sizeof(u32))
+#define STFIFO		(0x014 / sizeof(u32))
+#define DPAK		(0x01c / sizeof(u32))
+#define CKREV		(0x020 / sizeof(u32))
+#define EVNTC		(0x028 / sizeof(u32))
+#define SBCTL		(0x040 / sizeof(u32))
+#define SBPSET		(0x044 / sizeof(u32))
+#define SBFSTS		(0x068 / sizeof(u32))
+#define SBDVCA		(0x06c / sizeof(u32))
+#define SBDVCB		(0x070 / sizeof(u32))
+#define SBACTIV		(0x074 / sizeof(u32))
+#define DMAIA		(0x090 / sizeof(u32))
+#define DMAIB		(0x094 / sizeof(u32))
+#define DMAOA		(0x098 / sizeof(u32))
+#define DMAOB		(0x09c / sizeof(u32))
+#define DMAML		(0x0a0 / sizeof(u32))
+#define SPSTS		(0x0cc / sizeof(u32))
+#define SPCTL		(0x0d0 / sizeof(u32))
+#define BRGASEL		(0x100 / sizeof(u32))
+#define BRRA		(0x104 / sizeof(u32))
+#define BRGBSEL		(0x108 / sizeof(u32))
+#define 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..e5dbedb
--- /dev/null
+++ b/sound/soc/sh/siu_dai.c
@@ -0,0 +1,833 @@
+/*
+ * 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"
+
+/*
+ * 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[MAX_SIU_PORTS] = {
+	[SIU_PORTA] = {
+		.playback = {
+			.i2s	= 0x50000000,
+			.pcm	= 0x40000000,
+			.spdif	= 0x80000000,	/* not on all SIU versions */
+			.mask	= 0xd0000000,
+		},
+		.capture = {
+			.i2s	= 0x05000000,
+			.pcm	= 0x04000000,
+			.spdif	= 0x08000000,
+			.mask	= 0x0d000000,
+		},
+	},
+	[SIU_PORTB] = {
+		.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 + 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 + SRCTL, 0x301);
+
+	/* portA%6fs, portB%6fs */
+	siu_write32(base + CKCTL, 0x40400000);
+
+	/* portA's BRG does not divide SIUCKA */
+	siu_write32(base + BRGASEL, 0);
+	siu_write32(base + BRRA, 0);
+
+	/* portB's BRG divides SIUCKB by half */
+	siu_write32(base + BRGBSEL, 1);
+	siu_write32(base + BRRB, 0);
+
+	siu_write32(base + IFCTL, 0x44440000);
+
+	/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
+	siu_write32(base + SFORM, 0x0c0c0000);
+
+	/*
+	 * Volume levels: looks like the DSP firmware implements volume controls
+	 * differently from what's described in the datasheet
+	 */
+	siu_write32(base + SBDVCA, port_info->playback.volume);
+	siu_write32(base + 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 + 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;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	u32 srctl, ifctl;
+
+	srctl = siu_read32(base + SRCTL);
+	ifctl = siu_read32(base + IFCTL);
+
+	switch (info->port_id) {
+	case SIU_PORTA:
+		/* portA operates */
+		srctl |= 0x200;
+		ifctl &= ~0xc2;
+		/* Mono mode is not used, instead, stereo is simulated */
+		if (rt->channels = 1)
+			ifctl |= 0x80;
+		break;
+	case SIU_PORTB:
+		/* portB operates */
+		srctl |= 0x100;
+		ifctl &= ~0x31;
+		/* Mono mode is not used, instead, stereo is simulated */
+		if (rt->channels = 1)
+			ifctl |= 0x20;
+		break;
+	}
+
+	siu_write32(base + SRCTL, srctl);
+	/* Unmute and configure portA */
+	siu_write32(base + 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 + DPAK);
+
+	switch (info->port_id) {
+	case SIU_PORTA:
+		dpak &= ~0xc0000000;
+		break;
+	case SIU_PORTB:
+		dpak &= ~0x00c00000;
+		break;
+	}
+
+	siu_write32(base + 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 + TRDAT, port_info->trdat);
+	port_info->trdat = 0x0;
+
+
+	/* SPB start condition: software */
+	siu_write32(base + SBACTIV, 0);
+	/* Start SPB */
+	siu_write32(base + SBCTL, 0xc0000000);
+	/* Wait for program to halt */
+	cnt = 0x10000;
+	while (--cnt && siu_read32(base + SBCTL) != 0x80000000)
+		cpu_relax();
+
+	if (!cnt)
+		return -EBUSY;
+
+	/* SPB program start address setting */
+	siu_write32(base + SBPSET, 0x00400000);
+	/* SPB hardware start(FIFOCTL source) */
+	siu_write32(base + 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 + SBACTIV, 0);
+	/* SPB stop */
+	siu_write32(base + 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		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= BUFFER_BYTES_MAX,
+	.period_bytes_min	= PERIOD_BYTES_MIN,
+	.period_bytes_max	= PERIOD_BYTES_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= 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 = 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] > MAX_VOLUME ||
+	    ucontrol->value.integer.value[1] < 0 ||
+	    ucontrol->value.integer.value[1] > 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 + 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 + 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 + IFCTL);
+	siu_write32(base + 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 CLKA_PLL:
+		siu_name = "siua_clk";
+		parent_name = "pll_clk";
+		break;
+	case CLKA_EXT:
+		siu_name = "siua_clk";
+		parent_name = "siumcka_clk";
+		break;
+	case CLKB_PLL:
+		siu_name = "siub_clk";
+		parent_name = "pll_clk";
+		break;
+	case 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 = 1,
+		.channels_max = 2,
+		.formats = SNDRV_PCM_FMTBIT_S16,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+	},
+	.capture = {
+		.channels_min = 1,
+		.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..afe2e6e
--- /dev/null
+++ b/sound/soc/sh/siu_pcm.c
@@ -0,0 +1,716 @@
+/*
+ * 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"
+
+struct siu_port *siu_ports[MAX_SIU_PORTS];
+
+static void copy_playback_period(struct siu_stream *siu_stream)
+{
+	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
+	u16 *src;
+	u32 *dst;
+	int cp_cnt;
+	int i;
+
+	src = (u16 *)PERIOD_OFFSET(rt->dma_area,
+				   siu_stream->cur_period,
+				   siu_stream->period_bytes);
+	dst = siu_stream->mono_buf;
+	cp_cnt = siu_stream->xfer_cnt;
+
+	for (i = 0; i < cp_cnt; i++)
+		*dst++ = *src++;
+}
+
+static void copy_capture_period(struct siu_stream *siu_stream)
+{
+	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
+	u16 *src;
+	u16 *dst;
+	int cp_cnt;
+	int i;
+
+	dst = (u16 *)PERIOD_OFFSET(rt->dma_area,
+				   siu_stream->cur_period,
+				   siu_stream->period_bytes);
+	src = (u16 *)siu_stream->mono_buf;
+	cp_cnt = siu_stream->xfer_cnt;
+
+	for (i = 0; i < cp_cnt; i++) {
+		*dst++ = *src;
+		src += 2;
+	}
+}
+
+/* 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 + STFIFO);
+	siu_write32(base + 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;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+
+	if (!siu_stream->rw_flg)
+		return;
+
+	if (substream->runtime->channels = 1 &&
+	    substream->stream = SNDRV_PCM_STREAM_CAPTURE)
+		copy_capture_period(siu_stream);
+
+	/* 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 + STFIFO);
+	siu_write32(base + 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 + STFIFO);
+	siu_write32(base + STFIFO, siu_read32(base + 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;
+
+		if (rt->channels = 1) {
+			buff = siu_stream->mono_dma;
+			virt = siu_stream->mono_buf;
+			count = siu_stream->mono_size;
+		} else {
+			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 {
+		/* For mono streams we need to use the mono buffer */
+		if (rt->channels = 1) {
+			copy_playback_period(siu_stream);
+			siu_pcm_wr_set(port_info,
+				siu_stream->mono_dma, siu_stream->mono_size);
+		} 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 + STFIFO);
+	siu_write32(base + 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 void siu_pcm_mono_free(struct device *dev, struct siu_stream *stream)
+{
+	dma_free_coherent(dev, stream->mono_size,
+			  stream->mono_buf, stream->mono_dma);
+	stream->mono_buf = NULL;
+	stream->mono_size = 0;
+}
+
+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, mono %p\n", __func__,
+		info->port_id, siu_stream->mono_buf);
+
+	if (siu_stream->mono_buf && ss->runtime->channels = 1)
+		siu_pcm_mono_free(ss->pcm->card->dev, siu_stream);
+
+	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_mono_alloc(struct device *dev, struct siu_stream *siu_stream)
+{
+	/*
+	 * The hardware only supports stereo (2 channels) streams. We must
+	 * convert mono streams (1 channel) to stereo streams. To do that we
+	 * just copy the mono data to one of the stereo channels and instruct
+	 * the siu to play the data on both channels. However, the idle
+	 * channel must also be present in the buffer, so we use an extra
+	 * buffer twice as big as one mono period. Also since this function
+	 * can be called multiple times, we must adjust the buffer size.
+	 */
+	if (siu_stream->mono_buf && siu_stream->mono_size !+	    siu_stream->period_bytes * 2) {
+		dma_free_coherent(dev, siu_stream->mono_size,
+				  siu_stream->mono_buf, siu_stream->mono_dma);
+		siu_stream->mono_buf = NULL;
+		siu_stream->mono_size = 0;
+	}
+
+	if (!siu_stream->mono_buf) {
+		siu_stream->mono_buf = dma_alloc_coherent(dev,
+						siu_stream->period_bytes * 2,
+						&siu_stream->mono_dma,
+						GFP_KERNEL);
+		if (!siu_stream->mono_buf)
+			return -ENOMEM;
+
+		siu_stream->mono_size = siu_stream->period_bytes * 2;
+	}
+
+	dev_dbg(dev, "%s: mono buffer @ %p\n", __func__, siu_stream->mono_buf);
+
+	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;
+
+	if (rt->channels = 1) {
+		int ret = siu_pcm_mono_alloc(ss->pcm->card->dev,
+					     siu_stream);
+		if (ret < 0)
+			return ret;
+	}
+
+	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 + EVNTC),
+		siu_read32(base + 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 >= MAX_SIU_PORTS)
+		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,
+					BUFFER_BYTES_MAX, BUFFER_BYTES_MAX);
+		if (ret < 0) {
+			dev_err(card->dev,
+			       "snd_pcm_lib_preallocate_pages_for_all() err=%d",
+				ret);
+			goto fail;
+		}
+
+		/* 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);
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
Sound Interface Unit (SIU). This patch adds drivers for this interface and
support for the sh7722 Migo-R board.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

As mentioned in the introduction mail, this driver requires firmware to be 
loaded from user-space using the standard hotplug functionality.

 arch/sh/include/asm/siu.h |   26 ++
 sound/soc/sh/Kconfig      |   16 +
 sound/soc/sh/Makefile     |    4 +
 sound/soc/sh/migor.c      |  261 ++++++++++++++
 sound/soc/sh/siu.h        |  217 ++++++++++++
 sound/soc/sh/siu_dai.c    |  833 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/sh/siu_pcm.c    |  716 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 2073 insertions(+), 0 deletions(-)
 create mode 100644 arch/sh/include/asm/siu.h
 create mode 100644 sound/soc/sh/migor.c
 create mode 100644 sound/soc/sh/siu.h
 create mode 100644 sound/soc/sh/siu_dai.c
 create mode 100644 sound/soc/sh/siu_pcm.c

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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..eec6fe5 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -26,6 +26,14 @@ config SND_SOC_SH4_FSI
 	help
 	  This option enables FSI sound support
 
+config SND_SOC_SH4_SIU
+	tristate "SH4 SIU support"
+	depends on CPU_SUBTYPE_SH7722
+	select DMADEVICES
+	select SH_DMAE
+	help
+	  This option enables SIU sound support
+
 ##
 ## Boards
 ##
@@ -55,4 +63,12 @@ config SND_FSI_DA7210
 	  This option enables generic sound support for the
 	  FSI - DA7210 unit
 
+config SND_SIU_MIGOR
+	tristate "SIU sound support on Migo-R"
+	depends on SND_SOC_SH4_SIU && SH_MIGOR
+	select SND_SOC_WM8978
+	help
+	  This option enables generic sound support for the
+	  SH7722 Migo-R board
+
 endmenu
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
index 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,15 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 0000000..507e59e
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,261 @@
+/*
+ * 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 = 49152350 / 12;
+
+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};
+
+/* 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;
+	unsigned int mclk_div, opclk_div, f2;
+	int ret, mclk_idx;
+	unsigned int rate = params_rate(params);
+
+	switch (rate) {
+	case 48000:
+		mclk_div = 0x40;
+		opclk_div = 0;
+		/* f2 = 98304000, was 98304050 */
+		break;
+	case 44100:
+		mclk_div = 0x40;
+		opclk_div = 0;
+		/* f2 = 90316800, was 90317500 */
+		break;
+	case 32000:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 131072000, was 131072500 */
+		break;
+	case 24000:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 98304000, was 98304700 */
+		break;
+	case 22050:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 90316800, was 90317500 */
+		break;
+	case 16000:
+		mclk_div = 0xa0;
+		opclk_div = 0x020;
+		/* f2 = 98304000, was 98304700 */
+		break;
+	case 11025:
+		mclk_div = 0x80;
+		opclk_div = 0x010;
+		/* f2 = 45158400, was 45158752 */
+		break;
+	default:
+	case 8000:
+		mclk_div = 0xa0;
+		opclk_div = 0x020;
+		/* f2 = 49152000, was 49152350 */
+		break;
+	}
+
+	mclk_idx = mclk_div >> 5;
+	/*
+	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
+	 * in WM8978 datasheet
+	 */
+	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
+		mclk_denominator[mclk_idx];
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
+				     mclk_div & 0xe0);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
+	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_pll(codec_dai, 0, 0, 13000000, f2);
+	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;
+
+	/* See Figure 40 */
+	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;
+	/*
+	 * 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);
+
+	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, CLKB_EXT, codec_freq / 2,
+			       SND_SOC_CLOCK_IN);
+
+	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;
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);
+}
+
+static int migor_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	int ret;
+
+	/* Activate DAC output routes */
+	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
+	if (ret < 0) {
+		dev_warn(socdev->dev, "Left err %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_dapm_enable_pin(codec, "Right Speaker Out");
+	if (ret < 0) {
+		dev_warn(socdev->dev, "Right err %d\n", ret);
+		return ret;
+	}
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+	.hw_params = migor_hw_params,
+	.hw_free = migor_hw_free,
+	.startup = migor_startup,
+};
+
+/* 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,
+};
+
+/* 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..e7cba83
--- /dev/null
+++ b/sound/soc/sh/siu.h
@@ -0,0 +1,217 @@
+/*
+ * 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_PORTA		0		/* port A */
+#define SIU_PORTB		1		/* port B */
+#define MAX_SIU_PORTS		2
+
+/* SIU clock configuration */
+enum {CLKA_PLL, CLKA_EXT, CLKB_PLL, CLKB_EXT};
+
+/* Board specifics */
+#if defined(CONFIG_CPU_SUBTYPE_SH7722)
+# define MAX_VOLUME		0x1000
+#else
+# define MAX_VOLUME		0x7fff
+#endif
+
+struct siu_info {
+	int			port_id;
+	u32 __iomem		*pram;
+	u32 __iomem		*xram;
+	u32 __iomem		*yram;
+	u32 __iomem		*reg;
+	struct siu_firmware	fw;
+};
+
+#define PRAM_SIZE	0x2000
+#define XRAM_SIZE	0x800
+#define YRAM_SIZE	0x800
+
+#define XRAM_OFFSET	0x4000
+#define YRAM_OFFSET	0x6000
+#define REG_OFFSET	0xc000
+
+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;
+	void				*mono_buf;	/* Mono buffer */
+	size_t				mono_size;	/* and its size in bytes */
+	snd_pcm_sframes_t		xfer_cnt;	/* Number of frames */
+	u8				rw_flg;		/* transfer status */
+	/* DMA status */
+	dma_addr_t			mono_dma;
+	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[MAX_SIU_PORTS];
+
+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];
+}
+
+#define PLAYBACK_ENABLED	1
+#define CAPTURE_ENABLED		2
+
+#define VOLUME_CAPTURE		0
+#define VOLUME_PLAYBACK		1
+#define DFLT_VOLUME_LEVEL	0x08000800
+
+#define PERIOD_BYTES_MAX	8192		/* DMA transfer/period size */
+#define PERIOD_BYTES_MIN	256		/* DMA transfer/period size */
+#define PERIODS_MAX		64		/* Max periods in buffer */
+#define PERIODS_MIN		4		/* Min periods in buffer */
+#define BUFFER_BYTES_MAX	(PERIOD_BYTES_MAX * PERIODS_MAX)
+#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 */
+
+/* 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 IFCTL		(0x000 / sizeof(u32))
+#define SRCTL		(0x004 / sizeof(u32))
+#define SFORM		(0x008 / sizeof(u32))
+#define CKCTL		(0x00c / sizeof(u32))
+#define TRDAT		(0x010 / sizeof(u32))
+#define STFIFO		(0x014 / sizeof(u32))
+#define DPAK		(0x01c / sizeof(u32))
+#define CKREV		(0x020 / sizeof(u32))
+#define EVNTC		(0x028 / sizeof(u32))
+#define SBCTL		(0x040 / sizeof(u32))
+#define SBPSET		(0x044 / sizeof(u32))
+#define SBFSTS		(0x068 / sizeof(u32))
+#define SBDVCA		(0x06c / sizeof(u32))
+#define SBDVCB		(0x070 / sizeof(u32))
+#define SBACTIV		(0x074 / sizeof(u32))
+#define DMAIA		(0x090 / sizeof(u32))
+#define DMAIB		(0x094 / sizeof(u32))
+#define DMAOA		(0x098 / sizeof(u32))
+#define DMAOB		(0x09c / sizeof(u32))
+#define DMAML		(0x0a0 / sizeof(u32))
+#define SPSTS		(0x0cc / sizeof(u32))
+#define SPCTL		(0x0d0 / sizeof(u32))
+#define BRGASEL		(0x100 / sizeof(u32))
+#define BRRA		(0x104 / sizeof(u32))
+#define BRGBSEL		(0x108 / sizeof(u32))
+#define 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..e5dbedb
--- /dev/null
+++ b/sound/soc/sh/siu_dai.c
@@ -0,0 +1,833 @@
+/*
+ * 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"
+
+/*
+ * 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[MAX_SIU_PORTS] = {
+	[SIU_PORTA] = {
+		.playback = {
+			.i2s	= 0x50000000,
+			.pcm	= 0x40000000,
+			.spdif	= 0x80000000,	/* not on all SIU versions */
+			.mask	= 0xd0000000,
+		},
+		.capture = {
+			.i2s	= 0x05000000,
+			.pcm	= 0x04000000,
+			.spdif	= 0x08000000,
+			.mask	= 0x0d000000,
+		},
+	},
+	[SIU_PORTB] = {
+		.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 + 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 + SRCTL, 0x301);
+
+	/* portA=256fs, portB=256fs */
+	siu_write32(base + CKCTL, 0x40400000);
+
+	/* portA's BRG does not divide SIUCKA */
+	siu_write32(base + BRGASEL, 0);
+	siu_write32(base + BRRA, 0);
+
+	/* portB's BRG divides SIUCKB by half */
+	siu_write32(base + BRGBSEL, 1);
+	siu_write32(base + BRRB, 0);
+
+	siu_write32(base + IFCTL, 0x44440000);
+
+	/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
+	siu_write32(base + SFORM, 0x0c0c0000);
+
+	/*
+	 * Volume levels: looks like the DSP firmware implements volume controls
+	 * differently from what's described in the datasheet
+	 */
+	siu_write32(base + SBDVCA, port_info->playback.volume);
+	siu_write32(base + 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 + 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;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	u32 srctl, ifctl;
+
+	srctl = siu_read32(base + SRCTL);
+	ifctl = siu_read32(base + IFCTL);
+
+	switch (info->port_id) {
+	case SIU_PORTA:
+		/* portA operates */
+		srctl |= 0x200;
+		ifctl &= ~0xc2;
+		/* Mono mode is not used, instead, stereo is simulated */
+		if (rt->channels == 1)
+			ifctl |= 0x80;
+		break;
+	case SIU_PORTB:
+		/* portB operates */
+		srctl |= 0x100;
+		ifctl &= ~0x31;
+		/* Mono mode is not used, instead, stereo is simulated */
+		if (rt->channels == 1)
+			ifctl |= 0x20;
+		break;
+	}
+
+	siu_write32(base + SRCTL, srctl);
+	/* Unmute and configure portA */
+	siu_write32(base + 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 + DPAK);
+
+	switch (info->port_id) {
+	case SIU_PORTA:
+		dpak &= ~0xc0000000;
+		break;
+	case SIU_PORTB:
+		dpak &= ~0x00c00000;
+		break;
+	}
+
+	siu_write32(base + 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 + TRDAT, port_info->trdat);
+	port_info->trdat = 0x0;
+
+
+	/* SPB start condition: software */
+	siu_write32(base + SBACTIV, 0);
+	/* Start SPB */
+	siu_write32(base + SBCTL, 0xc0000000);
+	/* Wait for program to halt */
+	cnt = 0x10000;
+	while (--cnt && siu_read32(base + SBCTL) != 0x80000000)
+		cpu_relax();
+
+	if (!cnt)
+		return -EBUSY;
+
+	/* SPB program start address setting */
+	siu_write32(base + SBPSET, 0x00400000);
+	/* SPB hardware start(FIFOCTL source) */
+	siu_write32(base + 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 + SBACTIV, 0);
+	/* SPB stop */
+	siu_write32(base + 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		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= BUFFER_BYTES_MAX,
+	.period_bytes_min	= PERIOD_BYTES_MIN,
+	.period_bytes_max	= PERIOD_BYTES_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= 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 = 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] > MAX_VOLUME ||
+	    ucontrol->value.integer.value[1] < 0 ||
+	    ucontrol->value.integer.value[1] > 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 + 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 + 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 + IFCTL);
+	siu_write32(base + 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 CLKA_PLL:
+		siu_name = "siua_clk";
+		parent_name = "pll_clk";
+		break;
+	case CLKA_EXT:
+		siu_name = "siua_clk";
+		parent_name = "siumcka_clk";
+		break;
+	case CLKB_PLL:
+		siu_name = "siub_clk";
+		parent_name = "pll_clk";
+		break;
+	case 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 = 1,
+		.channels_max = 2,
+		.formats = SNDRV_PCM_FMTBIT_S16,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+	},
+	.capture = {
+		.channels_min = 1,
+		.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..afe2e6e
--- /dev/null
+++ b/sound/soc/sh/siu_pcm.c
@@ -0,0 +1,716 @@
+/*
+ * 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"
+
+struct siu_port *siu_ports[MAX_SIU_PORTS];
+
+static void copy_playback_period(struct siu_stream *siu_stream)
+{
+	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
+	u16 *src;
+	u32 *dst;
+	int cp_cnt;
+	int i;
+
+	src = (u16 *)PERIOD_OFFSET(rt->dma_area,
+				   siu_stream->cur_period,
+				   siu_stream->period_bytes);
+	dst = siu_stream->mono_buf;
+	cp_cnt = siu_stream->xfer_cnt;
+
+	for (i = 0; i < cp_cnt; i++)
+		*dst++ = *src++;
+}
+
+static void copy_capture_period(struct siu_stream *siu_stream)
+{
+	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
+	u16 *src;
+	u16 *dst;
+	int cp_cnt;
+	int i;
+
+	dst = (u16 *)PERIOD_OFFSET(rt->dma_area,
+				   siu_stream->cur_period,
+				   siu_stream->period_bytes);
+	src = (u16 *)siu_stream->mono_buf;
+	cp_cnt = siu_stream->xfer_cnt;
+
+	for (i = 0; i < cp_cnt; i++) {
+		*dst++ = *src;
+		src += 2;
+	}
+}
+
+/* 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 + STFIFO);
+	siu_write32(base + 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;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+
+	if (!siu_stream->rw_flg)
+		return;
+
+	if (substream->runtime->channels == 1 &&
+	    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		copy_capture_period(siu_stream);
+
+	/* 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 + STFIFO);
+	siu_write32(base + 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 + STFIFO);
+	siu_write32(base + STFIFO, siu_read32(base + 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;
+
+		if (rt->channels == 1) {
+			buff = siu_stream->mono_dma;
+			virt = siu_stream->mono_buf;
+			count = siu_stream->mono_size;
+		} else {
+			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 {
+		/* For mono streams we need to use the mono buffer */
+		if (rt->channels == 1) {
+			copy_playback_period(siu_stream);
+			siu_pcm_wr_set(port_info,
+				siu_stream->mono_dma, siu_stream->mono_size);
+		} 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 + STFIFO);
+	siu_write32(base + 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 void siu_pcm_mono_free(struct device *dev, struct siu_stream *stream)
+{
+	dma_free_coherent(dev, stream->mono_size,
+			  stream->mono_buf, stream->mono_dma);
+	stream->mono_buf = NULL;
+	stream->mono_size = 0;
+}
+
+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, mono %p\n", __func__,
+		info->port_id, siu_stream->mono_buf);
+
+	if (siu_stream->mono_buf && ss->runtime->channels == 1)
+		siu_pcm_mono_free(ss->pcm->card->dev, siu_stream);
+
+	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_mono_alloc(struct device *dev, struct siu_stream *siu_stream)
+{
+	/*
+	 * The hardware only supports stereo (2 channels) streams. We must
+	 * convert mono streams (1 channel) to stereo streams. To do that we
+	 * just copy the mono data to one of the stereo channels and instruct
+	 * the siu to play the data on both channels. However, the idle
+	 * channel must also be present in the buffer, so we use an extra
+	 * buffer twice as big as one mono period. Also since this function
+	 * can be called multiple times, we must adjust the buffer size.
+	 */
+	if (siu_stream->mono_buf && siu_stream->mono_size !=
+	    siu_stream->period_bytes * 2) {
+		dma_free_coherent(dev, siu_stream->mono_size,
+				  siu_stream->mono_buf, siu_stream->mono_dma);
+		siu_stream->mono_buf = NULL;
+		siu_stream->mono_size = 0;
+	}
+
+	if (!siu_stream->mono_buf) {
+		siu_stream->mono_buf = dma_alloc_coherent(dev,
+						siu_stream->period_bytes * 2,
+						&siu_stream->mono_dma,
+						GFP_KERNEL);
+		if (!siu_stream->mono_buf)
+			return -ENOMEM;
+
+		siu_stream->mono_size = siu_stream->period_bytes * 2;
+	}
+
+	dev_dbg(dev, "%s: mono buffer @ %p\n", __func__, siu_stream->mono_buf);
+
+	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;
+
+	if (rt->channels == 1) {
+		int ret = siu_pcm_mono_alloc(ss->pcm->card->dev,
+					     siu_stream);
+		if (ret < 0)
+			return ret;
+	}
+
+	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 + EVNTC),
+		siu_read32(base + 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 >= MAX_SIU_PORTS)
+		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,
+					BUFFER_BYTES_MAX, BUFFER_BYTES_MAX);
+		if (ret < 0) {
+			dev_err(card->dev,
+			       "snd_pcm_lib_preallocate_pages_for_all() err=%d",
+				ret);
+			goto fail;
+		}
+
+		/* 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);
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 3/4] sh: add DMA slave definitions and SIU platform data to
  2010-01-19  8:08 ` [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support Guennadi Liakhovetski
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

This patch is required to use the SIU ASoC driver on sh7722 systems.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/kernel/cpu/sh4a/setup-sh7722.c |  103 ++++++++++++++++++++++++++++++--
 1 files changed, 97 insertions(+), 6 deletions(-)

diff --git a/arch/sh/kernel/cpu/sh4a/setup-sh7722.c b/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
index adb65a9..5c6a40d 100644
--- a/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
+++ b/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
@@ -18,8 +18,77 @@
 #include <asm/clock.h>
 #include <asm/mmzone.h>
 #include <asm/dma-sh.h>
+#include <asm/siu.h>
 #include <cpu/sh7722.h>
 
+static struct sh_dmae_slave_config sh7722_dmae_slaves[] = {
+	{
+		.slave_id	= SHDMA_SLAVE_SCIF0_TX,
+		.addr		= 0xffe0000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x21,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF0_RX,
+		.addr		= 0xffe00014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x22,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF1_TX,
+		.addr		= 0xffe1000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x25,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF1_RX,
+		.addr		= 0xffe10014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x26,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF2_TX,
+		.addr		= 0xffe2000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x29,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF2_RX,
+		.addr		= 0xffe20014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x2a,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUA_TX,
+		.addr		= 0xa454c098,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb1,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUA_RX,
+		.addr		= 0xa454c090,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb2,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUB_TX,
+		.addr		= 0xa454c09c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb5,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUB_RX,
+		.addr		= 0xa454c094,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb6,
+	},
+};
+
+static struct sh_dmae_pdata dma_platform_data = {
+	.mode		= 0,
+	.config		= sh7722_dmae_slaves,
+	.config_num	= ARRAY_SIZE(sh7722_dmae_slaves),
+};
+
+struct platform_device dma_device = {
+	.name		= "sh-dma-engine",
+	.id		= -1,
+	.dev		= {
+		.platform_data	= &dma_platform_data,
+	},
+};
+
 /* Serial */
 static struct plat_sci_port scif0_platform_data = {
 	.mapbase        = 0xffe00000,
@@ -388,15 +457,36 @@ static struct platform_device tmu2_device = {
 	},
 };
 
-static struct sh_dmae_pdata dma_platform_data = {
-	.mode = 0,
+static struct siu_platform siu_platform_data = {
+	.dma_dev	= &dma_device.dev,
+	.dma_slave_tx_a	= SHDMA_SLAVE_SIUA_TX,
+	.dma_slave_rx_a	= SHDMA_SLAVE_SIUA_RX,
+	.dma_slave_tx_b	= SHDMA_SLAVE_SIUB_TX,
+	.dma_slave_rx_b	= SHDMA_SLAVE_SIUB_RX,
 };
 
-static struct platform_device dma_device = {
-	.name		= "sh-dma-engine",
+static struct resource siu_resources[] = {
+	[0] = {
+		.start	= 0xa4540000,
+		.end	= 0xa454c10f,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= 108,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device siu_device = {
+	.name		= "sh_siu",
 	.id		= -1,
-	.dev		= {
-		.platform_data	= &dma_platform_data,
+	.dev = {
+		.platform_data	= &siu_platform_data,
+	},
+	.resource	= siu_resources,
+	.num_resources	= ARRAY_SIZE(siu_resources),
+	.archdata = {
+		.hwblk_id = HWBLK_SIU,
 	},
 };
 
@@ -414,6 +504,7 @@ static struct platform_device *sh7722_devices[] __initdata = {
 	&vpu_device,
 	&veu_device,
 	&jpu_device,
+	&siu_device,
 	&dma_device,
 };
 
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 3/4] sh: add DMA slave definitions and SIU platform data to sh7722 setup
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

This patch is required to use the SIU ASoC driver on sh7722 systems.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/kernel/cpu/sh4a/setup-sh7722.c |  103 ++++++++++++++++++++++++++++++--
 1 files changed, 97 insertions(+), 6 deletions(-)

diff --git a/arch/sh/kernel/cpu/sh4a/setup-sh7722.c b/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
index adb65a9..5c6a40d 100644
--- a/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
+++ b/arch/sh/kernel/cpu/sh4a/setup-sh7722.c
@@ -18,8 +18,77 @@
 #include <asm/clock.h>
 #include <asm/mmzone.h>
 #include <asm/dma-sh.h>
+#include <asm/siu.h>
 #include <cpu/sh7722.h>
 
+static struct sh_dmae_slave_config sh7722_dmae_slaves[] = {
+	{
+		.slave_id	= SHDMA_SLAVE_SCIF0_TX,
+		.addr		= 0xffe0000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x21,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF0_RX,
+		.addr		= 0xffe00014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x22,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF1_TX,
+		.addr		= 0xffe1000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x25,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF1_RX,
+		.addr		= 0xffe10014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x26,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF2_TX,
+		.addr		= 0xffe2000c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x29,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SCIF2_RX,
+		.addr		= 0xffe20014,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_8BIT),
+		.mid_rid	= 0x2a,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUA_TX,
+		.addr		= 0xa454c098,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb1,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUA_RX,
+		.addr		= 0xa454c090,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb2,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUB_TX,
+		.addr		= 0xa454c09c,
+		.chcr		= DM_FIX | SM_INC | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb5,
+	}, {
+		.slave_id	= SHDMA_SLAVE_SIUB_RX,
+		.addr		= 0xa454c094,
+		.chcr		= DM_INC | SM_FIX | 0x800 | TS_INDEX2VAL(XMIT_SZ_32BIT),
+		.mid_rid	= 0xb6,
+	},
+};
+
+static struct sh_dmae_pdata dma_platform_data = {
+	.mode		= 0,
+	.config		= sh7722_dmae_slaves,
+	.config_num	= ARRAY_SIZE(sh7722_dmae_slaves),
+};
+
+struct platform_device dma_device = {
+	.name		= "sh-dma-engine",
+	.id		= -1,
+	.dev		= {
+		.platform_data	= &dma_platform_data,
+	},
+};
+
 /* Serial */
 static struct plat_sci_port scif0_platform_data = {
 	.mapbase        = 0xffe00000,
@@ -388,15 +457,36 @@ static struct platform_device tmu2_device = {
 	},
 };
 
-static struct sh_dmae_pdata dma_platform_data = {
-	.mode = 0,
+static struct siu_platform siu_platform_data = {
+	.dma_dev	= &dma_device.dev,
+	.dma_slave_tx_a	= SHDMA_SLAVE_SIUA_TX,
+	.dma_slave_rx_a	= SHDMA_SLAVE_SIUA_RX,
+	.dma_slave_tx_b	= SHDMA_SLAVE_SIUB_TX,
+	.dma_slave_rx_b	= SHDMA_SLAVE_SIUB_RX,
 };
 
-static struct platform_device dma_device = {
-	.name		= "sh-dma-engine",
+static struct resource siu_resources[] = {
+	[0] = {
+		.start	= 0xa4540000,
+		.end	= 0xa454c10f,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= 108,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device siu_device = {
+	.name		= "sh_siu",
 	.id		= -1,
-	.dev		= {
-		.platform_data	= &dma_platform_data,
+	.dev = {
+		.platform_data	= &siu_platform_data,
+	},
+	.resource	= siu_resources,
+	.num_resources	= ARRAY_SIZE(siu_resources),
+	.archdata = {
+		.hwblk_id = HWBLK_SIU,
 	},
 };
 
@@ -414,6 +504,7 @@ static struct platform_device *sh7722_devices[] __initdata = {
 	&vpu_device,
 	&veu_device,
 	&jpu_device,
+	&siu_device,
 	&dma_device,
 };
 
-- 
1.6.2.4

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 4/4] sh: audio support for the sh7722 Migo-R board
  2010-01-19  8:08 ` [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support Guennadi Liakhovetski
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Configure SIU port B pins and register the WM8978 audio codec.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/boards/mach-migor/setup.c       |   16 ++++++++++++++++
 arch/sh/include/mach-migor/mach/migor.h |    1 +
 2 files changed, 17 insertions(+), 0 deletions(-)

diff --git a/arch/sh/boards/mach-migor/setup.c b/arch/sh/boards/mach-migor/setup.c
index e1972a8..8f2bd4e 100644
--- a/arch/sh/boards/mach-migor/setup.c
+++ b/arch/sh/boards/mach-migor/setup.c
@@ -419,6 +419,9 @@ static struct i2c_board_info migor_i2c_devices[] = {
 		I2C_BOARD_INFO("migor_ts", 0x51),
 		.irq = 38, /* IRQ6 */
 	},
+	{
+		I2C_BOARD_INFO("wm8978", 0x1a),
+	},
 };
 
 static struct i2c_board_info migor_i2c_camera[] = {
@@ -631,6 +634,19 @@ static int __init migor_devices_setup(void)
 
 	platform_resource_setup_memory(&migor_ceu_device, "ceu", 4 << 20);
 
+	/* SIU: Port B */
+	gpio_request(GPIO_FN_SIUBOLR, NULL);
+	gpio_request(GPIO_FN_SIUBOBT, NULL);
+	gpio_request(GPIO_FN_SIUBISLD, NULL);
+	gpio_request(GPIO_FN_SIUBOSLD, NULL);
+	gpio_request(GPIO_FN_SIUMCKB, NULL);
+
+	/*
+	 * The original driver sets SIUB OLR/OBT, ILR/IBT, and SIUA OLR/OBT to
+	 * output. Need only SIUB, set to output for master mode (table 34.2)
+	 */
+	ctrl_outw(ctrl_inw(PORT_MSELCRA) | 1, PORT_MSELCRA);
+
 	i2c_register_board_info(0, migor_i2c_devices,
 				ARRAY_SIZE(migor_i2c_devices));
 
diff --git a/arch/sh/include/mach-migor/mach/migor.h b/arch/sh/include/mach-migor/mach/migor.h
index cee6cb8..42fccf9 100644
--- a/arch/sh/include/mach-migor/mach/migor.h
+++ b/arch/sh/include/mach-migor/mach/migor.h
@@ -1,6 +1,7 @@
 #ifndef __ASM_SH_MIGOR_H
 #define __ASM_SH_MIGOR_H
 
+#define PORT_MSELCRA 0xa4050180
 #define PORT_MSELCRB 0xa4050182
 #define BSC_CS4BCR 0xfec10010
 #define BSC_CS6ABCR 0xfec1001c
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 4/4] sh: audio support for the sh7722 Migo-R board
@ 2010-01-19  8:09   ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-19  8:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Configure SIU port B pins and register the WM8978 audio codec.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
 arch/sh/boards/mach-migor/setup.c       |   16 ++++++++++++++++
 arch/sh/include/mach-migor/mach/migor.h |    1 +
 2 files changed, 17 insertions(+), 0 deletions(-)

diff --git a/arch/sh/boards/mach-migor/setup.c b/arch/sh/boards/mach-migor/setup.c
index e1972a8..8f2bd4e 100644
--- a/arch/sh/boards/mach-migor/setup.c
+++ b/arch/sh/boards/mach-migor/setup.c
@@ -419,6 +419,9 @@ static struct i2c_board_info migor_i2c_devices[] = {
 		I2C_BOARD_INFO("migor_ts", 0x51),
 		.irq = 38, /* IRQ6 */
 	},
+	{
+		I2C_BOARD_INFO("wm8978", 0x1a),
+	},
 };
 
 static struct i2c_board_info migor_i2c_camera[] = {
@@ -631,6 +634,19 @@ static int __init migor_devices_setup(void)
 
 	platform_resource_setup_memory(&migor_ceu_device, "ceu", 4 << 20);
 
+	/* SIU: Port B */
+	gpio_request(GPIO_FN_SIUBOLR, NULL);
+	gpio_request(GPIO_FN_SIUBOBT, NULL);
+	gpio_request(GPIO_FN_SIUBISLD, NULL);
+	gpio_request(GPIO_FN_SIUBOSLD, NULL);
+	gpio_request(GPIO_FN_SIUMCKB, NULL);
+
+	/*
+	 * The original driver sets SIUB OLR/OBT, ILR/IBT, and SIUA OLR/OBT to
+	 * output. Need only SIUB, set to output for master mode (table 34.2)
+	 */
+	ctrl_outw(ctrl_inw(PORT_MSELCRA) | 1, PORT_MSELCRA);
+
 	i2c_register_board_info(0, migor_i2c_devices,
 				ARRAY_SIZE(migor_i2c_devices));
 
diff --git a/arch/sh/include/mach-migor/mach/migor.h b/arch/sh/include/mach-migor/mach/migor.h
index cee6cb8..42fccf9 100644
--- a/arch/sh/include/mach-migor/mach/migor.h
+++ b/arch/sh/include/mach-migor/mach/migor.h
@@ -1,6 +1,7 @@
 #ifndef __ASM_SH_MIGOR_H
 #define __ASM_SH_MIGOR_H
 
+#define PORT_MSELCRA 0xa4050180
 #define PORT_MSELCRB 0xa4050182
 #define BSC_CS4BCR 0xfec10010
 #define BSC_CS6ABCR 0xfec1001c
-- 
1.6.2.4


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-19  8:08   ` Guennadi Liakhovetski
@ 2010-01-19 10:46     ` Liam Girdwood
  -1 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-19 10:46 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Mark Brown, linux-sh

Looks ok, some questions below.

On Tue, 2010-01-19 at 09:08 +0100, Guennadi Liakhovetski wrote:
> The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> is stereo and also has some differences in pin configuration and internal
> signal routing. This driver is based on wm8974 and takes the differences into
> account.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> I know there is a driver for this codec in wm git-tree and I did have a 
> look at it. But, although both drivers have identical roots and look 
> similar in many places, the other one implements much less functionality, 
> doesn't seem to have been very intensively tested, and would require a 
> substantial amount of work to bring it into shape. Whereas this driver has 
> been tested, implements a few audio controls, and uses current ALSA / ASoC 
> APIs. The only part, that's missing from this version, that is present in 
> the wm driver is support for SPI, which can be added as required.
> 
>  sound/soc/codecs/Kconfig  |    4 +
>  sound/soc/codecs/Makefile |    2 +
>  sound/soc/codecs/wm8978.c |  919 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/wm8978.h |   84 ++++
>  4 files changed, 1009 insertions(+), 0 deletions(-)
>  create mode 100644 sound/soc/codecs/wm8978.c
>  create mode 100644 sound/soc/codecs/wm8978.h
> 
> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
> index 62ff26a..0aad72f 100644
> --- a/sound/soc/codecs/Kconfig
> +++ b/sound/soc/codecs/Kconfig
> @@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
>  	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
> @@ -230,6 +231,9 @@ config SND_SOC_WM8971
>  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 ea98354..fbd290e 100644
> --- a/sound/soc/codecs/Makefile
> +++ b/sound/soc/codecs/Makefile
> @@ -44,6 +44,7 @@ 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
> @@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
> new file mode 100644
> index 0000000..0f91d16
> --- /dev/null
> +++ b/sound/soc/codecs/wm8978.c
> @@ -0,0 +1,919 @@
> +/*
> + * 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, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
> +	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
> +	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
> +	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, 0x0110, 0x0110, 0x0100,	/* 0x2c...0x2f */ /* 0x2d and 0x2e contain the VU bit */
> +	0x0100, 0x0002, 0x0001, 0x0001,	/* 0x30...0x33 */
> +	0x0139, 0x0139, 0x0139, 0x0139,	/* 0x34...0x37 */ /* all contain the VU bit */
> +	0x0001,	0x0001,			/* 0x38...0x3b */
> +};
> +
> +/* codec private data */
> +struct wm8978_priv {
> +	struct snd_soc_codec codec;
> +	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" };
> +
> +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> +						ARRAY_SIZE(xtexts), xtexts)
> +
> +static const struct soc_enum wm8978_enum[] = {
> +	/* adc */
> +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
> +	/* dac */
> +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 3, wm8978_companding),
> +	ARRAY_SINGLE(WM8978_EQ1, 8, wm8978_eqmode),
> +
> +	ARRAY_SINGLE(WM8978_EQ1, 5, wm8978_eq1),
> +	ARRAY_SINGLE(WM8978_EQ2, 8, wm8978_bw),
> +	ARRAY_SINGLE(WM8978_EQ2, 5, wm8978_eq2),
> +	ARRAY_SINGLE(WM8978_EQ3, 8, wm8978_bw),
> +
> +	ARRAY_SINGLE(WM8978_EQ3, 5, wm8978_eq3),
> +	ARRAY_SINGLE(WM8978_EQ4, 8, wm8978_bw),
> +	ARRAY_SINGLE(WM8978_EQ4, 5, wm8978_eq4),
> +	ARRAY_SINGLE(WM8978_EQ5, 8, wm8978_bw),
> +
> +	ARRAY_SINGLE(WM8978_EQ5, 5, wm8978_eq5),
> +	ARRAY_SINGLE(WM8978_ALC_CONTROL_3, 8, wm8978_alc3),
> +	ARRAY_SINGLE(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 struct snd_kcontrol_new wm8978_snd_controls[] = {
> +
> +SOC_SINGLE("Digital Loopback Switch", WM8978_COMPANDING_CONTROL, 0, 1, 0),
> +
> +SOC_ENUM("ADC Companding", wm8978_enum[0]),
> +SOC_ENUM("DAC Companding", wm8978_enum[1]),
> +
> +SOC_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
> +
> +SOC_SINGLE_TLV("Left PCM Volume",
> +	       WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
> +SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
> +
> +SOC_SINGLE_TLV("Left Capture Volume",
> +	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +SOC_SINGLE_TLV("Right Capture Volume",
> +	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +
> +SOC_ENUM("Equaliser Function", wm8978_enum[2]),
> +SOC_ENUM("EQ1 Cut Off", wm8978_enum[3]),
> +SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ2 Bandwith", wm8978_enum[4]),
> +SOC_ENUM("EQ2 Cut Off", wm8978_enum[5]),
> +SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ3 Bandwith", wm8978_enum[6]),
> +SOC_ENUM("EQ3 Cut Off", wm8978_enum[7]),
> +SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ4 Bandwith", wm8978_enum[8]),
> +SOC_ENUM("EQ4 Cut Off", wm8978_enum[9]),
> +SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ5 Bandwith", wm8978_enum[10]),
> +SOC_ENUM("EQ5 Cut Off", wm8978_enum[11]),
> +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", wm8978_enum[13]),
> +SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
> +SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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", wm8978_enum[12]),
> +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_SINGLE("Left Capture PGA ZC Switch",
> +	   WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
> +SOC_SINGLE_TLV("Left Capture PGA Volume",
> +	       WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +SOC_SINGLE("Right Capture PGA ZC Switch",
> +	   WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
> +SOC_SINGLE_TLV("Right Capture PGA Volume",
> +	       WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +
> +/* OUT1 - HeadPhones */
> +SOC_SINGLE("Left HeadPhone Playback ZC Switch",
> +	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),

HeadPhone is usually "Headphone"

> +SOC_SINGLE("Left HeadPhone Playback Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Left HeadPhone Playback Volume",
> +	       WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Right HeadPhone Playback ZC Switch",
> +	   WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Right HeadPhone Playback Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Right HeadPhone Playback Volume",
> +	       WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
> +
> +/* OUT2 - Speakers */
> +SOC_SINGLE("Left Speaker Playback ZC Switch",
> +	   WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Left Speaker Playback Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Left Speaker Playback Volume",
> +	       WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Right Speaker Playback ZC Switch",
> +	   WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Right Speaker Playback Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Right Speaker Playback Volume",
> +	       WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Left Capture Boost(+20dB)",
> +	   WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
> +SOC_SINGLE("Right Capture Boost(+20dB)",
> +	   WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
> +
> +/* OUT3/4 - Line Output */
> +SOC_SINGLE("Left Line Playback Switch", WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
> +SOC_SINGLE("Right Line Playback Switch", WM8978_OUT4_MIXER_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, 1),
> +};
> +
> +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, 1),
> +};
> +
> +/* OUT3/OUT4 Mixer not implemented */
> +
> +/* Mixer #2: Input PGA Mute */
> +static const struct snd_kcontrol_new wm8978_left_inpga[] = {
> +SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
> +SOC_DAPM_SINGLE("Left MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
> +SOC_DAPM_SINGLE("Left MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
> +};
> +static const struct snd_kcontrol_new wm8978_right_inpga[] = {
> +SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
> +SOC_DAPM_SINGLE("Right MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
> +SOC_DAPM_SINGLE("Right MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
> +};
> +
> +/* Mixer #3: Boost (Input) mixer */
> +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
> +};
> +static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
> +};
> +
> +/* AUX Input boost vol */
> +static const struct snd_kcontrol_new wm8978_aux_boost_controls[] = {
> +SOC_DAPM_SINGLE("Left Aux Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0),
> +SOC_DAPM_SINGLE("Right Aux Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0),
> +};
> +
> +/* Mic Input boost vol */
> +static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
> +SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
> +SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
> +};
> +
> +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> +							m, ARRAY_SIZE(m))

I'd be tempted to rename this and add to soc-dapm.h

> +
> +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),
> +
> +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_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),
> +
> +/* Mixer #1: OUT1,2 */
> +MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_left_out_mixer),
> +MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_right_out_mixer),
> +
> +MIXER_ARRAY("Left Input PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_left_inpga),
> +MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
> +		   wm8978_right_inpga),
> +
> +MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
> +		   wm8978_left_boost_mixer),
> +MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
> +		   wm8978_right_boost_mixer),
> +
> +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 Input PGA"},
> +	{"Right Boost Mixer", NULL, "R2"},
> +
> +	{"Left ADC", NULL, "Left Boost Mixer"},
> +
> +	{"Left Boost Mixer", NULL, "LAUX"},
> +	{"Left Boost Mixer", NULL, "Left Input PGA"},
> +	{"Left Boost Mixer", NULL, "L2"},
> +
> +	/* Input PGA */
> +	{"Right Input PGA", "R2 Switch", "R2"},
> +	{"Right Input PGA", "Right MicN Switch", "RMICN"},
> +	{"Right Input PGA", "Right MicP Switch", "RMICP"},
> +
> +	{"Left Input PGA", "L2 Switch", "L2"},
> +	{"Left Input PGA", "Left MicN Switch", "LMICN"},
> +	{"Left Input PGA", "Left 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 Kpart;
> +	unsigned int K, Ndiv, Nmod;
> +
> +	Ndiv = target / source;
> +	if (Ndiv < 6) {
> +		source >>= 1;
> +		pll_div->div2 = 1;
> +		Ndiv = target / source;
> +	} else {
> +		pll_div->div2 = 0;
> +	}
> +

I would personally not put the extra { here

> +	if (Ndiv < 6 || Ndiv > 12)
> +		dev_warn(wm8978_codec->dev,
> +			 "WM8978 N value exceeds recommended range! N = %u\n",
> +			 Ndiv);
> +
> +	pll_div->n = Ndiv;
> +	Nmod = target - source * Ndiv;
> +	Kpart = FIXED_PLL_SIZE * (long long)Nmod + source / 2;
> +
> +	do_div(Kpart, source);
> +
> +	K = Kpart & 0xFFFFFFFF;
> +
> +	pll_div->k = K;
> +}
> +
> +static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
> +		int source, unsigned int freq_in, unsigned int freq_out)
> +{
> +	struct snd_soc_codec *codec = codec_dai->codec;
> +	struct wm8978_pll_div pll_div;
> +	u16 reg;
> +
> +	if (freq_in = 0 || freq_out = 0) {
> +		/* Clock CODEC directly from MCLK */
> +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> +
> +		/* Turn off PLL */
> +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> +		return 0;
> +	}
> +
> +	pll_factors(&pll_div, freq_out, freq_in);
> +
> +	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);
> +
> +	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);
> +	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
> +	/* Output PLL to GPIO1 */
> +	snd_soc_write(codec, WM8978_GPIO_CONTROL,
> +		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
> +
> +	/* Run CODEC from PLL instead of MCLK */
> +	reg = snd_soc_read(codec, WM8978_CLOCKING);
> +	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
> +
> +	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;
> +	u16 reg;
> +
> +	switch (div_id) {
> +	case WM8978_OPCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x1cf;
> +		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
> +		break;
> +	case WM8978_MCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x11f;
> +		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
> +		break;
> +	case WM8978_ADCCLK:
> +		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & 0x1f7;
> +		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
> +		break;
> +	case WM8978_DACCLK:
> +		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & 0x1f7;
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
> +		break;
> +	case WM8978_BCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x1e3;
> +		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(codec->dev, "%s: ID %d, value %x\n",
> +		__func__, div_id, reg | div);
> +
> +	return 0;
> +}
> +
> +/*
> + * 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;
> +	u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
> +	u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
> +
> +	/* 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;
> +}
> +
> +/*
> + * 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;
> +	u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
> +	u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
> +
> +	dev_dbg(codec->dev, "%s: fmt %d, rate %u\n", __func__,
> +		params_format(params), params_rate(params));
> +
> +	/* 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;
> +	}
> +
> +	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
> +	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
> +
> +	/* Mic bias */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> +
> +	/* Out-1 enabled, left/right input channel enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> +
> +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);

Power stuff should be in either dapm or bias functions.

> +
> +	return 0;
> +}
> +
> +static int wm8978_mute(struct snd_soc_dai *dai, int mute)
> +{
> +	struct snd_soc_codec *codec = dai->codec;
> +	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
> +
> +	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
> +
> +	if (mute)
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
> +	else
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
> +
> +	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:
> +		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:
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\n", __func__, level, power1);
> +
> +	codec->bias_level = level;
> +	return 0;
> +}
> +
> +/* Also supports 12kHz */
> +#define WM8978_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
> +	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \
> +	SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
> +
> +#define 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_pll	= wm8978_set_dai_pll,
> +};
> +
> +struct snd_soc_dai wm8978_dai = {
> +	.name = "WM8978 HiFi",
> +	.id = 1,
> +	.playback = {
> +		.stream_name = "Playback",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = WM8978_RATES,
> +		.formats = WM8978_FORMATS,
> +	},
> +	.capture = {
> +		.stream_name = "Capture",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = WM8978_RATES,
> +		.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;
> +
> +	/* we only need to suspend if we are a valid card */
> +	if (!codec->card)
> +		return 0;
> +
> +	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +	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;
> +	int i;
> +	u16 data;
> +	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(wm8978_reg); i++) {
> +		if (i = WM8978_RESET)
> +			continue;
> +		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
> +		codec->hw_write(codec->control_data, (char *)&data, 2);
> +	}
> +
> +	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
> +	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);
> +
> +static __devinit int wm8978_register(struct wm8978_priv *wm8978)
> +{
> +	int ret;
> +	struct snd_soc_codec *codec = &wm8978->codec;
> +
> +	if (wm8978_codec) {
> +		dev_err(codec->dev, "Another WM8978 is registered\n");
> +		return -EINVAL;
> +	}
> +
> +	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));
> +
> +	/* 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..61e39c0
> --- /dev/null
> +++ b/sound/soc/codecs/wm8978.h
> @@ -0,0 +1,84 @@
> +/*
> + * 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
> +

It would be nice to have the relevant bits defined here for set_fmt()
etc instead of just the magic numbers used in the above codec driver. 

> +/* Clock divider Id's */
> +enum wm8978_clk_id {
> +	WM8978_OPCLKDIV,
> +	WM8978_MCLKDIV,
> +	WM8978_ADCCLK,
> +	WM8978_DACCLK,
> +	WM8978_BCLKDIV,
> +};
> +
> +extern struct snd_soc_dai wm8978_dai;
> +extern struct snd_soc_codec_device soc_codec_dev_wm8978;
> +
> +#endif	/* __WM8978_H__ */



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-19 10:46     ` Liam Girdwood
  0 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-19 10:46 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Mark Brown, linux-sh

Looks ok, some questions below.

On Tue, 2010-01-19 at 09:08 +0100, Guennadi Liakhovetski wrote:
> The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> is stereo and also has some differences in pin configuration and internal
> signal routing. This driver is based on wm8974 and takes the differences into
> account.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> I know there is a driver for this codec in wm git-tree and I did have a 
> look at it. But, although both drivers have identical roots and look 
> similar in many places, the other one implements much less functionality, 
> doesn't seem to have been very intensively tested, and would require a 
> substantial amount of work to bring it into shape. Whereas this driver has 
> been tested, implements a few audio controls, and uses current ALSA / ASoC 
> APIs. The only part, that's missing from this version, that is present in 
> the wm driver is support for SPI, which can be added as required.
> 
>  sound/soc/codecs/Kconfig  |    4 +
>  sound/soc/codecs/Makefile |    2 +
>  sound/soc/codecs/wm8978.c |  919 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/wm8978.h |   84 ++++
>  4 files changed, 1009 insertions(+), 0 deletions(-)
>  create mode 100644 sound/soc/codecs/wm8978.c
>  create mode 100644 sound/soc/codecs/wm8978.h
> 
> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
> index 62ff26a..0aad72f 100644
> --- a/sound/soc/codecs/Kconfig
> +++ b/sound/soc/codecs/Kconfig
> @@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
>  	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
> @@ -230,6 +231,9 @@ config SND_SOC_WM8971
>  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 ea98354..fbd290e 100644
> --- a/sound/soc/codecs/Makefile
> +++ b/sound/soc/codecs/Makefile
> @@ -44,6 +44,7 @@ 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
> @@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
> new file mode 100644
> index 0000000..0f91d16
> --- /dev/null
> +++ b/sound/soc/codecs/wm8978.c
> @@ -0,0 +1,919 @@
> +/*
> + * 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, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
> +	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
> +	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
> +	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, 0x0110, 0x0110, 0x0100,	/* 0x2c...0x2f */ /* 0x2d and 0x2e contain the VU bit */
> +	0x0100, 0x0002, 0x0001, 0x0001,	/* 0x30...0x33 */
> +	0x0139, 0x0139, 0x0139, 0x0139,	/* 0x34...0x37 */ /* all contain the VU bit */
> +	0x0001,	0x0001,			/* 0x38...0x3b */
> +};
> +
> +/* codec private data */
> +struct wm8978_priv {
> +	struct snd_soc_codec codec;
> +	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" };
> +
> +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> +						ARRAY_SIZE(xtexts), xtexts)
> +
> +static const struct soc_enum wm8978_enum[] = {
> +	/* adc */
> +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
> +	/* dac */
> +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 3, wm8978_companding),
> +	ARRAY_SINGLE(WM8978_EQ1, 8, wm8978_eqmode),
> +
> +	ARRAY_SINGLE(WM8978_EQ1, 5, wm8978_eq1),
> +	ARRAY_SINGLE(WM8978_EQ2, 8, wm8978_bw),
> +	ARRAY_SINGLE(WM8978_EQ2, 5, wm8978_eq2),
> +	ARRAY_SINGLE(WM8978_EQ3, 8, wm8978_bw),
> +
> +	ARRAY_SINGLE(WM8978_EQ3, 5, wm8978_eq3),
> +	ARRAY_SINGLE(WM8978_EQ4, 8, wm8978_bw),
> +	ARRAY_SINGLE(WM8978_EQ4, 5, wm8978_eq4),
> +	ARRAY_SINGLE(WM8978_EQ5, 8, wm8978_bw),
> +
> +	ARRAY_SINGLE(WM8978_EQ5, 5, wm8978_eq5),
> +	ARRAY_SINGLE(WM8978_ALC_CONTROL_3, 8, wm8978_alc3),
> +	ARRAY_SINGLE(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 struct snd_kcontrol_new wm8978_snd_controls[] = {
> +
> +SOC_SINGLE("Digital Loopback Switch", WM8978_COMPANDING_CONTROL, 0, 1, 0),
> +
> +SOC_ENUM("ADC Companding", wm8978_enum[0]),
> +SOC_ENUM("DAC Companding", wm8978_enum[1]),
> +
> +SOC_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
> +
> +SOC_SINGLE_TLV("Left PCM Volume",
> +	       WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
> +SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
> +
> +SOC_SINGLE_TLV("Left Capture Volume",
> +	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +SOC_SINGLE_TLV("Right Capture Volume",
> +	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +
> +SOC_ENUM("Equaliser Function", wm8978_enum[2]),
> +SOC_ENUM("EQ1 Cut Off", wm8978_enum[3]),
> +SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ2 Bandwith", wm8978_enum[4]),
> +SOC_ENUM("EQ2 Cut Off", wm8978_enum[5]),
> +SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ3 Bandwith", wm8978_enum[6]),
> +SOC_ENUM("EQ3 Cut Off", wm8978_enum[7]),
> +SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ4 Bandwith", wm8978_enum[8]),
> +SOC_ENUM("EQ4 Cut Off", wm8978_enum[9]),
> +SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4,  0, 24, 1, eq_tlv),
> +
> +SOC_ENUM("Equaliser EQ5 Bandwith", wm8978_enum[10]),
> +SOC_ENUM("EQ5 Cut Off", wm8978_enum[11]),
> +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", wm8978_enum[13]),
> +SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
> +SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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", wm8978_enum[12]),
> +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_SINGLE("Left Capture PGA ZC Switch",
> +	   WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
> +SOC_SINGLE_TLV("Left Capture PGA Volume",
> +	       WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +SOC_SINGLE("Right Capture PGA ZC Switch",
> +	   WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
> +SOC_SINGLE_TLV("Right Capture PGA Volume",
> +	       WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +
> +/* OUT1 - HeadPhones */
> +SOC_SINGLE("Left HeadPhone Playback ZC Switch",
> +	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),

HeadPhone is usually "Headphone"

> +SOC_SINGLE("Left HeadPhone Playback Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Left HeadPhone Playback Volume",
> +	       WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Right HeadPhone Playback ZC Switch",
> +	   WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Right HeadPhone Playback Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Right HeadPhone Playback Volume",
> +	       WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
> +
> +/* OUT2 - Speakers */
> +SOC_SINGLE("Left Speaker Playback ZC Switch",
> +	   WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Left Speaker Playback Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Left Speaker Playback Volume",
> +	       WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Right Speaker Playback ZC Switch",
> +	   WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
> +SOC_SINGLE("Right Speaker Playback Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
> +SOC_SINGLE_TLV("Right Speaker Playback Volume",
> +	       WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
> +
> +SOC_SINGLE("Left Capture Boost(+20dB)",
> +	   WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
> +SOC_SINGLE("Right Capture Boost(+20dB)",
> +	   WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
> +
> +/* OUT3/4 - Line Output */
> +SOC_SINGLE("Left Line Playback Switch", WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
> +SOC_SINGLE("Right Line Playback Switch", WM8978_OUT4_MIXER_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, 1),
> +};
> +
> +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, 1),
> +};
> +
> +/* OUT3/OUT4 Mixer not implemented */
> +
> +/* Mixer #2: Input PGA Mute */
> +static const struct snd_kcontrol_new wm8978_left_inpga[] = {
> +SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
> +SOC_DAPM_SINGLE("Left MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
> +SOC_DAPM_SINGLE("Left MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
> +};
> +static const struct snd_kcontrol_new wm8978_right_inpga[] = {
> +SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
> +SOC_DAPM_SINGLE("Right MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
> +SOC_DAPM_SINGLE("Right MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
> +};
> +
> +/* Mixer #3: Boost (Input) mixer */
> +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
> +};
> +static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
> +};
> +
> +/* AUX Input boost vol */
> +static const struct snd_kcontrol_new wm8978_aux_boost_controls[] = {
> +SOC_DAPM_SINGLE("Left Aux Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0),
> +SOC_DAPM_SINGLE("Right Aux Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0),
> +};
> +
> +/* Mic Input boost vol */
> +static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
> +SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
> +SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
> +};
> +
> +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> +							m, ARRAY_SIZE(m))

I'd be tempted to rename this and add to soc-dapm.h

> +
> +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),
> +
> +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_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),
> +
> +/* Mixer #1: OUT1,2 */
> +MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_left_out_mixer),
> +MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_right_out_mixer),
> +
> +MIXER_ARRAY("Left Input PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
> +		   wm8978_left_inpga),
> +MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
> +		   wm8978_right_inpga),
> +
> +MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
> +		   wm8978_left_boost_mixer),
> +MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
> +		   wm8978_right_boost_mixer),
> +
> +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 Input PGA"},
> +	{"Right Boost Mixer", NULL, "R2"},
> +
> +	{"Left ADC", NULL, "Left Boost Mixer"},
> +
> +	{"Left Boost Mixer", NULL, "LAUX"},
> +	{"Left Boost Mixer", NULL, "Left Input PGA"},
> +	{"Left Boost Mixer", NULL, "L2"},
> +
> +	/* Input PGA */
> +	{"Right Input PGA", "R2 Switch", "R2"},
> +	{"Right Input PGA", "Right MicN Switch", "RMICN"},
> +	{"Right Input PGA", "Right MicP Switch", "RMICP"},
> +
> +	{"Left Input PGA", "L2 Switch", "L2"},
> +	{"Left Input PGA", "Left MicN Switch", "LMICN"},
> +	{"Left Input PGA", "Left 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 Kpart;
> +	unsigned int K, Ndiv, Nmod;
> +
> +	Ndiv = target / source;
> +	if (Ndiv < 6) {
> +		source >>= 1;
> +		pll_div->div2 = 1;
> +		Ndiv = target / source;
> +	} else {
> +		pll_div->div2 = 0;
> +	}
> +

I would personally not put the extra { here

> +	if (Ndiv < 6 || Ndiv > 12)
> +		dev_warn(wm8978_codec->dev,
> +			 "WM8978 N value exceeds recommended range! N = %u\n",
> +			 Ndiv);
> +
> +	pll_div->n = Ndiv;
> +	Nmod = target - source * Ndiv;
> +	Kpart = FIXED_PLL_SIZE * (long long)Nmod + source / 2;
> +
> +	do_div(Kpart, source);
> +
> +	K = Kpart & 0xFFFFFFFF;
> +
> +	pll_div->k = K;
> +}
> +
> +static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
> +		int source, unsigned int freq_in, unsigned int freq_out)
> +{
> +	struct snd_soc_codec *codec = codec_dai->codec;
> +	struct wm8978_pll_div pll_div;
> +	u16 reg;
> +
> +	if (freq_in == 0 || freq_out == 0) {
> +		/* Clock CODEC directly from MCLK */
> +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> +
> +		/* Turn off PLL */
> +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> +		return 0;
> +	}
> +
> +	pll_factors(&pll_div, freq_out, freq_in);
> +
> +	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);
> +
> +	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);
> +	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
> +	/* Output PLL to GPIO1 */
> +	snd_soc_write(codec, WM8978_GPIO_CONTROL,
> +		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
> +
> +	/* Run CODEC from PLL instead of MCLK */
> +	reg = snd_soc_read(codec, WM8978_CLOCKING);
> +	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
> +
> +	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;
> +	u16 reg;
> +
> +	switch (div_id) {
> +	case WM8978_OPCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x1cf;
> +		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
> +		break;
> +	case WM8978_MCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x11f;
> +		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
> +		break;
> +	case WM8978_ADCCLK:
> +		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & 0x1f7;
> +		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
> +		break;
> +	case WM8978_DACCLK:
> +		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & 0x1f7;
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
> +		break;
> +	case WM8978_BCLKDIV:
> +		reg = snd_soc_read(codec, WM8978_CLOCKING) & 0x1e3;
> +		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(codec->dev, "%s: ID %d, value %x\n",
> +		__func__, div_id, reg | div);
> +
> +	return 0;
> +}
> +
> +/*
> + * 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;
> +	u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
> +	u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
> +
> +	/* 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;
> +}
> +
> +/*
> + * 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;
> +	u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
> +	u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
> +
> +	dev_dbg(codec->dev, "%s: fmt %d, rate %u\n", __func__,
> +		params_format(params), params_rate(params));
> +
> +	/* 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;
> +	}
> +
> +	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
> +	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
> +
> +	/* Mic bias */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> +
> +	/* Out-1 enabled, left/right input channel enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> +
> +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);

Power stuff should be in either dapm or bias functions.

> +
> +	return 0;
> +}
> +
> +static int wm8978_mute(struct snd_soc_dai *dai, int mute)
> +{
> +	struct snd_soc_codec *codec = dai->codec;
> +	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
> +
> +	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
> +
> +	if (mute)
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
> +	else
> +		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
> +
> +	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:
> +		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:
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\n", __func__, level, power1);
> +
> +	codec->bias_level = level;
> +	return 0;
> +}
> +
> +/* Also supports 12kHz */
> +#define WM8978_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
> +	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \
> +	SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
> +
> +#define 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_pll	= wm8978_set_dai_pll,
> +};
> +
> +struct snd_soc_dai wm8978_dai = {
> +	.name = "WM8978 HiFi",
> +	.id = 1,
> +	.playback = {
> +		.stream_name = "Playback",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = WM8978_RATES,
> +		.formats = WM8978_FORMATS,
> +	},
> +	.capture = {
> +		.stream_name = "Capture",
> +		.channels_min = 1,
> +		.channels_max = 2,
> +		.rates = WM8978_RATES,
> +		.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;
> +
> +	/* we only need to suspend if we are a valid card */
> +	if (!codec->card)
> +		return 0;
> +
> +	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +	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;
> +	int i;
> +	u16 data;
> +	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(wm8978_reg); i++) {
> +		if (i == WM8978_RESET)
> +			continue;
> +		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
> +		codec->hw_write(codec->control_data, (char *)&data, 2);
> +	}
> +
> +	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
> +	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);
> +
> +static __devinit int wm8978_register(struct wm8978_priv *wm8978)
> +{
> +	int ret;
> +	struct snd_soc_codec *codec = &wm8978->codec;
> +
> +	if (wm8978_codec) {
> +		dev_err(codec->dev, "Another WM8978 is registered\n");
> +		return -EINVAL;
> +	}
> +
> +	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));
> +
> +	/* 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..61e39c0
> --- /dev/null
> +++ b/sound/soc/codecs/wm8978.h
> @@ -0,0 +1,84 @@
> +/*
> + * 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
> +

It would be nice to have the relevant bits defined here for set_fmt()
etc instead of just the magic numbers used in the above codec driver. 

> +/* Clock divider Id's */
> +enum wm8978_clk_id {
> +	WM8978_OPCLKDIV,
> +	WM8978_MCLKDIV,
> +	WM8978_ADCCLK,
> +	WM8978_DACCLK,
> +	WM8978_BCLKDIV,
> +};
> +
> +extern struct snd_soc_dai wm8978_dai;
> +extern struct snd_soc_codec_device soc_codec_dev_wm8978;
> +
> +#endif	/* __WM8978_H__ */



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-19  8:08   ` Guennadi Liakhovetski
  (?)
  (?)
@ 2010-01-19 10:57   ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-19 10:57 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, Liam Girdwood, Magnus Damm, linux-sh

On Tue, Jan 19, 2010 at 09:08:57AM +0100, Guennadi Liakhovetski wrote:

This all looks pretty good - there's a few issues below but they're
largely stylistic rather than anything fundamental.

> +	0x0000, 0x0000, 0x0000, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
> +	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
> +	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */

What do these comments refer to - do you mean to say that these are not
the actual chip register defaults?  The normal way to deal with those is
to have a write (or other update of the cache defaults).  This avoids
potential confusion later on if the chip updates the register defaults
or when reviewing against the datasheet.

> +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> +						ARRAY_SIZE(xtexts), xtexts)

This looks generic - please namespace it and add it to the header file,
other drivers can benefit from it.

> +static const struct soc_enum wm8978_enum[] = {
> +	/* adc */
> +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),

Please define individual variables for this - indexing into the array
gets hard to follow, it's far too easy to have an off by one errors
trying to match things up.

> +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> +                                                       m, ARRAY_SIZE(m))


> +/* Mixer #3: Boost (Input) mixer */
> +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
> +};
> +static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
> +SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
> +};

These should have "Switch" at the end of the name.  Also, the names are
going be concatenated with the mixer names so you'll end up with things
"Left Boost Mixer Left PGA Switch" - the individual controls should
probably be just "Switch" so you end up with "Left Boost Mixer Switch".

> +/* Mic Input boost vol */
> +static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
> +SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
> +SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
> +};

This doesn't seem to be referenced anywhere?

> +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> +							m, ARRAY_SIZE(m))

Again, this should be done somewhere generic.  Probably by going through
and changing the SND_SOC_DAPM_MIXER definition.

> +	/* Output PLL to GPIO1 */
> +	snd_soc_write(codec, WM8978_GPIO_CONTROL,
> +		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);

This should be being done unconditionally.  Put a switch in via the
set_dai_clkdiv() call.

> +	/* Mic bias */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> +
> +	/* Out-1 enabled, left/right input channel enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> +
> +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);

These should all be being managed via DAPM.

> +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;

This bitmask maintains everything except the two LSB...

> +	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:
> +		power1 |= 0xC;

...but this is also managing other bits.

> +		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);
> +		}

> +/* Also supports 12kHz */
> +#define WM8978_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_8000_48000

> +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;
> +
> +	/* we only need to suspend if we are a valid card */
> +	if (!codec->card)
> +		return 0;

Don't need to check for the card any more, the core will stop you
getting called without a card.

> +	/* Sync reg_cache with the hardware */
> +	for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
> +		if (i == WM8978_RESET)
> +			continue;

You can also skip the write if the register has the default value (this
will speed up resume slightly).

> +		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
> +		codec->hw_write(codec->control_data, (char *)&data, 2);
> +	}

Just use snd_soc_write()?

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2/4] ASoC: add DAI and platform drivers
  2010-01-19  8:09   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Guennadi Liakhovetski
@ 2010-01-19 11:13     ` Liam Girdwood
  -1 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-19 11:13 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Mark Brown, linux-sh

On Tue, 2010-01-19 at 09:09 +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
> Sound Interface Unit (SIU). This patch adds drivers for this interface and
> support for the sh7722 Migo-R board.
> 

Had a quick look wrt the ALSA parts. Comments below.

> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> As mentioned in the introduction mail, this driver requires firmware to be 
> loaded from user-space using the standard hotplug functionality.
> 
>  arch/sh/include/asm/siu.h |   26 ++
>  sound/soc/sh/Kconfig      |   16 +
>  sound/soc/sh/Makefile     |    4 +
>  sound/soc/sh/migor.c      |  261 ++++++++++++++
>  sound/soc/sh/siu.h        |  217 ++++++++++++
>  sound/soc/sh/siu_dai.c    |  833 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/sh/siu_pcm.c    |  716 ++++++++++++++++++++++++++++++++++++++
>  7 files changed, 2073 insertions(+), 0 deletions(-)
>  create mode 100644 arch/sh/include/asm/siu.h
>  create mode 100644 sound/soc/sh/migor.c
>  create mode 100644 sound/soc/sh/siu.h
>  create mode 100644 sound/soc/sh/siu_dai.c
>  create mode 100644 sound/soc/sh/siu_pcm.c
> 
> 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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
> index 8072a6d..eec6fe5 100644
> --- a/sound/soc/sh/Kconfig
> +++ b/sound/soc/sh/Kconfig
> @@ -26,6 +26,14 @@ config SND_SOC_SH4_FSI
>  	help
>  	  This option enables FSI sound support
>  
> +config SND_SOC_SH4_SIU
> +	tristate "SH4 SIU support"
> +	depends on CPU_SUBTYPE_SH7722
> +	select DMADEVICES
> +	select SH_DMAE
> +	help
> +	  This option enables SIU sound support
> +
>  ##
>  ## Boards
>  ##
> @@ -55,4 +63,12 @@ config SND_FSI_DA7210
>  	  This option enables generic sound support for the
>  	  FSI - DA7210 unit
>  
> +config SND_SIU_MIGOR
> +	tristate "SIU sound support on Migo-R"
> +	depends on SND_SOC_SH4_SIU && SH_MIGOR
> +	select SND_SOC_WM8978
> +	help
> +	  This option enables generic sound support for the
> +	  SH7722 Migo-R board
> +
>  endmenu
> diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
> index 1d0ec0a..8a5a192 100644
> --- a/sound/soc/sh/Makefile
> +++ b/sound/soc/sh/Makefile
> @@ -6,15 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
>  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/migor.c b/sound/soc/sh/migor.c
> new file mode 100644
> index 0000000..507e59e
> --- /dev/null
> +++ b/sound/soc/sh/migor.c
> @@ -0,0 +1,261 @@
> +/*
> + * 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 = 49152350 / 12;
> +
> +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};
> +
> +/* 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;
> +	unsigned int mclk_div, opclk_div, f2;
> +	int ret, mclk_idx;
> +	unsigned int rate = params_rate(params);
> +
> +	switch (rate) {
> +	case 48000:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 98304000, was 98304050 */

What does the "was" value represent here ?

> +		break;
> +	case 44100:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 90316800, was 90317500 */
> +		break;
> +	case 32000:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 131072000, was 131072500 */
> +		break;
> +	case 24000:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 98304000, was 98304700 */
> +		break;
> +	case 22050:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 90316800, was 90317500 */
> +		break;
> +	case 16000:
> +		mclk_div = 0xa0;
> +		opclk_div = 0x020;
> +		/* f2 = 98304000, was 98304700 */
> +		break;
> +	case 11025:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 45158400, was 45158752 */
> +		break;
> +	default:
> +	case 8000:
> +		mclk_div = 0xa0;
> +		opclk_div = 0x020;
> +		/* f2 = 49152000, was 49152350 */
> +		break;
> +	}
> +
> +	mclk_idx = mclk_div >> 5;
> +	/*
> +	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
> +	 * in WM8978 datasheet
> +	 */
> +	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
> +		mclk_denominator[mclk_idx];
> +
> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
> +				     mclk_div & 0xe0);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
> +	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_pll(codec_dai, 0, 0, 13000000, f2);
> +	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;
> +
> +	/* See Figure 40 */
> +	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;
> +	/*
> +	 * 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);
> +
> +	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, CLKB_EXT, codec_freq / 2,
> +			       SND_SOC_CLOCK_IN);
> +
> +	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;
> +
> +	/* disable the PLL */
> +	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);
> +}
> +
> +static int migor_startup(struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_soc_device *socdev = rtd->socdev;
> +	struct snd_soc_codec *codec = socdev->card->codec;
> +	int ret;
> +
> +	/* Activate DAC output routes */
> +	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Left err %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = snd_soc_dapm_enable_pin(codec, "Right Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Right err %d\n", ret);
> +		return ret;
> +	}
> +
> +	snd_soc_dapm_sync(codec);
> +
> +	return 0;
> +}
> +
> +static struct snd_soc_ops migor_dai_ops = {
> +	.hw_params = migor_hw_params,
> +	.hw_free = migor_hw_free,
> +	.startup = migor_startup,
> +};
> +
> +/* 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,
> +};
> +
> +/* 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..e7cba83
> --- /dev/null
> +++ b/sound/soc/sh/siu.h
> @@ -0,0 +1,217 @@
> +/*
> + * 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_PORTA		0		/* port A */
> +#define SIU_PORTB		1		/* port B */
> +#define MAX_SIU_PORTS		2
> +
> +/* SIU clock configuration */
> +enum {CLKA_PLL, CLKA_EXT, CLKB_PLL, CLKB_EXT};
> +
> +/* Board specifics */
> +#if defined(CONFIG_CPU_SUBTYPE_SH7722)
> +# define MAX_VOLUME		0x1000
> +#else
> +# define MAX_VOLUME		0x7fff
> +#endif
> +
> +struct siu_info {
> +	int			port_id;
> +	u32 __iomem		*pram;
> +	u32 __iomem		*xram;
> +	u32 __iomem		*yram;
> +	u32 __iomem		*reg;
> +	struct siu_firmware	fw;
> +};
> +
> +#define PRAM_SIZE	0x2000
> +#define XRAM_SIZE	0x800
> +#define YRAM_SIZE	0x800
> +
> +#define XRAM_OFFSET	0x4000
> +#define YRAM_OFFSET	0x6000
> +#define REG_OFFSET	0xc000
> +
> +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;
> +	void				*mono_buf;	/* Mono buffer */
> +	size_t				mono_size;	/* and its size in bytes */
> +	snd_pcm_sframes_t		xfer_cnt;	/* Number of frames */
> +	u8				rw_flg;		/* transfer status */
> +	/* DMA status */
> +	dma_addr_t			mono_dma;
> +	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[MAX_SIU_PORTS];
> +
> +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];
> +}
> +
> +#define PLAYBACK_ENABLED	1
> +#define CAPTURE_ENABLED		2
> +
> +#define VOLUME_CAPTURE		0
> +#define VOLUME_PLAYBACK		1
> +#define DFLT_VOLUME_LEVEL	0x08000800
> +
> +#define PERIOD_BYTES_MAX	8192		/* DMA transfer/period size */
> +#define PERIOD_BYTES_MIN	256		/* DMA transfer/period size */
> +#define PERIODS_MAX		64		/* Max periods in buffer */
> +#define PERIODS_MIN		4		/* Min periods in buffer */
> +#define BUFFER_BYTES_MAX	(PERIOD_BYTES_MAX * PERIODS_MAX)
> +#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 */
> +
> +/* 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 IFCTL		(0x000 / sizeof(u32))
> +#define SRCTL		(0x004 / sizeof(u32))
> +#define SFORM		(0x008 / sizeof(u32))
> +#define CKCTL		(0x00c / sizeof(u32))
> +#define TRDAT		(0x010 / sizeof(u32))
> +#define STFIFO		(0x014 / sizeof(u32))
> +#define DPAK		(0x01c / sizeof(u32))
> +#define CKREV		(0x020 / sizeof(u32))
> +#define EVNTC		(0x028 / sizeof(u32))
> +#define SBCTL		(0x040 / sizeof(u32))
> +#define SBPSET		(0x044 / sizeof(u32))
> +#define SBFSTS		(0x068 / sizeof(u32))
> +#define SBDVCA		(0x06c / sizeof(u32))
> +#define SBDVCB		(0x070 / sizeof(u32))
> +#define SBACTIV		(0x074 / sizeof(u32))
> +#define DMAIA		(0x090 / sizeof(u32))
> +#define DMAIB		(0x094 / sizeof(u32))
> +#define DMAOA		(0x098 / sizeof(u32))
> +#define DMAOB		(0x09c / sizeof(u32))
> +#define DMAML		(0x0a0 / sizeof(u32))
> +#define SPSTS		(0x0cc / sizeof(u32))
> +#define SPCTL		(0x0d0 / sizeof(u32))
> +#define BRGASEL		(0x100 / sizeof(u32))
> +#define BRRA		(0x104 / sizeof(u32))
> +#define BRGBSEL		(0x108 / sizeof(u32))
> +#define 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..e5dbedb
> --- /dev/null
> +++ b/sound/soc/sh/siu_dai.c
> @@ -0,0 +1,833 @@
> +/*
> + * 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"
> +
> +/*
> + * 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[MAX_SIU_PORTS] = {
> +	[SIU_PORTA] = {
> +		.playback = {
> +			.i2s	= 0x50000000,
> +			.pcm	= 0x40000000,
> +			.spdif	= 0x80000000,	/* not on all SIU versions */
> +			.mask	= 0xd0000000,
> +		},
> +		.capture = {
> +			.i2s	= 0x05000000,
> +			.pcm	= 0x04000000,
> +			.spdif	= 0x08000000,
> +			.mask	= 0x0d000000,
> +		},
> +	},
> +	[SIU_PORTB] = {
> +		.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 + 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 + SRCTL, 0x301);
> +
> +	/* portA%6fs, portB%6fs */
> +	siu_write32(base + CKCTL, 0x40400000);
> +
> +	/* portA's BRG does not divide SIUCKA */
> +	siu_write32(base + BRGASEL, 0);
> +	siu_write32(base + BRRA, 0);
> +
> +	/* portB's BRG divides SIUCKB by half */
> +	siu_write32(base + BRGBSEL, 1);
> +	siu_write32(base + BRRB, 0);
> +
> +	siu_write32(base + IFCTL, 0x44440000);
> +
> +	/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
> +	siu_write32(base + SFORM, 0x0c0c0000);
> +
> +	/*
> +	 * Volume levels: looks like the DSP firmware implements volume controls
> +	 * differently from what's described in the datasheet
> +	 */
> +	siu_write32(base + SBDVCA, port_info->playback.volume);
> +	siu_write32(base + 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 + 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;
> +	struct snd_pcm_substream *substream = siu_stream->substream;
> +	struct snd_pcm_runtime *rt = substream->runtime;
> +	u32 srctl, ifctl;
> +
> +	srctl = siu_read32(base + SRCTL);
> +	ifctl = siu_read32(base + IFCTL);
> +
> +	switch (info->port_id) {
> +	case SIU_PORTA:
> +		/* portA operates */
> +		srctl |= 0x200;
> +		ifctl &= ~0xc2;
> +		/* Mono mode is not used, instead, stereo is simulated */
> +		if (rt->channels = 1)
> +			ifctl |= 0x80;
> +		break;
> +	case SIU_PORTB:
> +		/* portB operates */
> +		srctl |= 0x100;
> +		ifctl &= ~0x31;
> +		/* Mono mode is not used, instead, stereo is simulated */
> +		if (rt->channels = 1)
> +			ifctl |= 0x20;
> +		break;
> +	}
> +
> +	siu_write32(base + SRCTL, srctl);
> +	/* Unmute and configure portA */
> +	siu_write32(base + 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 + DPAK);
> +
> +	switch (info->port_id) {
> +	case SIU_PORTA:
> +		dpak &= ~0xc0000000;
> +		break;
> +	case SIU_PORTB:
> +		dpak &= ~0x00c00000;
> +		break;
> +	}
> +
> +	siu_write32(base + 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 + TRDAT, port_info->trdat);
> +	port_info->trdat = 0x0;
> +
> +
> +	/* SPB start condition: software */
> +	siu_write32(base + SBACTIV, 0);
> +	/* Start SPB */
> +	siu_write32(base + SBCTL, 0xc0000000);
> +	/* Wait for program to halt */
> +	cnt = 0x10000;
> +	while (--cnt && siu_read32(base + SBCTL) != 0x80000000)
> +		cpu_relax();
> +
> +	if (!cnt)
> +		return -EBUSY;
> +
> +	/* SPB program start address setting */
> +	siu_write32(base + SBPSET, 0x00400000);
> +	/* SPB hardware start(FIFOCTL source) */
> +	siu_write32(base + 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 + SBACTIV, 0);
> +	/* SPB stop */
> +	siu_write32(base + 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		= 1,

Shouldn't this be 2 as it's stated in siu_dai_open() that mono is not
used.

> +	.channels_max		= 2,
> +	.buffer_bytes_max	= BUFFER_BYTES_MAX,
> +	.period_bytes_min	= PERIOD_BYTES_MIN,
> +	.period_bytes_max	= PERIOD_BYTES_MAX,
> +	.periods_min		= PERIODS_MIN,
> +	.periods_max		= 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 = 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] > MAX_VOLUME ||
> +	    ucontrol->value.integer.value[1] < 0 ||
> +	    ucontrol->value.integer.value[1] > 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 + 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 + 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 + IFCTL);
> +	siu_write32(base + 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 CLKA_PLL:
> +		siu_name = "siua_clk";
> +		parent_name = "pll_clk";
> +		break;
> +	case CLKA_EXT:
> +		siu_name = "siua_clk";
> +		parent_name = "siumcka_clk";
> +		break;
> +	case CLKB_PLL:
> +		siu_name = "siub_clk";
> +		parent_name = "pll_clk";
> +		break;
> +	case 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 = 1,

Shouldn't this also be 2 due to mono not used statement in
siu_dai_open()

> +		.channels_max = 2,
> +		.formats = SNDRV_PCM_FMTBIT_S16,
> +		.rates = SNDRV_PCM_RATE_8000_48000,
> +	},
> +	.capture = {
> +		.channels_min = 1,
> +		.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..afe2e6e
> --- /dev/null
> +++ b/sound/soc/sh/siu_pcm.c
> @@ -0,0 +1,716 @@
> +/*
> + * 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"
> +
> +struct siu_port *siu_ports[MAX_SIU_PORTS];
> +
> +static void copy_playback_period(struct siu_stream *siu_stream)
> +{
> +	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
> +	u16 *src;
> +	u32 *dst;
> +	int cp_cnt;
> +	int i;
> +
> +	src = (u16 *)PERIOD_OFFSET(rt->dma_area,
> +				   siu_stream->cur_period,
> +				   siu_stream->period_bytes);
> +	dst = siu_stream->mono_buf;
> +	cp_cnt = siu_stream->xfer_cnt;
> +
> +	for (i = 0; i < cp_cnt; i++)
> +		*dst++ = *src++;
> +}
> +
> +static void copy_capture_period(struct siu_stream *siu_stream)
> +{
> +	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
> +	u16 *src;
> +	u16 *dst;
> +	int cp_cnt;
> +	int i;
> +
> +	dst = (u16 *)PERIOD_OFFSET(rt->dma_area,
> +				   siu_stream->cur_period,
> +				   siu_stream->period_bytes);
> +	src = (u16 *)siu_stream->mono_buf;
> +	cp_cnt = siu_stream->xfer_cnt;
> +
> +	for (i = 0; i < cp_cnt; i++) {
> +		*dst++ = *src;
> +		src += 2;
> +	}
> +}
> +
> +/* 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 + STFIFO);
> +	siu_write32(base + 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;
> +	struct snd_pcm_substream *substream = siu_stream->substream;
> +
> +	if (!siu_stream->rw_flg)
> +		return;
> +
> +	if (substream->runtime->channels = 1 &&
> +	    substream->stream = SNDRV_PCM_STREAM_CAPTURE)
> +		copy_capture_period(siu_stream);
> +
> +	/* 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 + STFIFO);
> +	siu_write32(base + 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 + STFIFO);
> +	siu_write32(base + STFIFO, siu_read32(base + 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;
> +
> +		if (rt->channels = 1) {
> +			buff = siu_stream->mono_dma;
> +			virt = siu_stream->mono_buf;
> +			count = siu_stream->mono_size;
> +		} else {
> +			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 {
> +		/* For mono streams we need to use the mono buffer */
> +		if (rt->channels = 1) {
> +			copy_playback_period(siu_stream);
> +			siu_pcm_wr_set(port_info,
> +				siu_stream->mono_dma, siu_stream->mono_size);
> +		} 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 + STFIFO);
> +	siu_write32(base + 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 void siu_pcm_mono_free(struct device *dev, struct siu_stream *stream)
> +{
> +	dma_free_coherent(dev, stream->mono_size,
> +			  stream->mono_buf, stream->mono_dma);
> +	stream->mono_buf = NULL;
> +	stream->mono_size = 0;
> +}
> +
> +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, mono %p\n", __func__,
> +		info->port_id, siu_stream->mono_buf);
> +
> +	if (siu_stream->mono_buf && ss->runtime->channels = 1)
> +		siu_pcm_mono_free(ss->pcm->card->dev, siu_stream);
> +
> +	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_mono_alloc(struct device *dev, struct siu_stream *siu_stream)
> +{
> +	/*
> +	 * The hardware only supports stereo (2 channels) streams. We must
> +	 * convert mono streams (1 channel) to stereo streams. To do that we
> +	 * just copy the mono data to one of the stereo channels and instruct
> +	 * the siu to play the data on both channels. However, the idle
> +	 * channel must also be present in the buffer, so we use an extra
> +	 * buffer twice as big as one mono period. Also since this function
> +	 * can be called multiple times, we must adjust the buffer size.
> +	 */

Shouldn't this be done by userspace. i.e. alsa plugin or pulseaudio ?

> +	if (siu_stream->mono_buf && siu_stream->mono_size !> +	    siu_stream->period_bytes * 2) {
> +		dma_free_coherent(dev, siu_stream->mono_size,
> +				  siu_stream->mono_buf, siu_stream->mono_dma);
> +		siu_stream->mono_buf = NULL;
> +		siu_stream->mono_size = 0;
> +	}
> +
> +	if (!siu_stream->mono_buf) {
> +		siu_stream->mono_buf = dma_alloc_coherent(dev,
> +						siu_stream->period_bytes * 2,
> +						&siu_stream->mono_dma,
> +						GFP_KERNEL);
> +		if (!siu_stream->mono_buf)
> +			return -ENOMEM;
> +
> +		siu_stream->mono_size = siu_stream->period_bytes * 2;
> +	}
> +
> +	dev_dbg(dev, "%s: mono buffer @ %p\n", __func__, siu_stream->mono_buf);
> +
> +	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;
> +
> +	if (rt->channels = 1) {
> +		int ret = siu_pcm_mono_alloc(ss->pcm->card->dev,
> +					     siu_stream);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	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 + EVNTC),
> +		siu_read32(base + 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 >= MAX_SIU_PORTS)
> +		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,
> +					BUFFER_BYTES_MAX, BUFFER_BYTES_MAX);
> +		if (ret < 0) {
> +			dev_err(card->dev,
> +			       "snd_pcm_lib_preallocate_pages_for_all() err=%d",
> +				ret);
> +			goto fail;
> +		}
> +
> +		/* 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);



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board
@ 2010-01-19 11:13     ` Liam Girdwood
  0 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-19 11:13 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Mark Brown, linux-sh

On Tue, 2010-01-19 at 09:09 +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
> Sound Interface Unit (SIU). This patch adds drivers for this interface and
> support for the sh7722 Migo-R board.
> 

Had a quick look wrt the ALSA parts. Comments below.

> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> As mentioned in the introduction mail, this driver requires firmware to be 
> loaded from user-space using the standard hotplug functionality.
> 
>  arch/sh/include/asm/siu.h |   26 ++
>  sound/soc/sh/Kconfig      |   16 +
>  sound/soc/sh/Makefile     |    4 +
>  sound/soc/sh/migor.c      |  261 ++++++++++++++
>  sound/soc/sh/siu.h        |  217 ++++++++++++
>  sound/soc/sh/siu_dai.c    |  833 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/sh/siu_pcm.c    |  716 ++++++++++++++++++++++++++++++++++++++
>  7 files changed, 2073 insertions(+), 0 deletions(-)
>  create mode 100644 arch/sh/include/asm/siu.h
>  create mode 100644 sound/soc/sh/migor.c
>  create mode 100644 sound/soc/sh/siu.h
>  create mode 100644 sound/soc/sh/siu_dai.c
>  create mode 100644 sound/soc/sh/siu_pcm.c
> 
> 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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
> index 8072a6d..eec6fe5 100644
> --- a/sound/soc/sh/Kconfig
> +++ b/sound/soc/sh/Kconfig
> @@ -26,6 +26,14 @@ config SND_SOC_SH4_FSI
>  	help
>  	  This option enables FSI sound support
>  
> +config SND_SOC_SH4_SIU
> +	tristate "SH4 SIU support"
> +	depends on CPU_SUBTYPE_SH7722
> +	select DMADEVICES
> +	select SH_DMAE
> +	help
> +	  This option enables SIU sound support
> +
>  ##
>  ## Boards
>  ##
> @@ -55,4 +63,12 @@ config SND_FSI_DA7210
>  	  This option enables generic sound support for the
>  	  FSI - DA7210 unit
>  
> +config SND_SIU_MIGOR
> +	tristate "SIU sound support on Migo-R"
> +	depends on SND_SOC_SH4_SIU && SH_MIGOR
> +	select SND_SOC_WM8978
> +	help
> +	  This option enables generic sound support for the
> +	  SH7722 Migo-R board
> +
>  endmenu
> diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
> index 1d0ec0a..8a5a192 100644
> --- a/sound/soc/sh/Makefile
> +++ b/sound/soc/sh/Makefile
> @@ -6,15 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
>  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/migor.c b/sound/soc/sh/migor.c
> new file mode 100644
> index 0000000..507e59e
> --- /dev/null
> +++ b/sound/soc/sh/migor.c
> @@ -0,0 +1,261 @@
> +/*
> + * 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 = 49152350 / 12;
> +
> +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};
> +
> +/* 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;
> +	unsigned int mclk_div, opclk_div, f2;
> +	int ret, mclk_idx;
> +	unsigned int rate = params_rate(params);
> +
> +	switch (rate) {
> +	case 48000:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 98304000, was 98304050 */

What does the "was" value represent here ?

> +		break;
> +	case 44100:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 90316800, was 90317500 */
> +		break;
> +	case 32000:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 131072000, was 131072500 */
> +		break;
> +	case 24000:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 98304000, was 98304700 */
> +		break;
> +	case 22050:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 90316800, was 90317500 */
> +		break;
> +	case 16000:
> +		mclk_div = 0xa0;
> +		opclk_div = 0x020;
> +		/* f2 = 98304000, was 98304700 */
> +		break;
> +	case 11025:
> +		mclk_div = 0x80;
> +		opclk_div = 0x010;
> +		/* f2 = 45158400, was 45158752 */
> +		break;
> +	default:
> +	case 8000:
> +		mclk_div = 0xa0;
> +		opclk_div = 0x020;
> +		/* f2 = 49152000, was 49152350 */
> +		break;
> +	}
> +
> +	mclk_idx = mclk_div >> 5;
> +	/*
> +	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
> +	 * in WM8978 datasheet
> +	 */
> +	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
> +		mclk_denominator[mclk_idx];
> +
> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
> +				     mclk_div & 0xe0);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
> +	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_pll(codec_dai, 0, 0, 13000000, f2);
> +	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;
> +
> +	/* See Figure 40 */
> +	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;
> +	/*
> +	 * 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);
> +
> +	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, CLKB_EXT, codec_freq / 2,
> +			       SND_SOC_CLOCK_IN);
> +
> +	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;
> +
> +	/* disable the PLL */
> +	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);
> +}
> +
> +static int migor_startup(struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_soc_device *socdev = rtd->socdev;
> +	struct snd_soc_codec *codec = socdev->card->codec;
> +	int ret;
> +
> +	/* Activate DAC output routes */
> +	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Left err %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = snd_soc_dapm_enable_pin(codec, "Right Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Right err %d\n", ret);
> +		return ret;
> +	}
> +
> +	snd_soc_dapm_sync(codec);
> +
> +	return 0;
> +}
> +
> +static struct snd_soc_ops migor_dai_ops = {
> +	.hw_params = migor_hw_params,
> +	.hw_free = migor_hw_free,
> +	.startup = migor_startup,
> +};
> +
> +/* 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,
> +};
> +
> +/* 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..e7cba83
> --- /dev/null
> +++ b/sound/soc/sh/siu.h
> @@ -0,0 +1,217 @@
> +/*
> + * 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_PORTA		0		/* port A */
> +#define SIU_PORTB		1		/* port B */
> +#define MAX_SIU_PORTS		2
> +
> +/* SIU clock configuration */
> +enum {CLKA_PLL, CLKA_EXT, CLKB_PLL, CLKB_EXT};
> +
> +/* Board specifics */
> +#if defined(CONFIG_CPU_SUBTYPE_SH7722)
> +# define MAX_VOLUME		0x1000
> +#else
> +# define MAX_VOLUME		0x7fff
> +#endif
> +
> +struct siu_info {
> +	int			port_id;
> +	u32 __iomem		*pram;
> +	u32 __iomem		*xram;
> +	u32 __iomem		*yram;
> +	u32 __iomem		*reg;
> +	struct siu_firmware	fw;
> +};
> +
> +#define PRAM_SIZE	0x2000
> +#define XRAM_SIZE	0x800
> +#define YRAM_SIZE	0x800
> +
> +#define XRAM_OFFSET	0x4000
> +#define YRAM_OFFSET	0x6000
> +#define REG_OFFSET	0xc000
> +
> +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;
> +	void				*mono_buf;	/* Mono buffer */
> +	size_t				mono_size;	/* and its size in bytes */
> +	snd_pcm_sframes_t		xfer_cnt;	/* Number of frames */
> +	u8				rw_flg;		/* transfer status */
> +	/* DMA status */
> +	dma_addr_t			mono_dma;
> +	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[MAX_SIU_PORTS];
> +
> +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];
> +}
> +
> +#define PLAYBACK_ENABLED	1
> +#define CAPTURE_ENABLED		2
> +
> +#define VOLUME_CAPTURE		0
> +#define VOLUME_PLAYBACK		1
> +#define DFLT_VOLUME_LEVEL	0x08000800
> +
> +#define PERIOD_BYTES_MAX	8192		/* DMA transfer/period size */
> +#define PERIOD_BYTES_MIN	256		/* DMA transfer/period size */
> +#define PERIODS_MAX		64		/* Max periods in buffer */
> +#define PERIODS_MIN		4		/* Min periods in buffer */
> +#define BUFFER_BYTES_MAX	(PERIOD_BYTES_MAX * PERIODS_MAX)
> +#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 */
> +
> +/* 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 IFCTL		(0x000 / sizeof(u32))
> +#define SRCTL		(0x004 / sizeof(u32))
> +#define SFORM		(0x008 / sizeof(u32))
> +#define CKCTL		(0x00c / sizeof(u32))
> +#define TRDAT		(0x010 / sizeof(u32))
> +#define STFIFO		(0x014 / sizeof(u32))
> +#define DPAK		(0x01c / sizeof(u32))
> +#define CKREV		(0x020 / sizeof(u32))
> +#define EVNTC		(0x028 / sizeof(u32))
> +#define SBCTL		(0x040 / sizeof(u32))
> +#define SBPSET		(0x044 / sizeof(u32))
> +#define SBFSTS		(0x068 / sizeof(u32))
> +#define SBDVCA		(0x06c / sizeof(u32))
> +#define SBDVCB		(0x070 / sizeof(u32))
> +#define SBACTIV		(0x074 / sizeof(u32))
> +#define DMAIA		(0x090 / sizeof(u32))
> +#define DMAIB		(0x094 / sizeof(u32))
> +#define DMAOA		(0x098 / sizeof(u32))
> +#define DMAOB		(0x09c / sizeof(u32))
> +#define DMAML		(0x0a0 / sizeof(u32))
> +#define SPSTS		(0x0cc / sizeof(u32))
> +#define SPCTL		(0x0d0 / sizeof(u32))
> +#define BRGASEL		(0x100 / sizeof(u32))
> +#define BRRA		(0x104 / sizeof(u32))
> +#define BRGBSEL		(0x108 / sizeof(u32))
> +#define 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..e5dbedb
> --- /dev/null
> +++ b/sound/soc/sh/siu_dai.c
> @@ -0,0 +1,833 @@
> +/*
> + * 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"
> +
> +/*
> + * 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[MAX_SIU_PORTS] = {
> +	[SIU_PORTA] = {
> +		.playback = {
> +			.i2s	= 0x50000000,
> +			.pcm	= 0x40000000,
> +			.spdif	= 0x80000000,	/* not on all SIU versions */
> +			.mask	= 0xd0000000,
> +		},
> +		.capture = {
> +			.i2s	= 0x05000000,
> +			.pcm	= 0x04000000,
> +			.spdif	= 0x08000000,
> +			.mask	= 0x0d000000,
> +		},
> +	},
> +	[SIU_PORTB] = {
> +		.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 + 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 + SRCTL, 0x301);
> +
> +	/* portA=256fs, portB=256fs */
> +	siu_write32(base + CKCTL, 0x40400000);
> +
> +	/* portA's BRG does not divide SIUCKA */
> +	siu_write32(base + BRGASEL, 0);
> +	siu_write32(base + BRRA, 0);
> +
> +	/* portB's BRG divides SIUCKB by half */
> +	siu_write32(base + BRGBSEL, 1);
> +	siu_write32(base + BRRB, 0);
> +
> +	siu_write32(base + IFCTL, 0x44440000);
> +
> +	/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
> +	siu_write32(base + SFORM, 0x0c0c0000);
> +
> +	/*
> +	 * Volume levels: looks like the DSP firmware implements volume controls
> +	 * differently from what's described in the datasheet
> +	 */
> +	siu_write32(base + SBDVCA, port_info->playback.volume);
> +	siu_write32(base + 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 + 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;
> +	struct snd_pcm_substream *substream = siu_stream->substream;
> +	struct snd_pcm_runtime *rt = substream->runtime;
> +	u32 srctl, ifctl;
> +
> +	srctl = siu_read32(base + SRCTL);
> +	ifctl = siu_read32(base + IFCTL);
> +
> +	switch (info->port_id) {
> +	case SIU_PORTA:
> +		/* portA operates */
> +		srctl |= 0x200;
> +		ifctl &= ~0xc2;
> +		/* Mono mode is not used, instead, stereo is simulated */
> +		if (rt->channels == 1)
> +			ifctl |= 0x80;
> +		break;
> +	case SIU_PORTB:
> +		/* portB operates */
> +		srctl |= 0x100;
> +		ifctl &= ~0x31;
> +		/* Mono mode is not used, instead, stereo is simulated */
> +		if (rt->channels == 1)
> +			ifctl |= 0x20;
> +		break;
> +	}
> +
> +	siu_write32(base + SRCTL, srctl);
> +	/* Unmute and configure portA */
> +	siu_write32(base + 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 + DPAK);
> +
> +	switch (info->port_id) {
> +	case SIU_PORTA:
> +		dpak &= ~0xc0000000;
> +		break;
> +	case SIU_PORTB:
> +		dpak &= ~0x00c00000;
> +		break;
> +	}
> +
> +	siu_write32(base + 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 + TRDAT, port_info->trdat);
> +	port_info->trdat = 0x0;
> +
> +
> +	/* SPB start condition: software */
> +	siu_write32(base + SBACTIV, 0);
> +	/* Start SPB */
> +	siu_write32(base + SBCTL, 0xc0000000);
> +	/* Wait for program to halt */
> +	cnt = 0x10000;
> +	while (--cnt && siu_read32(base + SBCTL) != 0x80000000)
> +		cpu_relax();
> +
> +	if (!cnt)
> +		return -EBUSY;
> +
> +	/* SPB program start address setting */
> +	siu_write32(base + SBPSET, 0x00400000);
> +	/* SPB hardware start(FIFOCTL source) */
> +	siu_write32(base + 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 + SBACTIV, 0);
> +	/* SPB stop */
> +	siu_write32(base + 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		= 1,

Shouldn't this be 2 as it's stated in siu_dai_open() that mono is not
used.

> +	.channels_max		= 2,
> +	.buffer_bytes_max	= BUFFER_BYTES_MAX,
> +	.period_bytes_min	= PERIOD_BYTES_MIN,
> +	.period_bytes_max	= PERIOD_BYTES_MAX,
> +	.periods_min		= PERIODS_MIN,
> +	.periods_max		= 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 = 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] > MAX_VOLUME ||
> +	    ucontrol->value.integer.value[1] < 0 ||
> +	    ucontrol->value.integer.value[1] > 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 + 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 + 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 + IFCTL);
> +	siu_write32(base + 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 CLKA_PLL:
> +		siu_name = "siua_clk";
> +		parent_name = "pll_clk";
> +		break;
> +	case CLKA_EXT:
> +		siu_name = "siua_clk";
> +		parent_name = "siumcka_clk";
> +		break;
> +	case CLKB_PLL:
> +		siu_name = "siub_clk";
> +		parent_name = "pll_clk";
> +		break;
> +	case 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 = 1,

Shouldn't this also be 2 due to mono not used statement in
siu_dai_open()

> +		.channels_max = 2,
> +		.formats = SNDRV_PCM_FMTBIT_S16,
> +		.rates = SNDRV_PCM_RATE_8000_48000,
> +	},
> +	.capture = {
> +		.channels_min = 1,
> +		.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..afe2e6e
> --- /dev/null
> +++ b/sound/soc/sh/siu_pcm.c
> @@ -0,0 +1,716 @@
> +/*
> + * 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"
> +
> +struct siu_port *siu_ports[MAX_SIU_PORTS];
> +
> +static void copy_playback_period(struct siu_stream *siu_stream)
> +{
> +	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
> +	u16 *src;
> +	u32 *dst;
> +	int cp_cnt;
> +	int i;
> +
> +	src = (u16 *)PERIOD_OFFSET(rt->dma_area,
> +				   siu_stream->cur_period,
> +				   siu_stream->period_bytes);
> +	dst = siu_stream->mono_buf;
> +	cp_cnt = siu_stream->xfer_cnt;
> +
> +	for (i = 0; i < cp_cnt; i++)
> +		*dst++ = *src++;
> +}
> +
> +static void copy_capture_period(struct siu_stream *siu_stream)
> +{
> +	struct snd_pcm_runtime *rt = siu_stream->substream->runtime;
> +	u16 *src;
> +	u16 *dst;
> +	int cp_cnt;
> +	int i;
> +
> +	dst = (u16 *)PERIOD_OFFSET(rt->dma_area,
> +				   siu_stream->cur_period,
> +				   siu_stream->period_bytes);
> +	src = (u16 *)siu_stream->mono_buf;
> +	cp_cnt = siu_stream->xfer_cnt;
> +
> +	for (i = 0; i < cp_cnt; i++) {
> +		*dst++ = *src;
> +		src += 2;
> +	}
> +}
> +
> +/* 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 + STFIFO);
> +	siu_write32(base + 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;
> +	struct snd_pcm_substream *substream = siu_stream->substream;
> +
> +	if (!siu_stream->rw_flg)
> +		return;
> +
> +	if (substream->runtime->channels == 1 &&
> +	    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
> +		copy_capture_period(siu_stream);
> +
> +	/* 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 + STFIFO);
> +	siu_write32(base + 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 + STFIFO);
> +	siu_write32(base + STFIFO, siu_read32(base + 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;
> +
> +		if (rt->channels == 1) {
> +			buff = siu_stream->mono_dma;
> +			virt = siu_stream->mono_buf;
> +			count = siu_stream->mono_size;
> +		} else {
> +			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 {
> +		/* For mono streams we need to use the mono buffer */
> +		if (rt->channels == 1) {
> +			copy_playback_period(siu_stream);
> +			siu_pcm_wr_set(port_info,
> +				siu_stream->mono_dma, siu_stream->mono_size);
> +		} 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 + STFIFO);
> +	siu_write32(base + 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 void siu_pcm_mono_free(struct device *dev, struct siu_stream *stream)
> +{
> +	dma_free_coherent(dev, stream->mono_size,
> +			  stream->mono_buf, stream->mono_dma);
> +	stream->mono_buf = NULL;
> +	stream->mono_size = 0;
> +}
> +
> +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, mono %p\n", __func__,
> +		info->port_id, siu_stream->mono_buf);
> +
> +	if (siu_stream->mono_buf && ss->runtime->channels == 1)
> +		siu_pcm_mono_free(ss->pcm->card->dev, siu_stream);
> +
> +	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_mono_alloc(struct device *dev, struct siu_stream *siu_stream)
> +{
> +	/*
> +	 * The hardware only supports stereo (2 channels) streams. We must
> +	 * convert mono streams (1 channel) to stereo streams. To do that we
> +	 * just copy the mono data to one of the stereo channels and instruct
> +	 * the siu to play the data on both channels. However, the idle
> +	 * channel must also be present in the buffer, so we use an extra
> +	 * buffer twice as big as one mono period. Also since this function
> +	 * can be called multiple times, we must adjust the buffer size.
> +	 */

Shouldn't this be done by userspace. i.e. alsa plugin or pulseaudio ?

> +	if (siu_stream->mono_buf && siu_stream->mono_size !=
> +	    siu_stream->period_bytes * 2) {
> +		dma_free_coherent(dev, siu_stream->mono_size,
> +				  siu_stream->mono_buf, siu_stream->mono_dma);
> +		siu_stream->mono_buf = NULL;
> +		siu_stream->mono_size = 0;
> +	}
> +
> +	if (!siu_stream->mono_buf) {
> +		siu_stream->mono_buf = dma_alloc_coherent(dev,
> +						siu_stream->period_bytes * 2,
> +						&siu_stream->mono_dma,
> +						GFP_KERNEL);
> +		if (!siu_stream->mono_buf)
> +			return -ENOMEM;
> +
> +		siu_stream->mono_size = siu_stream->period_bytes * 2;
> +	}
> +
> +	dev_dbg(dev, "%s: mono buffer @ %p\n", __func__, siu_stream->mono_buf);
> +
> +	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;
> +
> +	if (rt->channels == 1) {
> +		int ret = siu_pcm_mono_alloc(ss->pcm->card->dev,
> +					     siu_stream);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	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 + EVNTC),
> +		siu_read32(base + 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 >= MAX_SIU_PORTS)
> +		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,
> +					BUFFER_BYTES_MAX, BUFFER_BYTES_MAX);
> +		if (ret < 0) {
> +			dev_err(card->dev,
> +			       "snd_pcm_lib_preallocate_pages_for_all() err=%d",
> +				ret);
> +			goto fail;
> +		}
> +
> +		/* 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);



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and
  2010-01-19  8:09   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Guennadi Liakhovetski
@ 2010-01-19 12:34     ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-19 12:34 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Liam Girdwood, Kuninori Morimoto, Magnus Damm

On Tue, Jan 19, 2010 at 09:09:01AM +0100, Guennadi Liakhovetski wrote:

> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
> Sound Interface Unit (SIU). This patch adds drivers for this interface and
> support for the sh7722 Migo-R board.

Normally you'd split the board support into a separate patch, and
splitting the DMA and DAI drivers wouldn't hurt either.  It makes the
review easier by keeping the patches smaller and more focused.

> +config SND_SIU_MIGOR
> +	tristate "SIU sound support on Migo-R"
> +	depends on SND_SOC_SH4_SIU && SH_MIGOR
> +	select SND_SOC_WM8978
> +	help
> +	  This option enables generic sound support for the
> +	  SH7722 Migo-R board
> +

I'd be tempted to just make SND_SOC_SH4_SIU a hidden variable and select
it from here.  I know that most of the CPU DAIs are exposed but it
doesn't actually seem to buy us anything.

> +/* Default 8000Hz sampling frequency */
> +static unsigned long codec_freq = 49152350 / 12;

Perhaps a #define for the input clock rate (I'm assuming that this is
what the 49152350 is)?

> +	switch (rate) {
> +	case 48000:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 98304000, was 98304050 */

Like Liam says either remove these comments or clarify them please :)

> +	/*
> +	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
> +	 * in WM8978 datasheet
> +	 */
> +	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
> +		mclk_denominator[mclk_idx];

Figure 40 of the current datasheet is the DSP mode B clock diagram -
it's now figure 41.  Better to include a datasheet revision.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
> +				     mclk_div & 0xe0);
> +	if (ret < 0)
> +		return ret;

This doesn't feel like something that the machine driver should have to
figure out, the CODEC driver should be able to figure out the MCLK
division from a set_sysclk() call telling it the PLL/MCLK frequency.
This would mean that the machine driver would need to at most specify
the PLL output frequency.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
> +	if (ret < 0)
> +		return ret;

I said add a set_clkdiv() option for the output clock GPIO configuration
- now that I think about it you can probably do this on any configuration
of OPCLKDIV by a machine driver.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_DACCLK, 8);
> +	if (ret < 0)
> +		return ret;

The DAC clock configuration can probably be factored into the CODEC
driver - you should know (or be able to be told) the CODEC system clock
so this should then only have an internal effect.

> +	/* See Figure 40 */
> +	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;

This feels like something that should be wrapped up in the CODEC
driver.  In any case, 

> +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;
> +
> +	/* disable the PLL */
> +	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);

This will happen for both playback and once for record, meaning that if
you've got both running then the first one to stop will halt the PLL
which probably isn't what you want.

> +static int migor_startup(struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_soc_device *socdev = rtd->socdev;
> +	struct snd_soc_codec *codec = socdev->card->codec;
> +	int ret;
> +
> +	/* Activate DAC output routes */
> +	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Left err %d\n", ret);
> +		return ret;
> +	}

Why are you doing this?  DAPM will automatically manage the power of the
DAC widget based on the playback state.

> +/* Board specifics */
> +#if defined(CONFIG_CPU_SUBTYPE_SH7722)
> +# define MAX_VOLUME		0x1000
> +#else
> +# define MAX_VOLUME		0x7fff
> +#endif

These defines could do with namespacing.

> +/* SIU registers */
> +#define IFCTL		(0x000 / sizeof(u32))
> +#define SRCTL		(0x004 / sizeof(u32))

As could all these.

> +/*
> + * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
> + * packing is supported

In what way are these supported?  The function appears to always set the
same register value...
 
> +static struct snd_kcontrol_new capture_controls = {
> +	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
> +	.name		= "PCM Capture Volume",

When we have multi-CODEC support we'll want to integrate these controls
with that since that'll have to be able to sort out the potential
namespace collison issues.

> +/*
> + * 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.
> + */

You should handle this by having separate DAIs for playback and capture
at the ASoC level if you want to support it, if all the signals going
out of the processor are independant there's no need for the rest of the
system to know that they're related internally.  However, forcing
symmetry is fine for now.

> +	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);

Does clk_put() mind getting fed PTR_ERR() pointers?

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board
@ 2010-01-19 12:34     ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-19 12:34 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Liam Girdwood, Kuninori Morimoto, Magnus Damm

On Tue, Jan 19, 2010 at 09:09:01AM +0100, Guennadi Liakhovetski wrote:

> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include a
> Sound Interface Unit (SIU). This patch adds drivers for this interface and
> support for the sh7722 Migo-R board.

Normally you'd split the board support into a separate patch, and
splitting the DMA and DAI drivers wouldn't hurt either.  It makes the
review easier by keeping the patches smaller and more focused.

> +config SND_SIU_MIGOR
> +	tristate "SIU sound support on Migo-R"
> +	depends on SND_SOC_SH4_SIU && SH_MIGOR
> +	select SND_SOC_WM8978
> +	help
> +	  This option enables generic sound support for the
> +	  SH7722 Migo-R board
> +

I'd be tempted to just make SND_SOC_SH4_SIU a hidden variable and select
it from here.  I know that most of the CPU DAIs are exposed but it
doesn't actually seem to buy us anything.

> +/* Default 8000Hz sampling frequency */
> +static unsigned long codec_freq = 49152350 / 12;

Perhaps a #define for the input clock rate (I'm assuming that this is
what the 49152350 is)?

> +	switch (rate) {
> +	case 48000:
> +		mclk_div = 0x40;
> +		opclk_div = 0;
> +		/* f2 = 98304000, was 98304050 */

Like Liam says either remove these comments or clarify them please :)

> +	/*
> +	 * Calculate f2, according to Figure 40 "PLL and Clock Select Circuit"
> +	 * in WM8978 datasheet
> +	 */
> +	f2 = rate * 256 * 4 * mclk_numerator[mclk_idx] /
> +		mclk_denominator[mclk_idx];

Figure 40 of the current datasheet is the DSP mode B clock diagram -
it's now figure 41.  Better to include a datasheet revision.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_MCLKDIV,
> +				     mclk_div & 0xe0);
> +	if (ret < 0)
> +		return ret;

This doesn't feel like something that the machine driver should have to
figure out, the CODEC driver should be able to figure out the MCLK
division from a set_sysclk() call telling it the PLL/MCLK frequency.
This would mean that the machine driver would need to at most specify
the PLL output frequency.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
> +	if (ret < 0)
> +		return ret;

I said add a set_clkdiv() option for the output clock GPIO configuration
- now that I think about it you can probably do this on any configuration
of OPCLKDIV by a machine driver.

> +	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_DACCLK, 8);
> +	if (ret < 0)
> +		return ret;

The DAC clock configuration can probably be factored into the CODEC
driver - you should know (or be able to be told) the CODEC system clock
so this should then only have an internal effect.

> +	/* See Figure 40 */
> +	codec_freq = f2 / ((opclk_div >> 4) + 1) >> 2;

This feels like something that should be wrapped up in the CODEC
driver.  In any case, 

> +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;
> +
> +	/* disable the PLL */
> +	return snd_soc_dai_set_pll(codec_dai, 0, 0, 0, 0);

This will happen for both playback and once for record, meaning that if
you've got both running then the first one to stop will halt the PLL
which probably isn't what you want.

> +static int migor_startup(struct snd_pcm_substream *substream)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct snd_soc_device *socdev = rtd->socdev;
> +	struct snd_soc_codec *codec = socdev->card->codec;
> +	int ret;
> +
> +	/* Activate DAC output routes */
> +	ret = snd_soc_dapm_enable_pin(codec, "Left Speaker Out");
> +	if (ret < 0) {
> +		dev_warn(socdev->dev, "Left err %d\n", ret);
> +		return ret;
> +	}

Why are you doing this?  DAPM will automatically manage the power of the
DAC widget based on the playback state.

> +/* Board specifics */
> +#if defined(CONFIG_CPU_SUBTYPE_SH7722)
> +# define MAX_VOLUME		0x1000
> +#else
> +# define MAX_VOLUME		0x7fff
> +#endif

These defines could do with namespacing.

> +/* SIU registers */
> +#define IFCTL		(0x000 / sizeof(u32))
> +#define SRCTL		(0x004 / sizeof(u32))

As could all these.

> +/*
> + * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
> + * packing is supported

In what way are these supported?  The function appears to always set the
same register value...
 
> +static struct snd_kcontrol_new capture_controls = {
> +	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
> +	.name		= "PCM Capture Volume",

When we have multi-CODEC support we'll want to integrate these controls
with that since that'll have to be able to sort out the potential
namespace collison issues.

> +/*
> + * 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.
> + */

You should handle this by having separate DAIs for playback and capture
at the ASoC level if you want to support it, if all the signals going
out of the processor are independant there's no need for the rest of the
system to know that they're related internally.  However, forcing
symmetry is fine for now.

> +	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);

Does clk_put() mind getting fed PTR_ERR() pointers?

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
       [not found]   ` <20100119105729.GA32559@opensource.wolfsonmicro.com::587>
@ 2010-01-20 19:50       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-20 19:50 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, Liam Girdwood, Magnus Damm, linux-sh

On Tue, 19 Jan 2010, Mark Brown wrote:

> On Tue, Jan 19, 2010 at 09:08:57AM +0100, Guennadi Liakhovetski wrote:
> 
> This all looks pretty good - there's a few issues below but they're
> largely stylistic rather than anything fundamental.
> 
> > +	0x0000, 0x0000, 0x0000, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
> > +	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
> > +	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
> 
> What do these comments refer to - do you mean to say that these are not
> the actual chip register defaults?  The normal way to deal with those is
> to have a write (or other update of the cache defaults).  This avoids
> potential confusion later on if the chip updates the register defaults
> or when reviewing against the datasheet.

No, I meant, that there's a "Value Update" bit in that register, usually 
bit 8. So, that bit, of course, is not in default, but it was suggested to 
me on IRC to set it in cache to force automatic value update.

> > +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> > +						ARRAY_SIZE(xtexts), xtexts)
> 
> This looks generic - please namespace it and add it to the header file,
> other drivers can benefit from it.

SOC_ARRAY_SINGLE()?

> > +static const struct soc_enum wm8978_enum[] = {
> > +	/* adc */
> > +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
> 
> Please define individual variables for this - indexing into the array
> gets hard to follow, it's far too easy to have an off by one errors
> trying to match things up.
> 
> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +                                                       m, ARRAY_SIZE(m))
> 
> 
> > +/* Mixer #3: Boost (Input) mixer */
> > +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> > +SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
> > +};
> > +static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
> > +SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
> > +};
> 
> These should have "Switch" at the end of the name.  Also, the names are
> going be concatenated with the mixer names so you'll end up with things
> "Left Boost Mixer Left PGA Switch" - the individual controls should
> probably be just "Switch" so you end up with "Left Boost Mixer Switch".
> 
> > +/* Mic Input boost vol */
> > +static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
> > +SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
> > +SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
> > +};
> 
> This doesn't seem to be referenced anywhere?
> 
> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +							m, ARRAY_SIZE(m))
> 
> Again, this should be done somewhere generic.  Probably by going through
> and changing the SND_SOC_DAPM_MIXER definition.

Well, having discussed this privately and having looked at the heades, I'm 
not sure, this is a good idea. First, there are

SND_SOC_DAPM_PGA()
SND_SOC_DAPM_MIXER()
SND_SOC_DAPM_MIXER_NAMED_CTL()
SND_SOC_DAPM_PGA_E()
SND_SOC_DAPM_MIXER_E()
SND_SOC_DAPM_MIXER_NAMED_CTL_E()

- 6 macros that follow the pattern

.kcontrols = wcontrols, .num_kcontrols = wncontrols,

and lots of macros like

SOC_ENUM_DOUBLE()
SOC_ENUM_SINGLE()
SOC_ENUM_SINGLE_EXT()
...

doing .max = xmax. BTW, many of the users even open-code array sizes 
instead of using ARRAY_SIZE() like

static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
	"Off"};
static const char *spk_function[] = {"On", "Off"};
static const struct soc_enum tosa_enum[] = {
	SOC_ENUM_SINGLE_EXT(5, jack_function),
	SOC_ENUM_SINGLE_EXT(2, spk_function),
};
 
Changing only one of them would make the headers and the API look 
inconsistent. Changing all of them... I really don't think we want to 
endeavor this now, at least not me;) Besides, doing this locally is very 
convenient, and is available to everyone - there's no space science 
involved with this. One can be even as evil as doing

+#define _A(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
+						m, ARRAY_SIZE(m))
...
+#undef _A

+#define _A(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
+					ARRAY_SIZE(xtexts), xtexts)
...
+#undef _A

I think, it's best to leave these things as they are.

Otherwise we can give this to kernel janitors as a small exercise;)

> > +	/* Output PLL to GPIO1 */
> > +	snd_soc_write(codec, WM8978_GPIO_CONTROL,
> > +		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
> 
> This should be being done unconditionally.  Put a switch in via the
> set_dai_clkdiv() call.

Ok, I moved it to wm8978_set_dai_clkdiv() under "case WM8978_OPCLKDIV:"

> > +	/* Mic bias */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> > +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> > +
> > +	/* Out-1 enabled, left/right input channel enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> > +
> > +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
> 
> These should all be being managed via DAPM.

Yes, I'm having quite a bit of trouble with DAPM, but I'm getting through 
slowly. Will need some more time for a next version.

> 
> > +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;
> 
> This bitmask maintains everything except the two LSB...
> 
> > +	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:
> > +		power1 |= 0xC;
> 
> ...but this is also managing other bits.

Right, bits 7-6 are either kept or set, nothing wrong with that.

> > +		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);
> > +		}
> 
> > +/* Also supports 12kHz */
> > +#define WM8978_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_8000_48000
> 
> > +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;
> > +
> > +	/* we only need to suspend if we are a valid card */
> > +	if (!codec->card)
> > +		return 0;
> 
> Don't need to check for the card any more, the core will stop you
> getting called without a card.
> 
> > +	/* Sync reg_cache with the hardware */
> > +	for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
> > +		if (i = WM8978_RESET)
> > +			continue;
> 
> You can also skip the write if the register has the default value (this
> will speed up resume slightly).
> 
> > +		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
> > +		codec->hw_write(codec->control_data, (char *)&data, 2);
> > +	}
> 
> Just use snd_soc_write()?

I'll take care about the rest.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-20 19:50       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-20 19:50 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, Liam Girdwood, Magnus Damm, linux-sh

On Tue, 19 Jan 2010, Mark Brown wrote:

> On Tue, Jan 19, 2010 at 09:08:57AM +0100, Guennadi Liakhovetski wrote:
> 
> This all looks pretty good - there's a few issues below but they're
> largely stylistic rather than anything fundamental.
> 
> > +	0x0000, 0x0000, 0x0000, 0x01ff,	/* 0x08...0x0b */ /* 0x0b contains the VU bit */
> > +	0x01ff, 0x0000, 0x0100, 0x01ff,	/* 0x0c...0x0f */ /* 0x0c and 0x0f contain the VU bit */
> > +	0x01ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */ /* 0x10 contains the VU bit */
> 
> What do these comments refer to - do you mean to say that these are not
> the actual chip register defaults?  The normal way to deal with those is
> to have a write (or other update of the cache defaults).  This avoids
> potential confusion later on if the chip updates the register defaults
> or when reviewing against the datasheet.

No, I meant, that there's a "Value Update" bit in that register, usually 
bit 8. So, that bit, of course, is not in default, but it was suggested to 
me on IRC to set it in cache to force automatic value update.

> > +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> > +						ARRAY_SIZE(xtexts), xtexts)
> 
> This looks generic - please namespace it and add it to the header file,
> other drivers can benefit from it.

SOC_ARRAY_SINGLE()?

> > +static const struct soc_enum wm8978_enum[] = {
> > +	/* adc */
> > +	ARRAY_SINGLE(WM8978_COMPANDING_CONTROL, 1, wm8978_companding),
> 
> Please define individual variables for this - indexing into the array
> gets hard to follow, it's far too easy to have an off by one errors
> trying to match things up.
> 
> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +                                                       m, ARRAY_SIZE(m))
> 
> 
> > +/* Mixer #3: Boost (Input) mixer */
> > +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> > +SOC_DAPM_SINGLE("Left PGA Mute", WM8978_LEFT_INP_PGA_CONTROL, 6, 1, 0),
> > +};
> > +static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
> > +SOC_DAPM_SINGLE("Right PGA Mute", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1, 0),
> > +};
> 
> These should have "Switch" at the end of the name.  Also, the names are
> going be concatenated with the mixer names so you'll end up with things
> "Left Boost Mixer Left PGA Switch" - the individual controls should
> probably be just "Switch" so you end up with "Left Boost Mixer Switch".
> 
> > +/* Mic Input boost vol */
> > +static const struct snd_kcontrol_new wm8978_mic_boost_controls[] = {
> > +SOC_DAPM_SINGLE("Left Mic Volume", WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0),
> > +SOC_DAPM_SINGLE("Right Mic Volume", WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0),
> > +};
> 
> This doesn't seem to be referenced anywhere?
> 
> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +							m, ARRAY_SIZE(m))
> 
> Again, this should be done somewhere generic.  Probably by going through
> and changing the SND_SOC_DAPM_MIXER definition.

Well, having discussed this privately and having looked at the heades, I'm 
not sure, this is a good idea. First, there are

SND_SOC_DAPM_PGA()
SND_SOC_DAPM_MIXER()
SND_SOC_DAPM_MIXER_NAMED_CTL()
SND_SOC_DAPM_PGA_E()
SND_SOC_DAPM_MIXER_E()
SND_SOC_DAPM_MIXER_NAMED_CTL_E()

- 6 macros that follow the pattern

.kcontrols = wcontrols, .num_kcontrols = wncontrols,

and lots of macros like

SOC_ENUM_DOUBLE()
SOC_ENUM_SINGLE()
SOC_ENUM_SINGLE_EXT()
...

doing .max = xmax. BTW, many of the users even open-code array sizes 
instead of using ARRAY_SIZE() like

static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
	"Off"};
static const char *spk_function[] = {"On", "Off"};
static const struct soc_enum tosa_enum[] = {
	SOC_ENUM_SINGLE_EXT(5, jack_function),
	SOC_ENUM_SINGLE_EXT(2, spk_function),
};
 
Changing only one of them would make the headers and the API look 
inconsistent. Changing all of them... I really don't think we want to 
endeavor this now, at least not me;) Besides, doing this locally is very 
convenient, and is available to everyone - there's no space science 
involved with this. One can be even as evil as doing

+#define _A(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
+						m, ARRAY_SIZE(m))
...
+#undef _A

+#define _A(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
+					ARRAY_SIZE(xtexts), xtexts)
...
+#undef _A

I think, it's best to leave these things as they are.

Otherwise we can give this to kernel janitors as a small exercise;)

> > +	/* Output PLL to GPIO1 */
> > +	snd_soc_write(codec, WM8978_GPIO_CONTROL,
> > +		      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
> 
> This should be being done unconditionally.  Put a switch in via the
> set_dai_clkdiv() call.

Ok, I moved it to wm8978_set_dai_clkdiv() under "case WM8978_OPCLKDIV:"

> > +	/* Mic bias */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> > +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> > +
> > +	/* Out-1 enabled, left/right input channel enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> > +
> > +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
> 
> These should all be being managed via DAPM.

Yes, I'm having quite a bit of trouble with DAPM, but I'm getting through 
slowly. Will need some more time for a next version.

> 
> > +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;
> 
> This bitmask maintains everything except the two LSB...
> 
> > +	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:
> > +		power1 |= 0xC;
> 
> ...but this is also managing other bits.

Right, bits 7-6 are either kept or set, nothing wrong with that.

> > +		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);
> > +		}
> 
> > +/* Also supports 12kHz */
> > +#define WM8978_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_8000_48000
> 
> > +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;
> > +
> > +	/* we only need to suspend if we are a valid card */
> > +	if (!codec->card)
> > +		return 0;
> 
> Don't need to check for the card any more, the core will stop you
> getting called without a card.
> 
> > +	/* Sync reg_cache with the hardware */
> > +	for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
> > +		if (i == WM8978_RESET)
> > +			continue;
> 
> You can also skip the write if the register has the default value (this
> will speed up resume slightly).
> 
> > +		data = cpu_to_be16((i << 9) | (cache[i] & 0x1ff));
> > +		codec->hw_write(codec->control_data, (char *)&data, 2);
> > +	}
> 
> Just use snd_soc_write()?

I'll take care about the rest.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-19 10:46     ` Liam Girdwood
@ 2010-01-20 20:01       ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-20 20:01 UTC (permalink / raw)
  To: Liam Girdwood
  Cc: Kuninori Morimoto, alsa-devel, Mark Brown, Magnus Damm, linux-sh

On Tue, 19 Jan 2010, Liam Girdwood wrote:

> Looks ok, some questions below.
> 
> On Tue, 2010-01-19 at 09:08 +0100, Guennadi Liakhovetski wrote:
> > The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> > is stereo and also has some differences in pin configuration and internal
> > signal routing. This driver is based on wm8974 and takes the differences into
> > account.
> > 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> > ---

[snip]

> > +/* OUT1 - HeadPhones */
> > +SOC_SINGLE("Left HeadPhone Playback ZC Switch",
> > +	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
> 
> HeadPhone is usually "Headphone"

Ok

> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +							m, ARRAY_SIZE(m))
> 
> I'd be tempted to rename this and add to soc-dapm.h

Please, see my reply to Mark's review.

> > +static void pll_factors(struct wm8978_pll_div *pll_div, unsigned int target,
> > +			unsigned int source)
> > +{
> > +	u64 Kpart;
> > +	unsigned int K, Ndiv, Nmod;
> > +
> > +	Ndiv = target / source;
> > +	if (Ndiv < 6) {
> > +		source >>= 1;
> > +		pll_div->div2 = 1;
> > +		Ndiv = target / source;
> > +	} else {
> > +		pll_div->div2 = 0;
> > +	}
> > +
> 
> I would personally not put the extra { here

That's also what I did a couple of years ago, but this contradicts the 
kernel CodingStyle, and, I think, checkpatch would complain too.

> > +	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
> > +	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
> > +
> > +	/* Mic bias */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> > +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> > +
> > +	/* Out-1 enabled, left/right input channel enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> > +
> > +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
> 
> Power stuff should be in either dapm or bias functions.

Yes, still working on this.

> > +#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
> > +
> 
> It would be nice to have the relevant bits defined here for set_fmt()
> etc instead of just the magic numbers used in the above codec driver. 

As I explained privately, I agree, that using names instead of bits helps 
- but (mostly) only where those bits are reused multiple times in the 
code. If you only have to initialise a register once with some bitmask, I 
think, code like

	/* Enable input X, output Y, set default W polarity to Z */
	__raw_writel(0x123, reg);

looks better than

	__raw_writel(CHIP_INPUT_X_ENABLE | CHIP_OUTPUT_Y_ENABLE | 
			CHIP_SIGNAL_W_POLARITY_Z, reg);

so, unless there strong preferences in ALSA world, I'll try to combine 
both. Let me know if this contradicts the common ALSA style.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-20 20:01       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-20 20:01 UTC (permalink / raw)
  To: Liam Girdwood
  Cc: Kuninori Morimoto, alsa-devel, Mark Brown, Magnus Damm, linux-sh

On Tue, 19 Jan 2010, Liam Girdwood wrote:

> Looks ok, some questions below.
> 
> On Tue, 2010-01-19 at 09:08 +0100, Guennadi Liakhovetski wrote:
> > The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> > is stereo and also has some differences in pin configuration and internal
> > signal routing. This driver is based on wm8974 and takes the differences into
> > account.
> > 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> > ---

[snip]

> > +/* OUT1 - HeadPhones */
> > +SOC_SINGLE("Left HeadPhone Playback ZC Switch",
> > +	   WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
> 
> HeadPhone is usually "Headphone"

Ok

> > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > +							m, ARRAY_SIZE(m))
> 
> I'd be tempted to rename this and add to soc-dapm.h

Please, see my reply to Mark's review.

> > +static void pll_factors(struct wm8978_pll_div *pll_div, unsigned int target,
> > +			unsigned int source)
> > +{
> > +	u64 Kpart;
> > +	unsigned int K, Ndiv, Nmod;
> > +
> > +	Ndiv = target / source;
> > +	if (Ndiv < 6) {
> > +		source >>= 1;
> > +		pll_div->div2 = 1;
> > +		Ndiv = target / source;
> > +	} else {
> > +		pll_div->div2 = 0;
> > +	}
> > +
> 
> I would personally not put the extra { here

That's also what I did a couple of years ago, but this contradicts the 
kernel CodingStyle, and, I think, checkpatch would complain too.

> > +	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
> > +	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
> > +
> > +	/* Mic bias */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
> > +		      (snd_soc_read(codec, 1) & ~4) | 0x10);
> > +
> > +	/* Out-1 enabled, left/right input channel enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x1bf);
> > +
> > +	/* Out-2 disabled, right/left output channel enabled, dac enabled */
> > +	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0x10f);
> 
> Power stuff should be in either dapm or bias functions.

Yes, still working on this.

> > +#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
> > +
> 
> It would be nice to have the relevant bits defined here for set_fmt()
> etc instead of just the magic numbers used in the above codec driver. 

As I explained privately, I agree, that using names instead of bits helps 
- but (mostly) only where those bits are reused multiple times in the 
code. If you only have to initialise a register once with some bitmask, I 
think, code like

	/* Enable input X, output Y, set default W polarity to Z */
	__raw_writel(0x123, reg);

looks better than

	__raw_writel(CHIP_INPUT_X_ENABLE | CHIP_OUTPUT_Y_ENABLE | 
			CHIP_SIGNAL_W_POLARITY_Z, reg);

so, unless there strong preferences in ALSA world, I'll try to combine 
both. Let me know if this contradicts the common ALSA style.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-20 19:50       ` Guennadi Liakhovetski
@ 2010-01-20 20:17         ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-20 20:17 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Wed, Jan 20, 2010 at 08:50:59PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 19 Jan 2010, Mark Brown wrote:

> > What do these comments refer to - do you mean to say that these are not
> > the actual chip register defaults?  The normal way to deal with those is

> No, I meant, that there's a "Value Update" bit in that register, usually 
> bit 8. So, that bit, of course, is not in default, but it was suggested to 
> me on IRC to set it in cache to force automatic value update.

Right, but it's better to set it in the cache by writing after the fact
rather than by changing the register defaults.  Like I say, that can
easily confuse things since sometimes people want to know the actual
default values.

> 
> > > +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> > > +						ARRAY_SIZE(xtexts), xtexts)
> > 
> > This looks generic - please namespace it and add it to the header file,
> > other drivers can benefit from it.
> 
> SOC_ARRAY_SINGLE()?

How about SOC_ENUM_ARRAY() since it's really only useful for arrays?

> > > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > > +							m, ARRAY_SIZE(m))

> > Again, this should be done somewhere generic.  Probably by going through
> > and changing the SND_SOC_DAPM_MIXER definition.

> Well, having discussed this privately and having looked at the heades, I'm 
> not sure, this is a good idea. First, there are

> SND_SOC_DAPM_PGA()
> SND_SOC_DAPM_MIXER()
> SND_SOC_DAPM_MIXER_NAMED_CTL()
> SND_SOC_DAPM_PGA_E()
> SND_SOC_DAPM_MIXER_E()
> SND_SOC_DAPM_MIXER_NAMED_CTL_E()

> - 6 macros that follow the pattern

> .kcontrols = wcontrols, .num_kcontrols = wncontrols,

That doesn't mean it's a good idea, though for PGAs note that the normal
case is that the array is omitted (and actually, thinking about this we
need an array omitted case for everything anyway).

> Changing only one of them would make the headers and the API look 
> inconsistent. Changing all of them... I really don't think we want to 
> endeavor this now, at least not me;) Besides, doing this locally is very 
> convenient, and is available to everyone - there's no space science 
> involved with this. One can be even as evil as doing

For maintainability of the subsystem it's really nice if a driver doing
bog standard things looks like it's doing bog standard things - neat
local tricks break the pattern recognition that helps a lot when looking
at multiple drivers.

> > > +	u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
> > 
> > This bitmask maintains everything except the two LSB...
> > 
> > > +	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:
> > > +		power1 |= 0xC;
> > 
> > ...but this is also managing other bits.

> Right, bits 7-6 are either kept or set, nothing wrong with that.

That seems wrong - if they're being managed in the bias level
configuration they ought to be being turned off at some point.  If they
should be set all the time then set them during chip init.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-20 20:17         ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-20 20:17 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Wed, Jan 20, 2010 at 08:50:59PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 19 Jan 2010, Mark Brown wrote:

> > What do these comments refer to - do you mean to say that these are not
> > the actual chip register defaults?  The normal way to deal with those is

> No, I meant, that there's a "Value Update" bit in that register, usually 
> bit 8. So, that bit, of course, is not in default, but it was suggested to 
> me on IRC to set it in cache to force automatic value update.

Right, but it's better to set it in the cache by writing after the fact
rather than by changing the register defaults.  Like I say, that can
easily confuse things since sometimes people want to know the actual
default values.

> 
> > > +#define ARRAY_SINGLE(xreg, xshift, xtexts) SOC_ENUM_SINGLE(xreg, xshift, \
> > > +						ARRAY_SIZE(xtexts), xtexts)
> > 
> > This looks generic - please namespace it and add it to the header file,
> > other drivers can benefit from it.
> 
> SOC_ARRAY_SINGLE()?

How about SOC_ENUM_ARRAY() since it's really only useful for arrays?

> > > +#define MIXER_ARRAY(n, r, s, i, m) SND_SOC_DAPM_MIXER(n, r, s, i, \
> > > +							m, ARRAY_SIZE(m))

> > Again, this should be done somewhere generic.  Probably by going through
> > and changing the SND_SOC_DAPM_MIXER definition.

> Well, having discussed this privately and having looked at the heades, I'm 
> not sure, this is a good idea. First, there are

> SND_SOC_DAPM_PGA()
> SND_SOC_DAPM_MIXER()
> SND_SOC_DAPM_MIXER_NAMED_CTL()
> SND_SOC_DAPM_PGA_E()
> SND_SOC_DAPM_MIXER_E()
> SND_SOC_DAPM_MIXER_NAMED_CTL_E()

> - 6 macros that follow the pattern

> .kcontrols = wcontrols, .num_kcontrols = wncontrols,

That doesn't mean it's a good idea, though for PGAs note that the normal
case is that the array is omitted (and actually, thinking about this we
need an array omitted case for everything anyway).

> Changing only one of them would make the headers and the API look 
> inconsistent. Changing all of them... I really don't think we want to 
> endeavor this now, at least not me;) Besides, doing this locally is very 
> convenient, and is available to everyone - there's no space science 
> involved with this. One can be even as evil as doing

For maintainability of the subsystem it's really nice if a driver doing
bog standard things looks like it's doing bog standard things - neat
local tricks break the pattern recognition that helps a lot when looking
at multiple drivers.

> > > +	u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
> > 
> > This bitmask maintains everything except the two LSB...
> > 
> > > +	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:
> > > +		power1 |= 0xC;
> > 
> > ...but this is also managing other bits.

> Right, bits 7-6 are either kept or set, nothing wrong with that.

That seems wrong - if they're being managed in the bias level
configuration they ought to be being turned off at some point.  If they
should be set all the time then set them during chip init.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-20 20:01       ` Guennadi Liakhovetski
@ 2010-01-20 20:21         ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-20 20:21 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Wed, Jan 20, 2010 at 09:01:46PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 19 Jan 2010, Liam Girdwood wrote:

> > It would be nice to have the relevant bits defined here for set_fmt()
> > etc instead of just the magic numbers used in the above codec driver. 

> As I explained privately, I agree, that using names instead of bits helps 
> - but (mostly) only where those bits are reused multiple times in the 
> code. If you only have to initialise a register once with some bitmask, I 
> think, code like

> 	/* Enable input X, output Y, set default W polarity to Z */
> 	__raw_writel(0x123, reg);

> looks better than

> 	__raw_writel(CHIP_INPUT_X_ENABLE | CHIP_OUTPUT_Y_ENABLE | 
> 			CHIP_SIGNAL_W_POLARITY_Z, reg);

> so, unless there strong preferences in ALSA world, I'll try to combine 
> both. Let me know if this contradicts the common ALSA style.

It's nice to have the names used when they're readily available (as they
are for much of this device).  It's much easier to figure out exactly
what the latter case is supposed to do than reverse engineer a bitmask
and sometimes scratch your head over why exactly some of the bits were
set or if (as happens) someone made a mistake when converting to hex.

I'm not too religious about it, though - it's a relatively mild
preference.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-20 20:21         ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-20 20:21 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Wed, Jan 20, 2010 at 09:01:46PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 19 Jan 2010, Liam Girdwood wrote:

> > It would be nice to have the relevant bits defined here for set_fmt()
> > etc instead of just the magic numbers used in the above codec driver. 

> As I explained privately, I agree, that using names instead of bits helps 
> - but (mostly) only where those bits are reused multiple times in the 
> code. If you only have to initialise a register once with some bitmask, I 
> think, code like

> 	/* Enable input X, output Y, set default W polarity to Z */
> 	__raw_writel(0x123, reg);

> looks better than

> 	__raw_writel(CHIP_INPUT_X_ENABLE | CHIP_OUTPUT_Y_ENABLE | 
> 			CHIP_SIGNAL_W_POLARITY_Z, reg);

> so, unless there strong preferences in ALSA world, I'll try to combine 
> both. Let me know if this contradicts the common ALSA style.

It's nice to have the names used when they're readily available (as they
are for much of this device).  It's much easier to figure out exactly
what the latter case is supposed to do than reverse engineer a bitmask
and sometimes scratch your head over why exactly some of the bits were
set or if (as happens) someone made a mistake when converting to hex.

I'm not too religious about it, though - it's a relatively mild
preference.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-20 20:01       ` Guennadi Liakhovetski
  (?)
  (?)
@ 2010-01-20 20:25       ` Liam Girdwood
  -1 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-20 20:25 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Mark Brown, linux-sh

On Wed, 2010-01-20 at 21:01 +0100, Guennadi Liakhovetski wrote:
> On Tue, 19 Jan 2010, Liam Girdwood wrote:
> > 
> > It would be nice to have the relevant bits defined here for set_fmt()
> > etc instead of just the magic numbers used in the above codec driver. 
> 
> As I explained privately, I agree, that using names instead of bits helps 
> - but (mostly) only where those bits are reused multiple times in the 
> code. If you only have to initialise a register once with some bitmask, I 
> think, code like
> 
> 	/* Enable input X, output Y, set default W polarity to Z */
> 	__raw_writel(0x123, reg);
> 
> looks better than
> 
> 	__raw_writel(CHIP_INPUT_X_ENABLE | CHIP_OUTPUT_Y_ENABLE | 
> 			CHIP_SIGNAL_W_POLARITY_Z, reg);
> 
> so, unless there strong preferences in ALSA world, I'll try to combine 
> both. Let me know if this contradicts the common ALSA style.

I disagree, it's far better to use the bottom example as I can see
explicitly which bits you intend to write.  This makes it easier for
others to extend and debug your code.

Furthermore, WM codecs also have software generated register bits and
register macros (available upon request) that further reduce any effort
and any potential register value bugs.

Liam


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-20 20:17         ` Mark Brown
@ 2010-01-22  8:35           ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22  8:35 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, Liam Girdwood, Magnus Damm, linux-sh

On Wed, 20 Jan 2010, Mark Brown wrote:

> On Wed, Jan 20, 2010 at 08:50:59PM +0100, Guennadi Liakhovetski wrote:
> > On Tue, 19 Jan 2010, Mark Brown wrote:

[snip]

> > > > +	u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
> > > 
> > > This bitmask maintains everything except the two LSB...
> > > 
> > > > +	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:
> > > > +		power1 |= 0xC;
> > > 
> > > ...but this is also managing other bits.
> 
> > Right, bits 7-6 are either kept or set, nothing wrong with that.
> 
> That seems wrong - if they're being managed in the bias level
> configuration they ought to be being turned off at some point.  If they
> should be set all the time then set them during chip init.

Right, a couple of lines below:

+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-22  8:35           ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22  8:35 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, Liam Girdwood, Magnus Damm, linux-sh

On Wed, 20 Jan 2010, Mark Brown wrote:

> On Wed, Jan 20, 2010 at 08:50:59PM +0100, Guennadi Liakhovetski wrote:
> > On Tue, 19 Jan 2010, Mark Brown wrote:

[snip]

> > > > +	u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
> > > 
> > > This bitmask maintains everything except the two LSB...
> > > 
> > > > +	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:
> > > > +		power1 |= 0xC;
> > > 
> > > ...but this is also managing other bits.
> 
> > Right, bits 7-6 are either kept or set, nothing wrong with that.
> 
> That seems wrong - if they're being managed in the bias level
> configuration they ought to be being turned off at some point.  If they
> should be set all the time then set them during chip init.

Right, a couple of lines below:

+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4] ASoC: add a WM8978 codec driver
  2010-01-22  8:35           ` Guennadi Liakhovetski
@ 2010-01-22 10:35             ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-22 10:35 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Fri, Jan 22, 2010 at 09:35:44AM +0100, Guennadi Liakhovetski wrote:
> On Wed, 20 Jan 2010, Mark Brown wrote:

> > That seems wrong - if they're being managed in the bias level
> > configuration they ought to be being turned off at some point.  If they
> > should be set all the time then set them during chip init.

> Right, a couple of lines below:

> +	case SND_SOC_BIAS_OFF:
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);

Yeah, I saw that.  This is one of those cases where numeric bitmask
based updates make things much harder to follow - without referring to
the datasheet and thinking about it it's not clear what's going on and
if it's intentional.

I'd suggest changing all this code to use snd_soc_update_bits() and
symbolic names for the bits that are getting twiddled - it really is a
lot easier to follow.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4] ASoC: add a WM8978 codec driver
@ 2010-01-22 10:35             ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-22 10:35 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Fri, Jan 22, 2010 at 09:35:44AM +0100, Guennadi Liakhovetski wrote:
> On Wed, 20 Jan 2010, Mark Brown wrote:

> > That seems wrong - if they're being managed in the bias level
> > configuration they ought to be being turned off at some point.  If they
> > should be set all the time then set them during chip init.

> Right, a couple of lines below:

> +	case SND_SOC_BIAS_OFF:
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);

Yeah, I saw that.  This is one of those cases where numeric bitmask
based updates make things much harder to follow - without referring to
the datasheet and thinking about it it's not clear what's going on and
if it's intentional.

I'd suggest changing all this code to use snd_soc_update_bits() and
symbolic names for the bits that are getting twiddled - it really is a
lot easier to follow.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-19  8:08   ` Guennadi Liakhovetski
@ 2010-01-22 16:27     ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 16:27 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
is stereo and also has some differences in pin configuration and internal
signal routing. This driver is based on wm8974 and takes the differences into
account.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. changed HeadPhone to Headphone
2. using global simplified macros from two patches posted earlier
3. removed power-related operations from wm8978_hw_params()
4. register-cache: removed misleading comments, removed "update" bits, 
   they are set now in a dynamically-created copy
5. moved entries from wm8978_enum[] array into individual variables
6. adjusted control names
7. removed unused wm8978_aux_boost_controls[] and 
   wm8978_mic_boost_controls[]
8. moved setting GPIO1 to clock output to OPCLK configuration in 
   wm8978_set_dai_clkdiv()
9. multiple improvements to controls, widgets, audio-map to actually 
   enable automatic hardware configuration by DAPM
10. using SNDRV_PCM_RATE_8000_48000 now
11. removed "if (!codec->card)" tests
12. in wm8978_resume() only copying modified registers, using 
    snd_soc_write() instead of open-codying.

In short - addressed all comments (thanks a lot again!) - except one... I 
still would not like to use symbolic names for register bits. Instead I 
thoroughly commented all multi-bit register manipulations. Reasons:

1. thanks for the header, Mark, but unfortunately it contains errors 
   (duplicate register names, duplicate and wrong bitfield names)
2. the header also uses spaces for indentation, which would have to be 
   manually replaced with TABs
3. I am still not convinced, that macro names like WM8978_WL or 
   WM8978_DLRSWAP or WM8978_MS or... better describe the meaning of the 
   bitfield than respective comment in the source. Here's an example of a 
   comment from this patch:

	/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
	power1 |= 0xc;

   Where bit-field names do make sense, IMHO, is in drivers, where the 
   same bitfields have to be written / evaluated multiple times at 
   different locations. Than indeed giving those bits symbolic names helps 
   finding them. So, I'd like to request a permission to preserve the 
   present style of the driver.

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 62ff26a..0aad72f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
 	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
@@ -230,6 +231,9 @@ config SND_SOC_WM8971
 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 ea98354..fbd290e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -44,6 +44,7 @@ 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
@@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..1ca2377
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,988 @@
+/*
+ * 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 */
+};
+
+static const int update_reg[] = {
+	0xb, 0xc, 0xf, 0x10, 0x2d, 0x2e, 0x34, 0x35, 0x36, 0x37
+};
+
+/* codec private data */
+struct wm8978_priv {
+	struct snd_soc_codec codec;
+	unsigned int f_pllout;
+	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_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
+
+	SOC_SINGLE_TLV("Left PCM Volume",
+		WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+	SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
+	SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
+
+	SOC_SINGLE_TLV("Left ADC Volume",
+	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+	SOC_SINGLE_TLV("Right ADC 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 Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+	SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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_SINGLE("Left Capture PGA ZC Switch",
+		WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
+	SOC_SINGLE("Right Capture PGA ZC Switch",
+		WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
+
+	/* OUT1 - Headphones */
+	SOC_SINGLE("Left Headphone Playback ZC Switch",
+		WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Left Headphone Playback Volume",
+		WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+	SOC_SINGLE("Right Headphone Playback ZC Switch",
+		WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Right Headphone Playback Volume",
+		WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+	/* OUT2 - Speakers */
+	SOC_SINGLE("Left Speaker Playback ZC Switch",
+		WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Left Speaker Playback Volume",
+		WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+	SOC_SINGLE("Right Speaker Playback ZC Switch",
+		WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Right Speaker Playback Volume",
+		WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+	/* OUT3/4 - Line Output */
+	SOC_SINGLE("Left Line Playback Switch",
+		WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
+	SOC_SINGLE("Right Line Playback Switch",
+		WM8978_OUT4_MIXER_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_pga[] = {
+	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_pga[] = {
+	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),
+};
+
+/* Mixer #3: Boost (Input) mixer */
+static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
+
+	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE_TLV("L2 Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Aux Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv),
+};
+static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
+
+	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE_TLV("R2 Boost Volume",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Aux Boost Volume",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv),
+};
+
+/* Input PGA volume */
+static const struct snd_kcontrol_new wm8978_left_input_pga_volume[] = {
+	SOC_DAPM_SINGLE_TLV("Volume",
+		WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+};
+static const struct snd_kcontrol_new wm8978_right_input_pga_volume[] = {
+	SOC_DAPM_SINGLE_TLV("Volume",
+		WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+};
+
+/* Headphone */
+static const struct snd_kcontrol_new wm8978_left_hp_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
+};
+static const struct snd_kcontrol_new wm8978_right_hp_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+};
+
+/* Speaker */
+static const struct snd_kcontrol_new wm8978_left_speaker_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
+};
+static const struct snd_kcontrol_new wm8978_right_speaker_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+};
+
+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 PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_input_pga),
+	SOC_MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
+		   wm8978_right_input_pga),
+
+	SOC_MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
+		   wm8978_left_boost_mixer),
+	SOC_MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
+		   wm8978_right_boost_mixer),
+
+	SND_SOC_DAPM_SWITCH("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL, 6, 1,
+		   wm8978_left_input_pga_volume),
+	SND_SOC_DAPM_SWITCH("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1,
+		   wm8978_right_input_pga_volume),
+
+	SND_SOC_DAPM_SWITCH("Left Headphone Out", WM8978_POWER_MANAGEMENT_2, 7, 0,
+		   wm8978_left_hp_mute),
+	SND_SOC_DAPM_SWITCH("Right Headphone Out", WM8978_POWER_MANAGEMENT_2, 8, 0,
+		   wm8978_right_hp_mute),
+
+	SND_SOC_DAPM_SWITCH("Left Speaker Out", WM8978_POWER_MANAGEMENT_3, 6, 0,
+		   wm8978_left_speaker_mute),
+	SND_SOC_DAPM_SWITCH("Right Speaker Out", WM8978_POWER_MANAGEMENT_3, 5, 0,
+		   wm8978_right_speaker_mute),
+
+	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", "Mute Switch", "Right Output Mixer"},
+	{"RHP", NULL, "Right Headphone Out"},
+
+	{"Left Headphone Out", "Mute Switch", "Left Output Mixer"},
+	{"LHP", NULL, "Left Headphone Out"},
+
+	{"Right Speaker Out", "Mute Switch", "Right Output Mixer"},
+	{"RSPK", NULL, "Right Speaker Out"},
+
+	{"Left Speaker Out", "Mute Switch", "Left Output Mixer"},
+	{"LSPK", NULL, "Left Speaker Out"},
+
+	/* Boost Mixer */
+	{"Right ADC", NULL, "Right Boost Mixer"},
+
+	{"Right Boost Mixer", "Aux Boost Volume", "RAUX"},
+	{"Right Boost Mixer", "PGA Boost (+20dB)", "Right Capture PGA"},
+	{"Right Boost Mixer", "R2 Boost Volume", "R2"},
+
+	{"Left ADC", NULL, "Left Boost Mixer"},
+
+	{"Left Boost Mixer", "Aux Boost Volume", "LAUX"},
+	{"Left Boost Mixer", "PGA Boost (+20dB)", "Left Capture PGA"},
+	{"Left Boost Mixer", "L2 Boost Volume", "L2"},
+
+	/* Input PGA */
+	{"Right Capture PGA", "Volume", "Right Input PGA"},
+	{"Left Capture PGA", "Volume", "Left Input PGA"},
+
+	{"Right Input PGA", "R2 Switch", "R2"},
+	{"Right Input PGA", "MicN Switch", "RMICN"},
+	{"Right Input PGA", "MicP Switch", "RMICP"},
+
+	{"Left Input PGA", "L2 Switch", "L2"},
+	{"Left Input PGA", "MicN Switch", "LMICN"},
+	{"Left Input PGA", "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;
+}
+
+static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_priv *wm8978 = codec->private_data;
+	struct wm8978_pll_div pll_div;
+	unsigned int f2, opclk_div;
+	u16 reg;
+
+	if (freq_in = 0 || freq_out = 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8978_CLOCKING);
+		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
+		return 0;
+	}
+
+	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;
+
+	wm8978->f_pllout = freq_out * opclk_div;
+
+	f2 = wm8978->f_pllout * 4;
+
+	pll_factors(&pll_div, f2, freq_in);
+
+	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);
+
+	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);
+	/* Turn PLL on */
+	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8978_CLOCKING);
+	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
+
+	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;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8978_OPCLKDIV:
+		if (div & ~0x30)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & ~0x30;
+		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
+		/* Output PLL (OPCLK) to GPIO1 */
+		snd_soc_write(codec, WM8978_GPIO_CONTROL,
+			      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
+		break;
+	case WM8978_MCLKDIV:
+		if (div & ~0xe0)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & ~0xe0;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	case WM8978_ADCCLK:
+		if (div & ~8)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & ~8;
+		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
+		break;
+	case WM8978_DACCLK:
+		if (div & ~8)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & ~8;
+		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
+		break;
+	case WM8978_BCLKDIV:
+		if (div & ~0x1c)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & ~0x1c;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "%s: ID %d, value 0x%x\n",
+		__func__, div_id, reg | div);
+
+	return 0;
+}
+
+/*
+ * 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);
+
+	/* 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;
+	unsigned int f_sys;
+	int i;
+
+	/* 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;
+	}
+
+	/*
+	 * Calculate internal frequencies and dividers, according to Figure 40
+	 * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
+	 */
+	f_sys = params_rate(params) * 256;
+	if (f_sys > wm8978->f_pllout)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++)
+		if (wm8978->f_pllout * mclk_denominator[i] =
+		    mclk_numerator[i] * f_sys)
+			break;
+
+	if (i = ARRAY_SIZE(mclk_numerator))
+		return -EINVAL;
+
+	dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
+		params_format(params), params_rate(params), i);
+
+	/* MCLK divisor mask = 0xe0 */
+	snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, i << 5);
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+	return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
+
+	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+	if (mute)
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
+	else
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
+
+	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:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\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_pll	= wm8978_set_dai_pll,
+};
+
+/* 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);
+	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;
+	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);
+	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);
+
+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;
+	}
+
+	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..61e39c0
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,84 @@
+/*
+ * 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_OPCLKDIV,
+	WM8978_MCLKDIV,
+	WM8978_ADCCLK,
+	WM8978_DACCLK,
+	WM8978_BCLKDIV,
+};
+
+extern struct snd_soc_dai wm8978_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8978;
+
+#endif	/* __WM8978_H__ */

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
@ 2010-01-22 16:27     ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 16:27 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
is stereo and also has some differences in pin configuration and internal
signal routing. This driver is based on wm8974 and takes the differences into
account.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. changed HeadPhone to Headphone
2. using global simplified macros from two patches posted earlier
3. removed power-related operations from wm8978_hw_params()
4. register-cache: removed misleading comments, removed "update" bits, 
   they are set now in a dynamically-created copy
5. moved entries from wm8978_enum[] array into individual variables
6. adjusted control names
7. removed unused wm8978_aux_boost_controls[] and 
   wm8978_mic_boost_controls[]
8. moved setting GPIO1 to clock output to OPCLK configuration in 
   wm8978_set_dai_clkdiv()
9. multiple improvements to controls, widgets, audio-map to actually 
   enable automatic hardware configuration by DAPM
10. using SNDRV_PCM_RATE_8000_48000 now
11. removed "if (!codec->card)" tests
12. in wm8978_resume() only copying modified registers, using 
    snd_soc_write() instead of open-codying.

In short - addressed all comments (thanks a lot again!) - except one... I 
still would not like to use symbolic names for register bits. Instead I 
thoroughly commented all multi-bit register manipulations. Reasons:

1. thanks for the header, Mark, but unfortunately it contains errors 
   (duplicate register names, duplicate and wrong bitfield names)
2. the header also uses spaces for indentation, which would have to be 
   manually replaced with TABs
3. I am still not convinced, that macro names like WM8978_WL or 
   WM8978_DLRSWAP or WM8978_MS or... better describe the meaning of the 
   bitfield than respective comment in the source. Here's an example of a 
   comment from this patch:

	/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
	power1 |= 0xc;

   Where bit-field names do make sense, IMHO, is in drivers, where the 
   same bitfields have to be written / evaluated multiple times at 
   different locations. Than indeed giving those bits symbolic names helps 
   finding them. So, I'd like to request a permission to preserve the 
   present style of the driver.

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 62ff26a..0aad72f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS
 	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
@@ -230,6 +231,9 @@ config SND_SOC_WM8971
 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 ea98354..fbd290e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -44,6 +44,7 @@ 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
@@ -103,6 +104,7 @@ 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/wm8978.c b/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..1ca2377
--- /dev/null
+++ b/sound/soc/codecs/wm8978.c
@@ -0,0 +1,988 @@
+/*
+ * 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 */
+};
+
+static const int update_reg[] = {
+	0xb, 0xc, 0xf, 0x10, 0x2d, 0x2e, 0x34, 0x35, 0x36, 0x37
+};
+
+/* codec private data */
+struct wm8978_priv {
+	struct snd_soc_codec codec;
+	unsigned int f_pllout;
+	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_SINGLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 0),
+
+	SOC_SINGLE_TLV("Left PCM Volume",
+		WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+	SOC_SINGLE_TLV("Right PCM 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_SINGLE("Left ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 0),
+	SOC_SINGLE("Right ADC Inversion Switch", WM8978_ADC_CONTROL, 1, 1, 0),
+
+	SOC_SINGLE_TLV("Left ADC Volume",
+	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
+	SOC_SINGLE_TLV("Right ADC 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 Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+	SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 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_SINGLE("Left Capture PGA ZC Switch",
+		WM8978_LEFT_INP_PGA_CONTROL, 7, 1, 0),
+	SOC_SINGLE("Right Capture PGA ZC Switch",
+		WM8978_RIGHT_INP_PGA_CONTROL, 7, 1, 0),
+
+	/* OUT1 - Headphones */
+	SOC_SINGLE("Left Headphone Playback ZC Switch",
+		WM8978_LOUT1_HP_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Left Headphone Playback Volume",
+		WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+	SOC_SINGLE("Right Headphone Playback ZC Switch",
+		WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Right Headphone Playback Volume",
+		WM8978_ROUT1_HP_CONTROL, 0, 63, 0, spk_tlv),
+
+	/* OUT2 - Speakers */
+	SOC_SINGLE("Left Speaker Playback ZC Switch",
+		WM8978_LOUT2_SPK_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Left Speaker Playback Volume",
+		WM8978_LOUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+	SOC_SINGLE("Right Speaker Playback ZC Switch",
+		WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+	SOC_SINGLE_TLV("Right Speaker Playback Volume",
+		WM8978_ROUT2_SPK_CONTROL, 0, 63, 0, spk_tlv),
+
+	/* OUT3/4 - Line Output */
+	SOC_SINGLE("Left Line Playback Switch",
+		WM8978_OUT3_MIXER_CONTROL, 6, 1, 1),
+	SOC_SINGLE("Right Line Playback Switch",
+		WM8978_OUT4_MIXER_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_pga[] = {
+	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_pga[] = {
+	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),
+};
+
+/* Mixer #3: Boost (Input) mixer */
+static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
+
+	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE_TLV("L2 Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Aux Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv),
+};
+static const struct snd_kcontrol_new wm8978_right_boost_mixer[] = {
+
+	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE_TLV("R2 Boost Volume",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Aux Boost Volume",
+		WM8978_RIGHT_ADC_BOOST_CONTROL, 0, 7, 0, boost_tlv),
+};
+
+/* Input PGA volume */
+static const struct snd_kcontrol_new wm8978_left_input_pga_volume[] = {
+	SOC_DAPM_SINGLE_TLV("Volume",
+		WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+};
+static const struct snd_kcontrol_new wm8978_right_input_pga_volume[] = {
+	SOC_DAPM_SINGLE_TLV("Volume",
+		WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
+};
+
+/* Headphone */
+static const struct snd_kcontrol_new wm8978_left_hp_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
+};
+static const struct snd_kcontrol_new wm8978_right_hp_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+};
+
+/* Speaker */
+static const struct snd_kcontrol_new wm8978_left_speaker_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT2_SPK_CONTROL, 6, 1, 1),
+};
+static const struct snd_kcontrol_new wm8978_right_speaker_mute[] = {
+	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+};
+
+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 PGA", WM8978_POWER_MANAGEMENT_2, 2, 0,
+		   wm8978_left_input_pga),
+	SOC_MIXER_ARRAY("Right Input PGA", WM8978_POWER_MANAGEMENT_2, 3, 0,
+		   wm8978_right_input_pga),
+
+	SOC_MIXER_ARRAY("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, 4, 0,
+		   wm8978_left_boost_mixer),
+	SOC_MIXER_ARRAY("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, 5, 0,
+		   wm8978_right_boost_mixer),
+
+	SND_SOC_DAPM_SWITCH("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL, 6, 1,
+		   wm8978_left_input_pga_volume),
+	SND_SOC_DAPM_SWITCH("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL, 6, 1,
+		   wm8978_right_input_pga_volume),
+
+	SND_SOC_DAPM_SWITCH("Left Headphone Out", WM8978_POWER_MANAGEMENT_2, 7, 0,
+		   wm8978_left_hp_mute),
+	SND_SOC_DAPM_SWITCH("Right Headphone Out", WM8978_POWER_MANAGEMENT_2, 8, 0,
+		   wm8978_right_hp_mute),
+
+	SND_SOC_DAPM_SWITCH("Left Speaker Out", WM8978_POWER_MANAGEMENT_3, 6, 0,
+		   wm8978_left_speaker_mute),
+	SND_SOC_DAPM_SWITCH("Right Speaker Out", WM8978_POWER_MANAGEMENT_3, 5, 0,
+		   wm8978_right_speaker_mute),
+
+	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", "Mute Switch", "Right Output Mixer"},
+	{"RHP", NULL, "Right Headphone Out"},
+
+	{"Left Headphone Out", "Mute Switch", "Left Output Mixer"},
+	{"LHP", NULL, "Left Headphone Out"},
+
+	{"Right Speaker Out", "Mute Switch", "Right Output Mixer"},
+	{"RSPK", NULL, "Right Speaker Out"},
+
+	{"Left Speaker Out", "Mute Switch", "Left Output Mixer"},
+	{"LSPK", NULL, "Left Speaker Out"},
+
+	/* Boost Mixer */
+	{"Right ADC", NULL, "Right Boost Mixer"},
+
+	{"Right Boost Mixer", "Aux Boost Volume", "RAUX"},
+	{"Right Boost Mixer", "PGA Boost (+20dB)", "Right Capture PGA"},
+	{"Right Boost Mixer", "R2 Boost Volume", "R2"},
+
+	{"Left ADC", NULL, "Left Boost Mixer"},
+
+	{"Left Boost Mixer", "Aux Boost Volume", "LAUX"},
+	{"Left Boost Mixer", "PGA Boost (+20dB)", "Left Capture PGA"},
+	{"Left Boost Mixer", "L2 Boost Volume", "L2"},
+
+	/* Input PGA */
+	{"Right Capture PGA", "Volume", "Right Input PGA"},
+	{"Left Capture PGA", "Volume", "Left Input PGA"},
+
+	{"Right Input PGA", "R2 Switch", "R2"},
+	{"Right Input PGA", "MicN Switch", "RMICN"},
+	{"Right Input PGA", "MicP Switch", "RMICP"},
+
+	{"Left Input PGA", "L2 Switch", "L2"},
+	{"Left Input PGA", "MicN Switch", "LMICN"},
+	{"Left Input PGA", "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;
+}
+
+static int wm8978_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_priv *wm8978 = codec->private_data;
+	struct wm8978_pll_div pll_div;
+	unsigned int f2, opclk_div;
+	u16 reg;
+
+	if (freq_in == 0 || freq_out == 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8978_CLOCKING);
+		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
+		return 0;
+	}
+
+	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;
+
+	wm8978->f_pllout = freq_out * opclk_div;
+
+	f2 = wm8978->f_pllout * 4;
+
+	pll_factors(&pll_div, f2, freq_in);
+
+	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);
+
+	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);
+	/* Turn PLL on */
+	reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg | 0x020);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8978_CLOCKING);
+	snd_soc_write(codec, WM8978_CLOCKING, reg | 0x100);
+
+	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;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8978_OPCLKDIV:
+		if (div & ~0x30)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_GPIO_CONTROL) & ~0x30;
+		snd_soc_write(codec, WM8978_GPIO_CONTROL, reg | div);
+		/* Output PLL (OPCLK) to GPIO1 */
+		snd_soc_write(codec, WM8978_GPIO_CONTROL,
+			      snd_soc_read(codec, WM8978_GPIO_CONTROL) | 0x4);
+		break;
+	case WM8978_MCLKDIV:
+		if (div & ~0xe0)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & ~0xe0;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	case WM8978_ADCCLK:
+		if (div & ~8)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_ADC_CONTROL) & ~8;
+		snd_soc_write(codec, WM8978_ADC_CONTROL, reg | div);
+		break;
+	case WM8978_DACCLK:
+		if (div & ~8)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_DAC_CONTROL) & ~8;
+		snd_soc_write(codec, WM8978_DAC_CONTROL, reg | div);
+		break;
+	case WM8978_BCLKDIV:
+		if (div & ~0x1c)
+			return -EINVAL;
+		reg = snd_soc_read(codec, WM8978_CLOCKING) & ~0x1c;
+		snd_soc_write(codec, WM8978_CLOCKING, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "%s: ID %d, value 0x%x\n",
+		__func__, div_id, reg | div);
+
+	return 0;
+}
+
+/*
+ * 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);
+
+	/* 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;
+	unsigned int f_sys;
+	int i;
+
+	/* 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;
+	}
+
+	/*
+	 * Calculate internal frequencies and dividers, according to Figure 40
+	 * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
+	 */
+	f_sys = params_rate(params) * 256;
+	if (f_sys > wm8978->f_pllout)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++)
+		if (wm8978->f_pllout * mclk_denominator[i] ==
+		    mclk_numerator[i] * f_sys)
+			break;
+
+	if (i == ARRAY_SIZE(mclk_numerator))
+		return -EINVAL;
+
+	dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
+		params_format(params), params_rate(params), i);
+
+	/* MCLK divisor mask = 0xe0 */
+	snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, i << 5);
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+	return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val = snd_soc_read(codec, WM8978_DAC_CONTROL);
+
+	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+	if (mute)
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val | 0x40);
+	else
+		snd_soc_write(codec, WM8978_DAC_CONTROL, val & ~0x40);
+
+	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:
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 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, %u\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_pll	= wm8978_set_dai_pll,
+};
+
+/* 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);
+	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;
+	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);
+	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);
+
+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;
+	}
+
+	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..61e39c0
--- /dev/null
+++ b/sound/soc/codecs/wm8978.h
@@ -0,0 +1,84 @@
+/*
+ * 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_OPCLKDIV,
+	WM8978_MCLKDIV,
+	WM8978_ADCCLK,
+	WM8978_DACCLK,
+	WM8978_BCLKDIV,
+};
+
+extern struct snd_soc_dai wm8978_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8978;
+
+#endif	/* __WM8978_H__ */

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-22 16:27     ` Guennadi Liakhovetski
  (?)
@ 2010-01-22 17:39     ` Liam Girdwood
  -1 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-22 17:39 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Kuninori Morimoto, Mark Brown, Magnus Damm

On Fri, 2010-01-22 at 17:27 +0100, Guennadi Liakhovetski wrote:
> The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> is stereo and also has some differences in pin configuration and internal
> signal routing. This driver is based on wm8974 and takes the differences into
> account.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 

Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>

> 
> In short - addressed all comments (thanks a lot again!) - except one... I 
> still would not like to use symbolic names for register bits. Instead I 
> thoroughly commented all multi-bit register manipulations. Reasons:
> 
> 1. thanks for the header, Mark, but unfortunately it contains errors 
>    (duplicate register names, duplicate and wrong bitfield names)
> 2. the header also uses spaces for indentation, which would have to be 
>    manually replaced with TABs
> 3. I am still not convinced, that macro names like WM8978_WL or 
>    WM8978_DLRSWAP or WM8978_MS or... better describe the meaning of the 
>    bitfield than respective comment in the source. Here's an example of a 
>    comment from this patch:
> 
> 	/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
> 	power1 |= 0xc;
> 
>    Where bit-field names do make sense, IMHO, is in drivers, where the 
>    same bitfields have to be written / evaluated multiple times at 
>    different locations. Than indeed giving those bits symbolic names helps 
>    finding them. So, I'd like to request a permission to preserve the 
>    present style of the driver.
> 

I think we will have to agree to disagree on this one, but it can be
fixed at a later stage.

Liam


^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU
  2010-01-19  8:09   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Guennadi Liakhovetski
@ 2010-01-22 18:09     ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 18:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
drivers for this interface.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. splitted off board support into a separate patch
2. made SND_SOC_SH4_SIU a hidden Kconfig variable
3. SND_SOC_SH4_SIU now can be selected on other SuperH platforms, not only 
   on sh7722
4. moved many defines, that are only used in one of .c-files in those 
   files
5. prefixed remaining defines in the header with SIU_
6. clarification: "only fixed Left-upper, Left-lower, Right-upper, 
   Right-lower packing is supported" means - only this byte order is 
   hard-coded without support for other possible byte-orders
7. clk_put() doesn't mind getting an error code as its argument, but moved 
   it under the respective "if" just in case;)
8. removed mono-emulation via data-copying. Looking for a recipe to achive 
   this via alsa-configuration, anyone?

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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -26,6 +26,12 @@ config SND_SOC_SH4_FSI
 	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
 ##
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
index 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,9 +6,11 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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
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%6fs, portB%6fs */
+	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);

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU
@ 2010-01-22 18:09     ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 18:09 UTC (permalink / raw)
  To: alsa-devel
  Cc: linux-sh, Liam Girdwood, Kuninori Morimoto, Mark Brown, Magnus Damm

Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
drivers for this interface.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. splitted off board support into a separate patch
2. made SND_SOC_SH4_SIU a hidden Kconfig variable
3. SND_SOC_SH4_SIU now can be selected on other SuperH platforms, not only 
   on sh7722
4. moved many defines, that are only used in one of .c-files in those 
   files
5. prefixed remaining defines in the header with SIU_
6. clarification: "only fixed Left-upper, Left-lower, Right-upper, 
   Right-lower packing is supported" means - only this byte order is 
   hard-coded without support for other possible byte-orders
7. clk_put() doesn't mind getting an error code as its argument, but moved 
   it under the respective "if" just in case;)
8. removed mono-emulation via data-copying. Looking for a recipe to achive 
   this via alsa-configuration, anyone?

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/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -26,6 +26,12 @@ config SND_SOC_SH4_FSI
 	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
 ##
diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile
index 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,9 +6,11 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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
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);

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
  2010-01-19  8:09   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Guennadi Liakhovetski
@ 2010-01-22 18:17     ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 18:17 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
codec, recording via external microphone and playback via headphones are 
implemented.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. separated from core SIU patch
2. now selecting SND_SOC_SH4_SIU instead of depending on it
3. moved most of clocking calculations / configuration into the codec 
   driver, also removed confusing comments
4. refactored calculations, based on input frequency and output frequency 
   requirement, instead of internal codec knowledge
5. added datasheet revision - now in the actual codec driver
6. removed reconfiguring codec PLL in migor_hw_free() and consequently 
   removed migor_hw_free() altogether
7. removed hard-coded pin-enabling, now managed by DAPM
8. added widgets and an audio map to supportautomatic routing 
   configuration and other DAPM functions

diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -55,4 +61,12 @@ config SND_FSI_DA7210
 	  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 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,7 +6,9 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 0000000..cae832d
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,227 @@
+/*
+ * 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;
+
+/* 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;
+	unsigned int opclk_div;
+	int ret;
+	unsigned int rate = params_rate(params);
+
+	/* Configure f_out = rate * 512 */
+	switch (rate) {
+	case 48000:
+		opclk_div = 0;
+		break;
+	case 44100:
+		opclk_div = 0;
+		break;
+	case 32000:
+		opclk_div = 0x010;
+		break;
+	case 24000:
+		opclk_div = 0x010;
+		break;
+	case 22050:
+		opclk_div = 0x010;
+		break;
+	case 16000:
+		opclk_div = 0x020;
+		break;
+	case 11025:
+		opclk_div = 0x010;
+		break;
+	default:
+	case 8000:
+		opclk_div = 0x020;
+		break;
+	}
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
+	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_pll(codec_dai, 0, 0, 13000000, 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);
+
+	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, SIU_CLKB_EXT, codec_freq / 2,
+			       SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+	.hw_params = migor_hw_params,
+};
+
+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");

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
@ 2010-01-22 18:17     ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-22 18:17 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
codec, recording via external microphone and playback via headphones are 
implemented.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v1 -> v2:

1. separated from core SIU patch
2. now selecting SND_SOC_SH4_SIU instead of depending on it
3. moved most of clocking calculations / configuration into the codec 
   driver, also removed confusing comments
4. refactored calculations, based on input frequency and output frequency 
   requirement, instead of internal codec knowledge
5. added datasheet revision - now in the actual codec driver
6. removed reconfiguring codec PLL in migor_hw_free() and consequently 
   removed migor_hw_free() altogether
7. removed hard-coded pin-enabling, now managed by DAPM
8. added widgets and an audio map to supportautomatic routing 
   configuration and other DAPM functions

diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -55,4 +61,12 @@ config SND_FSI_DA7210
 	  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 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -6,7 +6,9 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/migor.c b/sound/soc/sh/migor.c
new file mode 100644
index 0000000..cae832d
--- /dev/null
+++ b/sound/soc/sh/migor.c
@@ -0,0 +1,227 @@
+/*
+ * 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;
+
+/* 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;
+	unsigned int opclk_div;
+	int ret;
+	unsigned int rate = params_rate(params);
+
+	/* Configure f_out = rate * 512 */
+	switch (rate) {
+	case 48000:
+		opclk_div = 0;
+		break;
+	case 44100:
+		opclk_div = 0;
+		break;
+	case 32000:
+		opclk_div = 0x010;
+		break;
+	case 24000:
+		opclk_div = 0x010;
+		break;
+	case 22050:
+		opclk_div = 0x010;
+		break;
+	case 16000:
+		opclk_div = 0x020;
+		break;
+	case 11025:
+		opclk_div = 0x010;
+		break;
+	default:
+	case 8000:
+		opclk_div = 0x020;
+		break;
+	}
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKDIV, opclk_div);
+	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_pll(codec_dai, 0, 0, 13000000, 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);
+
+	snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, SIU_CLKB_EXT, codec_freq / 2,
+			       SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+	.hw_params = migor_hw_params,
+};
+
+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");

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-22 16:27     ` Guennadi Liakhovetski
@ 2010-01-23 20:47       ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-23 20:47 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Fri, Jan 22, 2010 at 05:27:53PM +0100, Guennadi Liakhovetski wrote:

> The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> is stereo and also has some differences in pin configuration and internal
> signal routing. This driver is based on wm8974 and takes the differences into
> account.

> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

A few issues below remaining, mostly to do with the DAPM updates.

> 1. thanks for the header, Mark, but unfortunately it contains errors 
>    (duplicate register names, duplicate and wrong bitfield names)

Discussion on IRC revealed that this was a case of a small number of
typos and stuff that can be quickly fixed with indent :/

> 3. I am still not convinced, that macro names like WM8978_WL or 
>    WM8978_DLRSWAP or WM8978_MS or... better describe the meaning of the 
>    bitfield than respective comment in the source. Here's an example of a 
>    comment from this patch:

> 	/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
> 	power1 |= 0xc;

The comments and bitfield names do different things here; having words
is useful to explain what you're trying to accomplish while using
symbolic bitmasks is for connecting the bitbashing with the datasheet
when the datasheet names the bitfields.

> +static const int update_reg[] = {
> +	0xb, 0xc, 0xf, 0x10, 0x2d, 0x2e, 0x34, 0x35, 0x36, 0x37
> +};

It would be really helpful to use the symbolic names for the registers
here.  It'd also be nice to move this closer to the point where it's
used or have a comment explaining what will happen with it.

> +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);

Line wrapping please.

> +	SOC_SINGLE_TLV("Left PCM Volume",
> +		WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +	SOC_SINGLE_TLV("Right PCM Volume",
> +		WM8978_RIGHT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),

This should be a single stereo (_DOUBLE) control.

> +	SOC_SINGLE_TLV("Left ADC Volume",
> +	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +	SOC_SINGLE_TLV("Right ADC Volume",
> +	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),

As should this.

> +	SOC_SINGLE_TLV("Left Headphone Playback Volume",
> +		WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),

These also, and the speaker ones too.

> +/* Mixer #3: Boost (Input) mixer */
> +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> +
> +	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
> +		WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),

This isn't actually a mute so the connection to the PGA should (unless
there's something extra going on) be unconditional and this should be a
non-DAPM switch.

> +/* Input PGA volume */
> +static const struct snd_kcontrol_new wm8978_left_input_pga_volume[] = {
> +	SOC_DAPM_SINGLE_TLV("Volume",
> +		WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +};
> +static const struct snd_kcontrol_new wm8978_right_input_pga_volume[] = {
> +	SOC_DAPM_SINGLE_TLV("Volume",
> +		WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +};

It's just as well to take these off the PGAs, there's unlikely to be any
actual benefit from handling them with the PGAs on a modern CODEC.

> +/* Headphone */
> +static const struct snd_kcontrol_new wm8978_left_hp_mute[] = {
> +	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
> +};
> +static const struct snd_kcontrol_new wm8978_right_hp_mute[] = {
> +	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
> +};

Mute switches should always be just "Switch" - ALSA will map "Foo
Switch" and "Foo Volume" together to create a single control in the UI.

These (and the other output nodes) should really be stereo controls
outside of DAPM.  Managing them within DAPM as switches means that if
you mute the output then the path will be powered down.  This is
normally unhelpful since power up and down during playback can produce
audible effects for the listener such as pops and clicks or audible
volume ramps as the power comes on and off.  It's also possible that
some output will leak through when the output is powered down since
normally mutes on PGAs only work when they are powered.

> +	if (freq_in = 0 || freq_out = 0) {
> +		/* Clock CODEC directly from MCLK */
> +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> +
> +		/* Turn off PLL */
> +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> +		return 0;
> +	}

Note that ideally the PLL updates will handle the case where the PLL is
already on and power down the PLL while reconfiguring it, though it's
not essential.

> +	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;

> +	wm8978->f_pllout = freq_out * opclk_div;

Hrm.  I fear that there's a bit of confusion here - the PLL output is
used separately as the source for both OPCLK and the system clock for
the CODEC.  This means that the PLL output frequency that we need to
refer to later on is that before the OPCLK divide.

Looking further down the driver f_pllout is also being used as the
system master clock frequency, meaning that the driver will only work
when the PLL is in use.  Normally what would happen with this stuff is
that the system clock used by hw_params() will be set using the
set_sysclk() API call, which can tell the driver what the clock rate is
and also select between multiple clock sources.  You'd end up saying
something like:

	snd_soc_dai_set_pll(dai, 0, 0, 12000000, 256 * params_rate(params));
	snd_soc_dai_set_sysclk(dai, WM8978_PLL, 256 * params_rate(params), 0);

in the machine driver (note that this also means that the switchover to
use of the PLL as the clock would then be managed via set_sysclk() not
set_pll()).

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
@ 2010-01-23 20:47       ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-23 20:47 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Fri, Jan 22, 2010 at 05:27:53PM +0100, Guennadi Liakhovetski wrote:

> The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but
> is stereo and also has some differences in pin configuration and internal
> signal routing. This driver is based on wm8974 and takes the differences into
> account.

> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

A few issues below remaining, mostly to do with the DAPM updates.

> 1. thanks for the header, Mark, but unfortunately it contains errors 
>    (duplicate register names, duplicate and wrong bitfield names)

Discussion on IRC revealed that this was a case of a small number of
typos and stuff that can be quickly fixed with indent :/

> 3. I am still not convinced, that macro names like WM8978_WL or 
>    WM8978_DLRSWAP or WM8978_MS or... better describe the meaning of the 
>    bitfield than respective comment in the source. Here's an example of a 
>    comment from this patch:

> 	/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
> 	power1 |= 0xc;

The comments and bitfield names do different things here; having words
is useful to explain what you're trying to accomplish while using
symbolic bitmasks is for connecting the bitbashing with the datasheet
when the datasheet names the bitfields.

> +static const int update_reg[] = {
> +	0xb, 0xc, 0xf, 0x10, 0x2d, 0x2e, 0x34, 0x35, 0x36, 0x37
> +};

It would be really helpful to use the symbolic names for the registers
here.  It'd also be nice to move this closer to the point where it's
used or have a comment explaining what will happen with it.

> +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);

Line wrapping please.

> +	SOC_SINGLE_TLV("Left PCM Volume",
> +		WM8978_LEFT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +	SOC_SINGLE_TLV("Right PCM Volume",
> +		WM8978_RIGHT_DAC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),

This should be a single stereo (_DOUBLE) control.

> +	SOC_SINGLE_TLV("Left ADC Volume",
> +	       WM8978_LEFT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),
> +	SOC_SINGLE_TLV("Right ADC Volume",
> +	       WM8978_RIGHT_ADC_DIGITAL_VOLUME, 0, 255, 0, digital_tlv),

As should this.

> +	SOC_SINGLE_TLV("Left Headphone Playback Volume",
> +		WM8978_LOUT1_HP_CONTROL, 0, 63, 0, spk_tlv),

These also, and the speaker ones too.

> +/* Mixer #3: Boost (Input) mixer */
> +static const struct snd_kcontrol_new wm8978_left_boost_mixer[] = {
> +
> +	SOC_DAPM_SINGLE("PGA Boost (+20dB)",
> +		WM8978_LEFT_ADC_BOOST_CONTROL, 8, 1, 0),

This isn't actually a mute so the connection to the PGA should (unless
there's something extra going on) be unconditional and this should be a
non-DAPM switch.

> +/* Input PGA volume */
> +static const struct snd_kcontrol_new wm8978_left_input_pga_volume[] = {
> +	SOC_DAPM_SINGLE_TLV("Volume",
> +		WM8978_LEFT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +};
> +static const struct snd_kcontrol_new wm8978_right_input_pga_volume[] = {
> +	SOC_DAPM_SINGLE_TLV("Volume",
> +		WM8978_RIGHT_INP_PGA_CONTROL, 0, 63, 0, inpga_tlv),
> +};

It's just as well to take these off the PGAs, there's unlikely to be any
actual benefit from handling them with the PGAs on a modern CODEC.

> +/* Headphone */
> +static const struct snd_kcontrol_new wm8978_left_hp_mute[] = {
> +	SOC_DAPM_SINGLE("Mute Switch", WM8978_LOUT1_HP_CONTROL, 6, 1, 1),
> +};
> +static const struct snd_kcontrol_new wm8978_right_hp_mute[] = {
> +	SOC_DAPM_SINGLE("Mute Switch", WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
> +};

Mute switches should always be just "Switch" - ALSA will map "Foo
Switch" and "Foo Volume" together to create a single control in the UI.

These (and the other output nodes) should really be stereo controls
outside of DAPM.  Managing them within DAPM as switches means that if
you mute the output then the path will be powered down.  This is
normally unhelpful since power up and down during playback can produce
audible effects for the listener such as pops and clicks or audible
volume ramps as the power comes on and off.  It's also possible that
some output will leak through when the output is powered down since
normally mutes on PGAs only work when they are powered.

> +	if (freq_in == 0 || freq_out == 0) {
> +		/* Clock CODEC directly from MCLK */
> +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> +
> +		/* Turn off PLL */
> +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> +		return 0;
> +	}

Note that ideally the PLL updates will handle the case where the PLL is
already on and power down the PLL while reconfiguring it, though it's
not essential.

> +	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;

> +	wm8978->f_pllout = freq_out * opclk_div;

Hrm.  I fear that there's a bit of confusion here - the PLL output is
used separately as the source for both OPCLK and the system clock for
the CODEC.  This means that the PLL output frequency that we need to
refer to later on is that before the OPCLK divide.

Looking further down the driver f_pllout is also being used as the
system master clock frequency, meaning that the driver will only work
when the PLL is in use.  Normally what would happen with this stuff is
that the system clock used by hw_params() will be set using the
set_sysclk() API call, which can tell the driver what the clock rate is
and also select between multiple clock sources.  You'd end up saying
something like:

	snd_soc_dai_set_pll(dai, 0, 0, 12000000, 256 * params_rate(params));
	snd_soc_dai_set_sysclk(dai, WM8978_PLL, 256 * params_rate(params), 0);

in the machine driver (note that this also means that the switchover to
use of the PLL as the clock would then be managed via set_sysclk() not
set_pll()).

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
  2010-01-22 18:17     ` Guennadi Liakhovetski
  (?)
@ 2010-01-25 13:21     ` Mark Brown
  2010-01-25 13:47         ` [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board Liam Girdwood
  -1 siblings, 1 reply; 57+ messages in thread
From: Mark Brown @ 2010-01-25 13:21 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Liam Girdwood, Kuninori Morimoto, Magnus Damm

On Fri, Jan 22, 2010 at 07:17:09PM +0100, Guennadi Liakhovetski wrote:
> Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
> codec, recording via external microphone and playback via headphones are 
> implemented.

> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

This is fine for me but can't be applied yet due to the CODEC driver
dependency.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722
  2010-01-25 13:21     ` Mark Brown
@ 2010-01-25 13:47         ` Liam Girdwood
  0 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-25 13:47 UTC (permalink / raw)
  To: Mark Brown
  Cc: Guennadi Liakhovetski, Kuninori Morimoto, alsa-devel,
	Magnus Damm, linux-sh

On Mon, 2010-01-25 at 13:21 +0000, Mark Brown wrote:
> On Fri, Jan 22, 2010 at 07:17:09PM +0100, Guennadi Liakhovetski wrote:
> > Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
> > codec, recording via external microphone and playback via headphones are 
> > implemented.
> 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> 
> This is fine for me but can't be applied yet due to the CODEC driver
> dependency.

Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
@ 2010-01-25 13:47         ` Liam Girdwood
  0 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-25 13:47 UTC (permalink / raw)
  To: Mark Brown
  Cc: Guennadi Liakhovetski, Kuninori Morimoto, alsa-devel,
	Magnus Damm, linux-sh

On Mon, 2010-01-25 at 13:21 +0000, Mark Brown wrote:
> On Fri, Jan 22, 2010 at 07:17:09PM +0100, Guennadi Liakhovetski wrote:
> > Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
> > codec, recording via external microphone and playback via headphones are 
> > implemented.
> 
> > Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> 
> This is fine for me but can't be applied yet due to the CODEC driver
> dependency.

Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for
  2010-01-22 18:09     ` Guennadi Liakhovetski
@ 2010-01-25 13:58       ` Liam Girdwood
  -1 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-25 13:58 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Kuninori Morimoto, Mark Brown, Magnus Damm

On Fri, 2010-01-22 at 19:09 +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
> a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
> drivers for this interface.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> v1 -> v2:
> 
> 1. splitted off board support into a separate patch
> 2. made SND_SOC_SH4_SIU a hidden Kconfig variable
> 3. SND_SOC_SH4_SIU now can be selected on other SuperH platforms, not only 
>    on sh7722
> 4. moved many defines, that are only used in one of .c-files in those 
>    files
> 5. prefixed remaining defines in the header with SIU_
> 6. clarification: "only fixed Left-upper, Left-lower, Right-upper, 
>    Right-lower packing is supported" means - only this byte order is 
>    hard-coded without support for other possible byte-orders
> 7. clk_put() doesn't mind getting an error code as its argument, but moved 
>    it under the respective "if" just in case;)
> 8. removed mono-emulation via data-copying. Looking for a recipe to achive 
>    this via alsa-configuration, anyone?
> 

Acked-by:Liam Girdwood <lrg@slimlogic.co.uk>



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU
@ 2010-01-25 13:58       ` Liam Girdwood
  0 siblings, 0 replies; 57+ messages in thread
From: Liam Girdwood @ 2010-01-25 13:58 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Kuninori Morimoto, Mark Brown, Magnus Damm

On Fri, 2010-01-22 at 19:09 +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
> a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
> drivers for this interface.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
> 
> v1 -> v2:
> 
> 1. splitted off board support into a separate patch
> 2. made SND_SOC_SH4_SIU a hidden Kconfig variable
> 3. SND_SOC_SH4_SIU now can be selected on other SuperH platforms, not only 
>    on sh7722
> 4. moved many defines, that are only used in one of .c-files in those 
>    files
> 5. prefixed remaining defines in the header with SIU_
> 6. clarification: "only fixed Left-upper, Left-lower, Right-upper, 
>    Right-lower packing is supported" means - only this byte order is 
>    hard-coded without support for other possible byte-orders
> 7. clk_put() doesn't mind getting an error code as its argument, but moved 
>    it under the respective "if" just in case;)
> 8. removed mono-emulation via data-copying. Looking for a recipe to achive 
>    this via alsa-configuration, anyone?
> 

Acked-by:Liam Girdwood <lrg@slimlogic.co.uk>



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for
  2010-01-22 18:09     ` Guennadi Liakhovetski
@ 2010-01-25 15:06       ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-25 15:06 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Liam Girdwood, Kuninori Morimoto, Magnus Damm

On Fri, Jan 22, 2010 at 07:09:03PM +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
> a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
> drivers for this interface.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU
@ 2010-01-25 15:06       ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-25 15:06 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, linux-sh, Liam Girdwood, Kuninori Morimoto, Magnus Damm

On Fri, Jan 22, 2010 at 07:09:03PM +0100, Guennadi Liakhovetski wrote:
> Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include 
> a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA 
> drivers for this interface.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-23 20:47       ` Mark Brown
@ 2010-01-26 13:04         ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-26 13:04 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Sat, 23 Jan 2010, Mark Brown wrote:

> On Fri, Jan 22, 2010 at 05:27:53PM +0100, Guennadi Liakhovetski wrote:

[snip]

> > +	if (freq_in = 0 || freq_out = 0) {
> > +		/* Clock CODEC directly from MCLK */
> > +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> > +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> > +
> > +		/* Turn off PLL */
> > +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> > +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> > +		return 0;
> > +	}
> 
> Note that ideally the PLL updates will handle the case where the PLL is
> already on and power down the PLL while reconfiguring it, though it's
> not essential.
> 
> > +	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;
> 
> > +	wm8978->f_pllout = freq_out * opclk_div;
> 
> Hrm.  I fear that there's a bit of confusion here - the PLL output is
> used separately as the source for both OPCLK and the system clock for
> the CODEC.  This means that the PLL output frequency that we need to
> refer to later on is that before the OPCLK divide.
> 
> Looking further down the driver f_pllout is also being used as the
> system master clock frequency, meaning that the driver will only work
> when the PLL is in use.  Normally what would happen with this stuff is
> that the system clock used by hw_params() will be set using the
> set_sysclk() API call, which can tell the driver what the clock rate is
> and also select between multiple clock sources.  You'd end up saying
> something like:
> 
> 	snd_soc_dai_set_pll(dai, 0, 0, 12000000, 256 * params_rate(params));
> 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, 256 * params_rate(params), 0);
> 
> in the machine driver (note that this also means that the switchover to
> use of the PLL as the clock would then be managed via set_sysclk() not
> set_pll()).

Ok, my new concept is the following:

1. wm8978 needs an input clock for its operation, and it needs to know its 
   frequency. The only way to supply this information to the driver is to 
   use

	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);

   where if f_in = 0, input clock is off and the driver has nothing 
   better to do but to switch its own clocking off too. f_out is the 
   frequency, that the user (board) is asking wm8978 to provide on its 
   OPCLK (GPIO1) output. If f_out = 0, this means, the board does not 
   need that output _and_ it is asking the codec to switch PLL off. 
   Otherwise PLL is immediately configured and switched on.

2. having configured codec's PLL the board can select, which clock the 
   codec shall be using as a source for its system clock - PLL or codec 
   input clock directly. The board uses

	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);

   or

	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);

   for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
   fixed at 256 * fs, and therefore it has to be configured in 
   .hw_params().

3. in .hw_params() we configure the MCLK divider, based on the system 
   clock frequency and baudrate, and set up the selected clock source.

This allows for the most flexible clock configuration, one can even 
configure to use PLL for output clock and input clock directly for the 
system clock, even if such a configuration doesn't make much sense.

I'll be submitting an updated patch shortly, please, let me know if there 
are any problems with this concept.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
@ 2010-01-26 13:04         ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-26 13:04 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Sat, 23 Jan 2010, Mark Brown wrote:

> On Fri, Jan 22, 2010 at 05:27:53PM +0100, Guennadi Liakhovetski wrote:

[snip]

> > +	if (freq_in == 0 || freq_out == 0) {
> > +		/* Clock CODEC directly from MCLK */
> > +		reg = snd_soc_read(codec, WM8978_CLOCKING);
> > +		snd_soc_write(codec, WM8978_CLOCKING, reg & ~0x100);
> > +
> > +		/* Turn off PLL */
> > +		reg = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1);
> > +		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, reg & ~0x20);
> > +		return 0;
> > +	}
> 
> Note that ideally the PLL updates will handle the case where the PLL is
> already on and power down the PLL while reconfiguring it, though it's
> not essential.
> 
> > +	opclk_div = ((snd_soc_read(codec, WM8978_GPIO_CONTROL) & 0x30) >> 4) + 1;
> 
> > +	wm8978->f_pllout = freq_out * opclk_div;
> 
> Hrm.  I fear that there's a bit of confusion here - the PLL output is
> used separately as the source for both OPCLK and the system clock for
> the CODEC.  This means that the PLL output frequency that we need to
> refer to later on is that before the OPCLK divide.
> 
> Looking further down the driver f_pllout is also being used as the
> system master clock frequency, meaning that the driver will only work
> when the PLL is in use.  Normally what would happen with this stuff is
> that the system clock used by hw_params() will be set using the
> set_sysclk() API call, which can tell the driver what the clock rate is
> and also select between multiple clock sources.  You'd end up saying
> something like:
> 
> 	snd_soc_dai_set_pll(dai, 0, 0, 12000000, 256 * params_rate(params));
> 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, 256 * params_rate(params), 0);
> 
> in the machine driver (note that this also means that the switchover to
> use of the PLL as the clock would then be managed via set_sysclk() not
> set_pll()).

Ok, my new concept is the following:

1. wm8978 needs an input clock for its operation, and it needs to know its 
   frequency. The only way to supply this information to the driver is to 
   use

	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);

   where if f_in == 0, input clock is off and the driver has nothing 
   better to do but to switch its own clocking off too. f_out is the 
   frequency, that the user (board) is asking wm8978 to provide on its 
   OPCLK (GPIO1) output. If f_out == 0, this means, the board does not 
   need that output _and_ it is asking the codec to switch PLL off. 
   Otherwise PLL is immediately configured and switched on.

2. having configured codec's PLL the board can select, which clock the 
   codec shall be using as a source for its system clock - PLL or codec 
   input clock directly. The board uses

	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);

   or

	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);

   for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
   fixed at 256 * fs, and therefore it has to be configured in 
   .hw_params().

3. in .hw_params() we configure the MCLK divider, based on the system 
   clock frequency and baudrate, and set up the selected clock source.

This allows for the most flexible clock configuration, one can even 
configure to use PLL for output clock and input clock directly for the 
system clock, even if such a configuration doesn't make much sense.

I'll be submitting an updated patch shortly, please, let me know if there 
are any problems with this concept.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-26 13:04         ` Guennadi Liakhovetski
@ 2010-01-26 13:26           ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-26 13:26 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Tue, Jan 26, 2010 at 02:04:45PM +0100, Guennadi Liakhovetski wrote:

This is mostly good, there's a few relatively small changes needed but
the broad concept is fine:

> 1. wm8978 needs an input clock for its operation, and it needs to know its 
>    frequency. The only way to supply this information to the driver is to 
>    use
> 
> 	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);
> 
>    where if f_in = 0, input clock is off and the driver has nothing 
>    better to do but to switch its own clocking off too. f_out is the 
>    frequency, that the user (board) is asking wm8978 to provide on its 
>    OPCLK (GPIO1) output. If f_out = 0, this means, the board does not 
>    need that output _and_ it is asking the codec to switch PLL off. 
>    Otherwise PLL is immediately configured and switched on.

The PLL is not tied in with OPCLK.  set_pll() should pay no attention to
OPCLK, it should just start the PLL when given a configuration and stop
the PLL when given no input/output frequency.

Remember, OPCLK may not be being used at all and there are separate
dividers from the PLL output for it and the main SYSCLK in the CODEC.

> 2. having configured codec's PLL the board can select, which clock the 
>    codec shall be using as a source for its system clock - PLL or codec 
>    input clock directly. The board uses

> 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);

>    or
> 
> 	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);

This is mostly OK but...

>    for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
>    fixed at 256 * fs, and therefore it has to be configured in 
>    .hw_params().

If the MCLK is being used as SYSCLK then the frequency should be used -
if it's recorded then the driver can automatically configure MCLKDIV to
generate the 256fs by itself (see below).  It could also generate
constraints for ALSA to let applications know what frequencies are
supported, though that's less urgent.

The clock rate passed in needn't be the actual system clock, it's more
useful if it's the rate of the clock that is used to generate that,
which is what the outside world can see.

> 3. in .hw_params() we configure the MCLK divider, based on the system 
>    clock frequency and baudrate, and set up the selected clock source.

> This allows for the most flexible clock configuration, one can even 
> configure to use PLL for output clock and input clock directly for the 
> system clock, even if such a configuration doesn't make much sense.

This is why you need the input clock frequency when the MCLK is being
used directly - you can't configure the divider without knowing what
MCLK is.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
@ 2010-01-26 13:26           ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-26 13:26 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Tue, Jan 26, 2010 at 02:04:45PM +0100, Guennadi Liakhovetski wrote:

This is mostly good, there's a few relatively small changes needed but
the broad concept is fine:

> 1. wm8978 needs an input clock for its operation, and it needs to know its 
>    frequency. The only way to supply this information to the driver is to 
>    use
> 
> 	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);
> 
>    where if f_in == 0, input clock is off and the driver has nothing 
>    better to do but to switch its own clocking off too. f_out is the 
>    frequency, that the user (board) is asking wm8978 to provide on its 
>    OPCLK (GPIO1) output. If f_out == 0, this means, the board does not 
>    need that output _and_ it is asking the codec to switch PLL off. 
>    Otherwise PLL is immediately configured and switched on.

The PLL is not tied in with OPCLK.  set_pll() should pay no attention to
OPCLK, it should just start the PLL when given a configuration and stop
the PLL when given no input/output frequency.

Remember, OPCLK may not be being used at all and there are separate
dividers from the PLL output for it and the main SYSCLK in the CODEC.

> 2. having configured codec's PLL the board can select, which clock the 
>    codec shall be using as a source for its system clock - PLL or codec 
>    input clock directly. The board uses

> 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);

>    or
> 
> 	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);

This is mostly OK but...

>    for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
>    fixed at 256 * fs, and therefore it has to be configured in 
>    .hw_params().

If the MCLK is being used as SYSCLK then the frequency should be used -
if it's recorded then the driver can automatically configure MCLKDIV to
generate the 256fs by itself (see below).  It could also generate
constraints for ALSA to let applications know what frequencies are
supported, though that's less urgent.

The clock rate passed in needn't be the actual system clock, it's more
useful if it's the rate of the clock that is used to generate that,
which is what the outside world can see.

> 3. in .hw_params() we configure the MCLK divider, based on the system 
>    clock frequency and baudrate, and set up the selected clock source.

> This allows for the most flexible clock configuration, one can even 
> configure to use PLL for output clock and input clock directly for the 
> system clock, even if such a configuration doesn't make much sense.

This is why you need the input clock frequency when the MCLK is being
used directly - you can't configure the divider without knowing what
MCLK is.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-26 13:26           ` Mark Brown
@ 2010-01-26 14:08             ` Guennadi Liakhovetski
  -1 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-26 14:08 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Tue, 26 Jan 2010, Mark Brown wrote:

> On Tue, Jan 26, 2010 at 02:04:45PM +0100, Guennadi Liakhovetski wrote:
> 
> This is mostly good, there's a few relatively small changes needed but
> the broad concept is fine:
> 
> > 1. wm8978 needs an input clock for its operation, and it needs to know its 
> >    frequency. The only way to supply this information to the driver is to 
> >    use
> > 
> > 	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);
> > 
> >    where if f_in = 0, input clock is off and the driver has nothing 
> >    better to do but to switch its own clocking off too. f_out is the 
> >    frequency, that the user (board) is asking wm8978 to provide on its 
> >    OPCLK (GPIO1) output. If f_out = 0, this means, the board does not 
> >    need that output _and_ it is asking the codec to switch PLL off. 
> >    Otherwise PLL is immediately configured and switched on.
> 
> The PLL is not tied in with OPCLK.  set_pll() should pay no attention to
> OPCLK, it should just start the PLL when given a configuration and stop
> the PLL when given no input/output frequency.
> 
> Remember, OPCLK may not be being used at all and there are separate
> dividers from the PLL output for it and the main SYSCLK in the CODEC.
> 
> > 2. having configured codec's PLL the board can select, which clock the 
> >    codec shall be using as a source for its system clock - PLL or codec 
> >    input clock directly. The board uses
> 
> > 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);
> 
> >    or
> > 
> > 	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);
> 
> This is mostly OK but...
> 
> >    for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
> >    fixed at 256 * fs, and therefore it has to be configured in 
> >    .hw_params().
> 
> If the MCLK is being used as SYSCLK then the frequency should be used -
> if it's recorded then the driver can automatically configure MCLKDIV to
> generate the 256fs by itself (see below).  It could also generate
> constraints for ALSA to let applications know what frequencies are
> supported, though that's less urgent.
> 
> The clock rate passed in needn't be the actual system clock, it's more
> useful if it's the rate of the clock that is used to generate that,
> which is what the outside world can see.
> 
> > 3. in .hw_params() we configure the MCLK divider, based on the system 
> >    clock frequency and baudrate, and set up the selected clock source.
> 
> > This allows for the most flexible clock configuration, one can even 
> > configure to use PLL for output clock and input clock directly for the 
> > system clock, even if such a configuration doesn't make much sense.
> 
> This is why you need the input clock frequency when the MCLK is being
> used directly - you can't configure the divider without knowing what
> MCLK is.

Ok, here's how wm8978 clocking works, when PLL is used to generate the 
system clock:

            ___________                ____________
 f_MCLK    !           !  f_PLLOUT    !            ! f_sys = 256fs
---------->! x PLL_mul !------+------>! / MCLK_div !--------------->
           !___________!      !       !____________!
                              !
                              !
            _____________     !
 f_OPCLK   !             !    !
<----------! / OPCLK_div !----+
           !_____________!


f_PLLOUT = f_MCLK * PLL_mul

f_sys = 256fs = f_PLLOUT / MCLK_div

f_OPCLK = f_PLLOUT / OPCLK_div

here f_MCLK is what is fed into the codec, f_OPCLK is the master clock, 
that the codec is generating for the outside world, PLL_mul, MCLK_div, and 
OPCLK_div are internal coefficients, that have to be configured. So, there 
are 3 equations with 4 unknowns: f_PLLOUT, PLL_mul, MCLK_div, and 
OPCLK_div. Since there are additional constraints on all of them, there 
are usually not too many possible solutions. Still, this system is not 
uniquely resolvable and you have to use some heuristics to select a 
specific solution.

Now, Mark, you are suggesting, that the board code should call

	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);

with f_out = f_PLLOUT. This means, the board has to use its own heuristics 
to configure the codec, which I find a bad idea. Especially since there is 
not a single simple recipe like "f_PLLOUT = f_OPCLK, OPCLK_div = 1," you 
really have to vary all those variables to get to a suitable result. Now, 
I can do this. This was actually done in the original code:

	switch (rate) {
	case 48000:
		mclk_div = 0x40;
		opclk_div = 0;
		/* f2 = 98304000, was 98304050 */
		break;
	case 44100:
		mclk_div = 0x40;
		opclk_div = 0;
		/* f2 = 90316800, was 90317500 */
		break;
	case 32000:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 131072000, was 131072500 */
		break;
	case 24000:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 98304000, was 98304700 */
		break;
	case 22050:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 90316800, was 90317500 */
		break;
	case 16000:
		mclk_div = 0xa0;
		opclk_div = 0x020;
		/* f2 = 98304000, was 98304700 */
		break;
	case 11025:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 45158400, was 45158752 */
		break;
	default:
	case 8000:
		mclk_div = 0xa0;
		opclk_div = 0x020;
		/* f2 = 49152000, was 49152350 */
		break;
	}

You remember those superfluous "was-comments," that we have removed. See, 
the board driver chooses an OPCLK_div, and, thereby, f_PLLOUT. And this is 
exactly what I disliked about that code and was quite happy to remove. If 
you insist, I will put it back and switch to using f_PLLOUT for 
.set_pll(), but I emphasise, that I really dislike this approach, and I 
really believe, that the board really should keep its fingers away from 
devices' internals - ever. The only thing that the board knows is input 
clock, required output clock and sampling rate. The rest should be solely 
a matter of specific device drivers.

Now, please, let's make a decision and I'll just obey.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
@ 2010-01-26 14:08             ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-26 14:08 UTC (permalink / raw)
  To: Mark Brown
  Cc: Kuninori Morimoto, alsa-devel, linux-sh, Magnus Damm, Liam Girdwood

On Tue, 26 Jan 2010, Mark Brown wrote:

> On Tue, Jan 26, 2010 at 02:04:45PM +0100, Guennadi Liakhovetski wrote:
> 
> This is mostly good, there's a few relatively small changes needed but
> the broad concept is fine:
> 
> > 1. wm8978 needs an input clock for its operation, and it needs to know its 
> >    frequency. The only way to supply this information to the driver is to 
> >    use
> > 
> > 	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);
> > 
> >    where if f_in == 0, input clock is off and the driver has nothing 
> >    better to do but to switch its own clocking off too. f_out is the 
> >    frequency, that the user (board) is asking wm8978 to provide on its 
> >    OPCLK (GPIO1) output. If f_out == 0, this means, the board does not 
> >    need that output _and_ it is asking the codec to switch PLL off. 
> >    Otherwise PLL is immediately configured and switched on.
> 
> The PLL is not tied in with OPCLK.  set_pll() should pay no attention to
> OPCLK, it should just start the PLL when given a configuration and stop
> the PLL when given no input/output frequency.
> 
> Remember, OPCLK may not be being used at all and there are separate
> dividers from the PLL output for it and the main SYSCLK in the CODEC.
> 
> > 2. having configured codec's PLL the board can select, which clock the 
> >    codec shall be using as a source for its system clock - PLL or codec 
> >    input clock directly. The board uses
> 
> > 	snd_soc_dai_set_sysclk(dai, WM8978_PLL, f_sysclk, 0);
> 
> >    or
> > 
> > 	snd_soc_dai_set_sysclk(dai, WM8978_MCLK, f_sysclk, 0);
> 
> This is mostly OK but...
> 
> >    for this. Notice, that f_sysclk is ignored, because in wm8978 it is 
> >    fixed at 256 * fs, and therefore it has to be configured in 
> >    .hw_params().
> 
> If the MCLK is being used as SYSCLK then the frequency should be used -
> if it's recorded then the driver can automatically configure MCLKDIV to
> generate the 256fs by itself (see below).  It could also generate
> constraints for ALSA to let applications know what frequencies are
> supported, though that's less urgent.
> 
> The clock rate passed in needn't be the actual system clock, it's more
> useful if it's the rate of the clock that is used to generate that,
> which is what the outside world can see.
> 
> > 3. in .hw_params() we configure the MCLK divider, based on the system 
> >    clock frequency and baudrate, and set up the selected clock source.
> 
> > This allows for the most flexible clock configuration, one can even 
> > configure to use PLL for output clock and input clock directly for the 
> > system clock, even if such a configuration doesn't make much sense.
> 
> This is why you need the input clock frequency when the MCLK is being
> used directly - you can't configure the divider without knowing what
> MCLK is.

Ok, here's how wm8978 clocking works, when PLL is used to generate the 
system clock:

            ___________                ____________
 f_MCLK    !           !  f_PLLOUT    !            ! f_sys = 256fs
---------->! x PLL_mul !------+------>! / MCLK_div !--------------->
           !___________!      !       !____________!
                              !
                              !
            _____________     !
 f_OPCLK   !             !    !
<----------! / OPCLK_div !----+
           !_____________!


f_PLLOUT = f_MCLK * PLL_mul

f_sys = 256fs = f_PLLOUT / MCLK_div

f_OPCLK = f_PLLOUT / OPCLK_div

here f_MCLK is what is fed into the codec, f_OPCLK is the master clock, 
that the codec is generating for the outside world, PLL_mul, MCLK_div, and 
OPCLK_div are internal coefficients, that have to be configured. So, there 
are 3 equations with 4 unknowns: f_PLLOUT, PLL_mul, MCLK_div, and 
OPCLK_div. Since there are additional constraints on all of them, there 
are usually not too many possible solutions. Still, this system is not 
uniquely resolvable and you have to use some heuristics to select a 
specific solution.

Now, Mark, you are suggesting, that the board code should call

	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);

with f_out = f_PLLOUT. This means, the board has to use its own heuristics 
to configure the codec, which I find a bad idea. Especially since there is 
not a single simple recipe like "f_PLLOUT = f_OPCLK, OPCLK_div = 1," you 
really have to vary all those variables to get to a suitable result. Now, 
I can do this. This was actually done in the original code:

	switch (rate) {
	case 48000:
		mclk_div = 0x40;
		opclk_div = 0;
		/* f2 = 98304000, was 98304050 */
		break;
	case 44100:
		mclk_div = 0x40;
		opclk_div = 0;
		/* f2 = 90316800, was 90317500 */
		break;
	case 32000:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 131072000, was 131072500 */
		break;
	case 24000:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 98304000, was 98304700 */
		break;
	case 22050:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 90316800, was 90317500 */
		break;
	case 16000:
		mclk_div = 0xa0;
		opclk_div = 0x020;
		/* f2 = 98304000, was 98304700 */
		break;
	case 11025:
		mclk_div = 0x80;
		opclk_div = 0x010;
		/* f2 = 45158400, was 45158752 */
		break;
	default:
	case 8000:
		mclk_div = 0xa0;
		opclk_div = 0x020;
		/* f2 = 49152000, was 49152350 */
		break;
	}

You remember those superfluous "was-comments," that we have removed. See, 
the board driver chooses an OPCLK_div, and, thereby, f_PLLOUT. And this is 
exactly what I disliked about that code and was quite happy to remove. If 
you insist, I will put it back and switch to using f_PLLOUT for 
.set_pll(), but I emphasise, that I really dislike this approach, and I 
really believe, that the board really should keep its fingers away from 
devices' internals - ever. The only thing that the board knows is input 
clock, required output clock and sampling rate. The rest should be solely 
a matter of specific device drivers.

Now, please, let's make a decision and I'll just obey.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 1/4 v2] ASoC: add a WM8978 codec driver
  2010-01-26 14:08             ` Guennadi Liakhovetski
  (?)
@ 2010-01-26 15:22             ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-26 15:22 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Tue, Jan 26, 2010 at 03:08:44PM +0100, Guennadi Liakhovetski wrote:

> here f_MCLK is what is fed into the codec, f_OPCLK is the master clock, 
> that the codec is generating for the outside world, PLL_mul, MCLK_div, and 
> OPCLK_div are internal coefficients, that have to be configured. So, there 
> are 3 equations with 4 unknowns: f_PLLOUT, PLL_mul, MCLK_div, and 
> OPCLK_div. Since there are additional constraints on all of them, there 
> are usually not too many possible solutions. Still, this system is not 
> uniquely resolvable and you have to use some heuristics to select a 
> specific solution.

Right, the CODEC driver needs something to tell it what frequency to
generate from both the PLL and OPCLK.  Given the PLL configuration it
can work out the CODEC-side bits of things but it has no idea what's
going on with OPCLK or how that is related to the PLL.

> Now, Mark, you are suggesting, that the board code should call

> 	snd_soc_dai_set_pll(dai, 0, 0, f_in, f_out);

> with f_out = f_PLLOUT. This means, the board has to use its own heuristics 
> to configure the codec, which I find a bad idea. Especially since there is 
> not a single simple recipe like "f_PLLOUT = f_OPCLK, OPCLK_div = 1," you 
> really have to vary all those variables to get to a suitable result. Now, 

The CODEC only needs to know OPCLKDIV and the PLL output frequency.  It
doesn't really care that these are related.  There's no way it can know
what the requirements are for OPCLK, when it's safe to vary it or in
what way it's safe to vary it - reconfiguring the PLL requires that the
PLL be stopped, which may be undesirable.  The CODEC driver can't assume
that OPCLK is only needed when the CODEC is handling audio, and 

There's also the fact that set_pll() normally does exactly that - it
configures the PLL.  If you start making it do other magic 

> You remember those superfluous "was-comments," that we have removed. See, 
> the board driver chooses an OPCLK_div, and, thereby, f_PLLOUT. And this is 

I'm not sure how those comments are directly related to this issue?

> exactly what I disliked about that code and was quite happy to remove. If 
> you insist, I will put it back and switch to using f_PLLOUT for 
> .set_pll(), but I emphasise, that I really dislike this approach, and I 
> really believe, that the board really should keep its fingers away from 
> devices' internals - ever. The only thing that the board knows is input 
> clock, required output clock and sampling rate. The rest should be solely 
> a matter of specific device drivers.

The machine driver already needs to know about the clocking scheme for
the system - at the end of the day it's entirely specific to the
combination of CODEC, CPU, whatever other components are there, the
way they've been wired up and anything system level that software has to
be able to achieve.  Solving this sort of problem in a generic fashion
is non-trivial, there's a reason why we don't have an algorithm for it
hidden away in a generic clock API.

If you want to try to do this automatically I'd say that at a bare
minimum you need to remove set_pll() from the driver API and make
configuring OPCLK a bolt on which is not part of the standard flow for
using the device with the PLL.  Use set_sysclk() to specify the input
frequency for the PLL, and use something like the set_clkdiv() API to
specify the OPCLK output rate.

Your previous patches have all treated the PLL output frequency and
OPCLK frequency too interchangibly (or documented themselves as doing
so) - if the normal flow is worrying about what OPCLK is or if the
system clock is being stored as OPCLK something is wrong.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
@ 2010-01-27 11:15       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-27 11:15 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
codec, recording via external microphone and playback via headphones are 
implemented.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v2 -> v3:

1. adjusted to the new wm8978 clocking scheme
2. added .hw_free() to switch off PLL

diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -55,4 +61,12 @@ config SND_FSI_DA7210
 	  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 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -16,7 +16,9 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/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");

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
@ 2010-01-27 11:15       ` Guennadi Liakhovetski
  0 siblings, 0 replies; 57+ messages in thread
From: Guennadi Liakhovetski @ 2010-01-27 11:15 UTC (permalink / raw)
  To: alsa-devel
  Cc: Kuninori Morimoto, Magnus Damm, Liam Girdwood, Mark Brown, linux-sh

Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
codec, recording via external microphone and playback via headphones are 
implemented.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---

v2 -> v3:

1. adjusted to the new wm8978 clocking scheme
2. added .hw_free() to switch off PLL

diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig
index 8072a6d..a86696b 100644
--- a/sound/soc/sh/Kconfig
+++ b/sound/soc/sh/Kconfig
@@ -55,4 +61,12 @@ config SND_FSI_DA7210
 	  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 1d0ec0a..8a5a192 100644
--- a/sound/soc/sh/Makefile
+++ b/sound/soc/sh/Makefile
@@ -16,7 +16,9 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
 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/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");

^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722
  2010-01-27 11:15       ` Guennadi Liakhovetski
@ 2010-01-29 14:13         ` Mark Brown
  -1 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-29 14:13 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Wed, Jan 27, 2010 at 12:15:00PM +0100, Guennadi Liakhovetski wrote:
> Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
> codec, recording via external microphone and playback via headphones are 
> implemented.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board
@ 2010-01-29 14:13         ` Mark Brown
  0 siblings, 0 replies; 57+ messages in thread
From: Mark Brown @ 2010-01-29 14:13 UTC (permalink / raw)
  To: Guennadi Liakhovetski
  Cc: alsa-devel, Kuninori Morimoto, Magnus Damm, Liam Girdwood, linux-sh

On Wed, Jan 27, 2010 at 12:15:00PM +0100, Guennadi Liakhovetski wrote:
> Add support for audio on sh7722-based Migo-R boards, using SIU and wm8978 
> codec, recording via external microphone and playback via headphones are 
> implemented.
> 
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 57+ messages in thread

end of thread, other threads:[~2010-01-29 14:13 UTC | newest]

Thread overview: 57+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-01-19  8:08 [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio Guennadi Liakhovetski
2010-01-19  8:08 ` [PATCH 0/4] ALSA: SH: add ASoC driver for SIU audio engine, an audio codec and platform support Guennadi Liakhovetski
2010-01-19  8:08 ` [PATCH 1/4] ASoC: add a WM8978 codec driver Guennadi Liakhovetski
2010-01-19  8:08   ` Guennadi Liakhovetski
2010-01-19 10:46   ` [alsa-devel] " Liam Girdwood
2010-01-19 10:46     ` Liam Girdwood
2010-01-20 20:01     ` Guennadi Liakhovetski
2010-01-20 20:01       ` Guennadi Liakhovetski
2010-01-20 20:21       ` [alsa-devel] " Mark Brown
2010-01-20 20:21         ` Mark Brown
2010-01-20 20:25       ` [alsa-devel] " Liam Girdwood
2010-01-19 10:57   ` Mark Brown
     [not found]   ` <20100119105729.GA32559@opensource.wolfsonmicro.com::587>
2010-01-20 19:50     ` Guennadi Liakhovetski
2010-01-20 19:50       ` Guennadi Liakhovetski
2010-01-20 20:17       ` [alsa-devel] " Mark Brown
2010-01-20 20:17         ` Mark Brown
2010-01-22  8:35         ` [alsa-devel] " Guennadi Liakhovetski
2010-01-22  8:35           ` Guennadi Liakhovetski
2010-01-22 10:35           ` Mark Brown
2010-01-22 10:35             ` Mark Brown
2010-01-22 16:27   ` [PATCH 1/4 v2] " Guennadi Liakhovetski
2010-01-22 16:27     ` Guennadi Liakhovetski
2010-01-22 17:39     ` Liam Girdwood
2010-01-23 20:47     ` [alsa-devel] " Mark Brown
2010-01-23 20:47       ` Mark Brown
2010-01-26 13:04       ` Guennadi Liakhovetski
2010-01-26 13:04         ` Guennadi Liakhovetski
2010-01-26 13:26         ` Mark Brown
2010-01-26 13:26           ` Mark Brown
2010-01-26 14:08           ` [alsa-devel] " Guennadi Liakhovetski
2010-01-26 14:08             ` Guennadi Liakhovetski
2010-01-26 15:22             ` [alsa-devel] " Mark Brown
2010-01-19  8:09 ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support Guennadi Liakhovetski
2010-01-19  8:09   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Guennadi Liakhovetski
2010-01-19 11:13   ` [alsa-devel] [PATCH 2/4] ASoC: add DAI and platform drivers Liam Girdwood
2010-01-19 11:13     ` [alsa-devel] [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Liam Girdwood
2010-01-19 12:34   ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and Mark Brown
2010-01-19 12:34     ` [PATCH 2/4] ASoC: add DAI and platform drivers for SH SIU and support for the Migo-R board Mark Brown
2010-01-22 18:09   ` [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU Guennadi Liakhovetski
2010-01-22 18:09     ` Guennadi Liakhovetski
2010-01-25 13:58     ` [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for Liam Girdwood
2010-01-25 13:58       ` [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU Liam Girdwood
2010-01-25 15:06     ` [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for Mark Brown
2010-01-25 15:06       ` [PATCH 2a/4 v2] ASoC: add DAI and platform / DMA drivers for SH SIU Mark Brown
2010-01-22 18:17   ` [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board Guennadi Liakhovetski
2010-01-22 18:17     ` Guennadi Liakhovetski
2010-01-25 13:21     ` Mark Brown
2010-01-25 13:47       ` [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Liam Girdwood
2010-01-25 13:47         ` [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board Liam Girdwood
2010-01-27 11:15     ` Guennadi Liakhovetski
2010-01-27 11:15       ` Guennadi Liakhovetski
2010-01-29 14:13       ` [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Mark Brown
2010-01-29 14:13         ` [alsa-devel] [PATCH 2b/4 v2] ASoC: add support for the sh7722 Migo-R board Mark Brown
2010-01-19  8:09 ` [PATCH 3/4] sh: add DMA slave definitions and SIU platform data to Guennadi Liakhovetski
2010-01-19  8:09   ` [PATCH 3/4] sh: add DMA slave definitions and SIU platform data to sh7722 setup Guennadi Liakhovetski
2010-01-19  8:09 ` [PATCH 4/4] sh: audio support for the sh7722 Migo-R board Guennadi Liakhovetski
2010-01-19  8:09   ` Guennadi Liakhovetski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.