aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorOla Lilja <ola.o.lilja@stericsson.com>2010-01-20 11:37:28 +0100
committerJohn Rigby <john.rigby@linaro.org>2010-09-02 22:43:43 -0600
commit9dcf1ca851c3a18c8fe4e586ea848b85a8ba9d45 (patch)
tree327e01af4d0dc8123657712ae967956b2dae777b /sound
parente0c67890aa1b309162559414514f4b090fbbb57e (diff)
New ASoC driver for Fairbanks.
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig2
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/codecs/Kconfig7
-rw-r--r--sound/soc/codecs/Makefile2
-rwxr-xr-xsound/soc/codecs/ab3550.c1011
-rwxr-xr-xsound/soc/codecs/ab3550.h291
-rwxr-xr-xsound/soc/u8500/Kconfig13
-rwxr-xr-xsound/soc/u8500/Makefile12
-rwxr-xr-xsound/soc/u8500/mop500_ab3550.c170
-rwxr-xr-xsound/soc/u8500/u8500_msp_dai.c504
-rwxr-xr-xsound/soc/u8500/u8500_msp_dai.h25
-rwxr-xr-xsound/soc/u8500/u8500_pcm.c424
-rwxr-xr-xsound/soc/u8500/u8500_pcm.h53
13 files changed, 2514 insertions, 1 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index b1749bc6797..6bd98196009 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -5,7 +5,6 @@
menuconfig SND_SOC
tristate "ALSA for SoC audio support"
select SND_PCM
- select AC97_BUS if SND_SOC_AC97_BUS
select SND_JACK if INPUT=y || INPUT=SND
---help---
@@ -36,6 +35,7 @@ source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/u8500/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 1470141d416..beefeec88e6 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += u8500/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 1743d565e99..2b5b0682ec1 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -260,6 +260,13 @@ config SND_SOC_WM9712
config SND_SOC_WM9713
tristate
+config SND_SOC_AB3550
+ tristate "ST-Ericsson AB3550 Mixed Signal Circuit CODEC"
+ depends on SND_SOC_U8500 && MFD_AB3550_CORE
+ default m
+ help
+ Select this to support the AB3550 codec block.
+
# Amp
config SND_SOC_MAX9877
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index dd5ce6df629..962bfe107d8 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -1,3 +1,4 @@
+snd-soc-ab3550-objs := ab3550.o
snd-soc-ac97-objs := ac97.o
snd-soc-ad1836-objs := ad1836.o
snd-soc-ad1938-objs := ad1938.o
@@ -60,6 +61,7 @@ snd-soc-max9877-objs := max9877.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
snd-soc-wm2000-objs := wm2000.o
+obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-ab3550.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o
diff --git a/sound/soc/codecs/ab3550.c b/sound/soc/codecs/ab3550.c
new file mode 100755
index 00000000000..94f906c3fda
--- /dev/null
+++ b/sound/soc/codecs/ab3550.c
@@ -0,0 +1,1011 @@
+/*
+ * ab3550 -- ST-Ericsson Mixed Signaling ASIC
+ *
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/mfd/abx.h>
+#include <linux/bitops.h>
+#include "ab3550.h"
+
+#include "../u8500/u8500_pcm.h"
+#include "../u8500/u8500_msp_dai.h"
+
+#define I2C_BANK 0
+
+static struct abx_dev *ab3550_dev = NULL;
+
+#define SET_REG(reg, val) abx_set_register_interruptible( \
+ ab3550_dev, I2C_BANK, (reg), (val))
+
+#define MASK_SET_REG(reg, mask, val) abx_mask_and_set_register_interruptible( \
+ ab3550_dev, I2C_BANK, (reg), (mask), (val))
+
+#define GET_REG(reg, val) abx_get_register_interruptible( \
+ ab3550_dev, I2C_BANK, (reg), (val))
+
+#define AB3550_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define AB3550_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static const u8 outamp_reg[] = {
+ LINE1, LINE2, SPKR, EAR, AUXO1, AUXO2
+};
+static const u8 outamp_gain_shift[] = {
+ LINEx_GAIN_SHIFT, LINEx_GAIN_SHIFT,
+ SPKR_GAIN_SHIFT, EAR_GAIN_SHIFT,
+ AUXOx_GAIN_SHIFT, AUXOx_GAIN_SHIFT
+};
+static const u8 outamp_gain_mask[] = {
+ LINEx_GAIN_MASK, LINEx_GAIN_MASK,
+ SPKR_GAIN_MASK, EAR_GAIN_MASK,
+ AUXOx_GAIN_MASK, AUXOx_GAIN_MASK
+};
+static const u8 outamp_pwr_shift[] = {
+ LINEx_PWR_SHIFT, LINEx_PWR_SHIFT,
+ SPKR_PWR_SHIFT, EAR_PWR_SHIFT,
+ AUXOx_PWR_SHIFT, AUXOx_PWR_SHIFT
+};
+static const u8 outamp_pwr_mask[] = {
+ LINEx_PWR_MASK, LINEx_PWR_MASK,
+ SPKR_PWR_MASK, EAR_PWR_MASK,
+ AUXOx_PWR_MASK, AUXOx_PWR_MASK
+};
+
+static int ab3550_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai* dai);
+static int ab3550_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params, struct snd_soc_dai* dai);
+static void ab3550_pcm_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai* dai);
+
+static int ab3550_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id,
+ unsigned int freq, int dir);
+static int ab3550_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt);
+
+struct snd_soc_dai ab3550_codec_dai[] = {
+ {
+ .name = "ab3550_0",
+ .playback = {
+ .stream_name = "ab3550_0",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab3550_0",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .ops = {
+ .prepare = ab3550_pcm_prepare,
+ .hw_params = ab3550_pcm_hw_params,
+ .shutdown = ab3550_pcm_shutdown,
+ .set_sysclk = ab3550_set_dai_sysclk,
+ .set_fmt = ab3550_set_dai_fmt
+ },
+ },
+ {
+ .name = "ab3550_1",
+ .playback = {
+ .stream_name = "ab3550_1",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab3550_1",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .ops = {
+ .prepare = ab3550_pcm_prepare,
+ .hw_params = ab3550_pcm_hw_params,
+ .shutdown = ab3550_pcm_shutdown,
+ .set_sysclk = ab3550_set_dai_sysclk,
+ .set_fmt = ab3550_set_dai_fmt
+ },
+ }
+};
+EXPORT_SYMBOL_GPL(ab3550_codec_dai);
+
+struct ab3550_control {
+ const char *name;
+ u8 value;
+ int is_enum;
+};
+
+/* When AB3550 starts up, these registers are initialized to 0 */
+struct ab3550_control ab3550_ctl[] = {
+ {"RX2 Select", 0, 1},
+ {"DAC1 Routing", 0, 0},
+ {"DAC2 Routing", 0, 0},
+ {"DAC3 Routing", 0, 0},
+ {"MIC1 Input Select", 0, 0},
+ {"MIC2 Input Select", 0, 0},
+ {"I2S0 TX", 0, 1},
+ {"I2S1 TX", 0, 1},
+ {"APGA1 Source", 0, 1},
+ {"APGA2 Source", 0, 1},
+ {"APGA1 Destination", 0, 0},
+ {"APGA2 Destination", 0, 0},
+ {"DAC1 Side Tone", 0, 1},
+ {"DAC2 Side Tone", 0, 1},
+ {"RX-DPGA1 Gain", 0x3D, 0},
+ {"RX-DPGA2 Gain", 0x3D, 0},
+ {"RX-DPGA3 Gain", 0x3D, 0},
+ {"LINE1 Gain", 0x0A, 0},
+ {"LINE2 Gain", 0x0A, 0},
+ {"SPKR Gain", 0x0A, 0},
+ {"EAR Gain", 0x0A, 0},
+ {"AUXO1 Gain", 0x0A, 0},
+ {"AUXO2 Gain", 0x0A, 0},
+ {"MIC1 Gain", 0, 0},
+ {"MIC2 Gain", 0, 0},
+ {"TX-DPGA1 Gain", 0, 0},
+ {"TX-DPGA2 Gain", 0, 0},
+ {"ST-PGA1 Gain", 0, 0},
+ {"ST-PGA2 Gain", 0, 0},
+ {"APGA1 Gain", 0, 0},
+ {"APGA2 Gain", 0, 0},
+ {"DAC1 Power Mode", 0, 1},
+ {"DAC2 Power Mode", 0, 1},
+ {"DAC3 Power Mode", 0, 1},
+ {"EAR Power Mode", 0, 1},
+ {"AUXO Power Mode", 0, 1},
+ {"LINE1 Inverse", 0, 0},
+ {"LINE2 Inverse", 0, 0},
+ {"AUXO1 Inverse", 0, 0},
+ {"AUXO2 Inverse", 0, 0},
+ {"AUXO1 Pulldown", 0, 0},
+ {"AUXO2 Pulldown", 0, 0},
+ {"VMID1", 0, 0},
+ {"VMID2", 0, 0},
+ {"MIC1-1 MBias", 0, 1},
+ {"MIC1-2 MBias", 0, 1},
+ {"MIC2-1 MBias", 0, 1},
+ {"MIC2-2 MBias", 0, 1},
+ {"MBIAS1 HiZ Option", 0, 1},
+ {"MBIAS2 HiZ Option", 0, 1},
+ {"MBIAS2 Output Voltage", 0, 1},
+ {"MBIAS2 Internal Resistor", 0, 1},
+ {"MIC1 Input Impedance", 0, 1},
+ {"MIC2 Input Impedance", 0, 1},
+ {"TX1 HP Filter", 0, 1},
+ {"TX2 HP Filter", 0, 1},
+ {"LINEIN1 Pre-charge", 0, 0},
+ {"LINEIN2 Pre-charge", 0, 0},
+ {"MIC1P1 Pre-charge", 0, 0},
+ {"MIC1P2 Pre-charge", 0, 0},
+ {"MIC1N1 Pre-charge", 0, 0},
+ {"MIC1N2 Pre-charge", 0, 0},
+ {"MIC2P1 Pre-charge", 0, 0},
+ {"MIC2P2 Pre-charge", 0, 0},
+ {"MIC2N1 Pre-charge", 0, 0},
+ {"MIC2N2 Pre-charge", 0, 0},
+ {"ST1 HP Filter", 0, 1},
+ {"ST2 HP Filter", 0, 1},
+ {"I2S0 Word Length", 0, 1},
+ {"I2S1 Word Length", 0, 1},
+ {"I2S0 Mode", 0, 1},
+ {"I2S1 Mode", 0, 1},
+ {"I2S0 Tri-state", 0, 1},
+ {"I2S1 Tri-state", 0, 1},
+ {"I2S0 Pulldown", 0, 1},
+ {"I2S1 Pulldown", 0, 1},
+ {"I2S0 Sample Rate", 0, 1},
+ {"I2S1 Sample Rate", 0, 1},
+ {"Interface Loop", 0, 0},
+ {"Interface Swap", 0, 0},
+ {"Commit", 0, 0},
+};
+
+/* control No. 0 correpsonds to bit No. 0 in this array.
+ If a control value has been changed, but not commited
+ to the AB3550. Its corresponding bit is set.
+ */
+static unsigned long control_changed[] = {
+ 0, 0, 0, 0
+};
+
+static const char *enum_rx2_select[] = {"I2S0", "I2S1"};
+static const char *enum_i2s_tx[] =
+{"TX1", "TX2", "tri-state", "mute"};
+
+static const char *enum_apga1_source[] =
+{"None", "LINEIN1", "MIC1", "MIC2"};
+
+static const char *enum_apga2_source[] =
+{"None", "LINEIN2", "MIC1", "MIC2"};
+
+static const char *enum_dac1_side_tone[] =
+{"None", "TX1", "TX2"};
+
+static const char *enum_dac1_power_mode[] =
+{"100%", "75%", "50%"};
+
+static const char *enum_ear_power_mode[] =
+{"100%", "75%"};
+
+static const char *enum_auxo_power_mode[] =
+{"100%", "75%", "50%", "25%"};
+
+static const char *enum_mic_bias_connection[] =
+{"MIC Bias 1", "MIC Bias 2"};
+
+static const char *enum_mbias1_hiz_option[] =
+{"GND", "HiZ"};
+
+static const char *enum_mbias2_output_voltage[] =
+{"2.0v", "2.2v"};
+
+static const char *enum_mbias2_internal_resistor[] =
+{"connected", "bypassed"};
+
+static const char *enum_mic1_input_impedance[] =
+{"12.5 kohm", "25 kohm", "50 kohm"};
+
+static const char *enum_tx1_hp_filter[] =
+{"HP3", "HP1", "bypass"};
+
+static const char *enum_i2s_word_length[] =
+{"16 bits", "24 bits"};
+
+static const char *enum_i2s_mode[] =
+{"Master Mode", "Slave Mode"};
+
+static const char *enum_i2s_tristate[] =
+{"Normal", "Tri-state"};
+
+static const char *enum_i2s_pulldown[] =
+{"disconnected", "connected"};
+
+static const char *enum_i2s_sample_rate[] =
+{"8 kHz", "16 kHz", "44.1 kHz", "48 kHz"};
+
+
+static struct soc_enum ab3550_enum[] = {
+ /* RX2 Select */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_rx2_select), enum_rx2_select),
+ /* I2S0 TX */
+ SOC_ENUM_DOUBLE(0, 0, 4, ARRAY_SIZE(enum_i2s_tx), enum_i2s_tx),
+ /* I2S1 TX */
+ SOC_ENUM_DOUBLE(0, 0, 4, ARRAY_SIZE(enum_i2s_tx), enum_i2s_tx),
+ /* APGA1 Source */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_apga1_source),
+ enum_apga1_source),
+ /* APGA2 Source */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_apga2_source),
+ enum_apga2_source),
+ /* DAC1 Side Tone */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_dac1_side_tone),
+ enum_dac1_side_tone),
+ /* DAC2 Side Tone */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_dac1_side_tone),
+ enum_dac1_side_tone),
+ /* DAC1 Power Mode */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_dac1_power_mode),
+ enum_dac1_power_mode),
+ /* DAC2 Power Mode */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_dac1_power_mode),
+ enum_dac1_power_mode),
+ /* DAC3 Power Mode */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_dac1_power_mode),
+ enum_dac1_power_mode),
+ /* EAR Power Mode */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_ear_power_mode),
+ enum_ear_power_mode),
+ /* AUXO Power Mode */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode),
+ /* MIC Bias Connection */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic_bias_connection),
+ enum_mic_bias_connection),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic_bias_connection),
+ enum_mic_bias_connection),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic_bias_connection),
+ enum_mic_bias_connection),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic_bias_connection),
+ enum_mic_bias_connection),
+ /* MBIAS1 HiZ Option */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mbias1_hiz_option),
+ enum_mbias1_hiz_option),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mbias1_hiz_option),
+ enum_mbias1_hiz_option),
+ /* MBIAS2 Output voltage */
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mbias2_output_voltage),
+ enum_mbias2_output_voltage),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mbias2_internal_resistor),
+ enum_mbias2_internal_resistor),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic1_input_impedance),
+ enum_mic1_input_impedance),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_mic1_input_impedance),
+ enum_mic1_input_impedance),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_tx1_hp_filter),
+ enum_tx1_hp_filter),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_tx1_hp_filter),
+ enum_tx1_hp_filter),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_tx1_hp_filter),
+ enum_tx1_hp_filter),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_tx1_hp_filter),
+ enum_tx1_hp_filter),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_pulldown),
+ enum_i2s_pulldown),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_pulldown),
+ enum_i2s_pulldown),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate),
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate),
+
+};
+
+static struct snd_kcontrol_new ab3550_snd_controls[] = {
+ /* RX Routing */
+ SOC_ENUM("RX2 Select", ab3550_enum[0]),
+ SOC_SINGLE("DAC1 Routing", 0, 0, 0x3f, 0),
+ SOC_SINGLE("DAC2 Routing", 0, 0, 0x3f, 0),
+ SOC_SINGLE("DAC3 Routing", 0, 0, 0x3f, 0),
+ /* TX Routing */
+ SOC_SINGLE("MIC1 Input Select", 0, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 Input Select", 0, 0, 0x3f, 0),
+ SOC_ENUM("I2S0 TX", ab3550_enum[1]),
+ SOC_ENUM("I2S1 TX", ab3550_enum[2]),
+ /* Routing of Side Tone and Analop Loop */
+ SOC_ENUM("APGA1 Source", ab3550_enum[3]),
+ SOC_ENUM("APGA2 Source", ab3550_enum[4]),
+ SOC_SINGLE("APGA1 Destination", 0, 0, 0x3f, 0),
+ SOC_SINGLE("APGA2 Destination", 0, 0, 0x3f, 0),
+ SOC_ENUM("DAC1 Side Tone", ab3550_enum[5]),
+ SOC_ENUM("DAC2 Side Tone", ab3550_enum[5]),
+ /* RX Volume Control */
+ SOC_SINGLE("RX-DPGA1 Gain", 0, 0, 66, 0),
+ SOC_SINGLE("RX-DPGA2 Gain", 0, 0, 66, 0),
+ SOC_SINGLE("RX-DPGA3 Gain", 0, 0, 66, 0),
+ SOC_SINGLE("LINE1 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("LINE2 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("SPKR Gain", 0, 0, 22, 0),
+ SOC_SINGLE("EAR Gain", 0, 0, 14, 0),
+ SOC_SINGLE("AUXO1 Gain", 0, 0, 12, 0),
+ SOC_SINGLE("AUXO2 Gain", 0, 0, 12, 0),
+ /* TX Volume Control */
+ SOC_SINGLE("MIC1 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("MIC2 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("TX-DPGA1 Gain", 0, 0, 15, 0),
+ SOC_SINGLE("TX-DPGA2 Gain", 0, 0, 15, 0),
+ /* Volume Control of Side Tone and Analog Loop */
+ SOC_SINGLE("ST-PGA1 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("ST-PGA2 Gain", 0, 0, 10, 0),
+ SOC_SINGLE("APGA1 Gain", 0, 0, 28, 0),
+ SOC_SINGLE("APGA2 Gain", 0, 0, 28, 0),
+ /* RX Properties */
+ SOC_ENUM("DAC1 Power Mode", ab3550_enum[6]),
+ SOC_ENUM("DAC2 Power Mode", ab3550_enum[7]),
+ SOC_ENUM("DAC3 Power Mode", ab3550_enum[8]),
+ SOC_ENUM("EAR Power Mode", ab3550_enum[9]),
+ SOC_ENUM("AUXO Power Mode", ab3550_enum[10]),
+ SOC_SINGLE("LINE1 Inverse", 0, 0, 1, 0),
+ SOC_SINGLE("LINE2 Inverse", 0, 0, 1, 0),
+ SOC_SINGLE("AUXO1 Inverse", 0, 0, 1, 0),
+ SOC_SINGLE("AUXO2 Inverse", 0, 0, 1, 0),
+ SOC_SINGLE("AUXO1 Pulldown", 0, 0, 1, 0),
+ SOC_SINGLE("AUXO2 Pulldown", 0, 0, 1, 0),
+ /* TX Properties */
+ SOC_SINGLE("VMID1", 0, 0, 0xff, 0),
+ SOC_SINGLE("VMID2", 0, 0, 0xff, 0),
+ SOC_ENUM("MIC1-1 MBias", ab3550_enum[11]),
+ SOC_ENUM("MIC1-2 MBias", ab3550_enum[12]),
+ SOC_ENUM("MIC2-1 MBias", ab3550_enum[13]),
+ SOC_ENUM("MIC2-2 MBias", ab3550_enum[14]),
+ SOC_ENUM("MBIAS1 HiZ Option", ab3550_enum[15]),
+ SOC_ENUM("MBIAS2 HiZ Option", ab3550_enum[16]),
+ SOC_ENUM("MBIAS2 Output Voltage", ab3550_enum[17]),
+ SOC_ENUM("MBIAS2 Internal Resistor", ab3550_enum[18]),
+ SOC_ENUM("MIC1 Input Impedance", ab3550_enum[19]),
+ SOC_ENUM("MIC2 Input Impedance", ab3550_enum[20]),
+ SOC_ENUM("TX1 HP Filter", ab3550_enum[21]),
+ SOC_ENUM("TX2 HP Filter", ab3550_enum[22]),
+ SOC_SINGLE("LINEIN1 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("LINEIN2 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC1P1 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC1P2 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC1N1 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC1N2 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC2P1 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC2P2 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC2N1 Pre-charge", 0, 0, 100, 0),
+ SOC_SINGLE("MIC2N2 Pre-charge", 0, 0, 100, 0),
+ /* Side Tone and Analog Loop Properties */
+ SOC_ENUM("ST1 HP Filter", ab3550_enum[23]),
+ SOC_ENUM("ST2 HP Filter", ab3550_enum[24]),
+ /* I2S Interface Properties */
+ SOC_ENUM("I2S0 Word Length", ab3550_enum[25]),
+ SOC_ENUM("I2S1 Word Length", ab3550_enum[26]),
+ SOC_ENUM("I2S0 Mode", ab3550_enum[27]),
+ SOC_ENUM("I2S1 Mode", ab3550_enum[28]),
+ SOC_ENUM("I2S0 tri-state", ab3550_enum[29]),
+ SOC_ENUM("I2S1 tri-state", ab3550_enum[30]),
+ SOC_ENUM("I2S0 pulldown", ab3550_enum[31]),
+ SOC_ENUM("I2S1 pulldown", ab3550_enum[32]),
+ SOC_ENUM("I2S0 Sample Rate", ab3550_enum[33]),
+ SOC_ENUM("I2S1 Sample Rate", ab3550_enum[34]),
+ SOC_SINGLE("Interface Loop", 0, 0, 0x0f, 0),
+ SOC_SINGLE("Interface Swap", 0, 0, 0x1f, 0),
+ /* Miscellaneous */
+ SOC_SINGLE("Commit", 0, 0, 1, 0)
+};
+
+static const struct snd_soc_dapm_widget ab3550_dapm_widgets[] = {
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+};
+
+static unsigned int ab3550_get_control_value(struct snd_soc_codec *codec,
+ unsigned int ctl)
+{
+ if (ctl >= ARRAY_SIZE(ab3550_ctl))
+ return -EINVAL;
+ return ab3550_ctl[ctl].value;
+}
+
+static unsigned int get_control_index(const char *name)
+{
+ int i, n;
+ for(i = 0, n = ARRAY_SIZE(ab3550_ctl); i < n; i++) {
+ if (strcmp(ab3550_ctl[i].name, name) == 0)
+ break;
+ }
+ return i;
+}
+
+static unsigned int get_control_value_by_name(const char *name)
+{
+ int i, n;
+ for(i = 0, n = ARRAY_SIZE(ab3550_ctl); i < n; i++) {
+ if (strcmp(ab3550_ctl[i].name, name) == 0)
+ break;
+ }
+ return ab3550_ctl[i].value;
+}
+
+static int gain_ramp(u8 reg, u8 target, int shift, u8 mask)
+{
+ u8 curr, step;
+ int ret = 0;
+
+// printk(KERN_DEBUG "%s invoked.\n", __func__);
+ if ((ret = GET_REG(reg, &curr))) {
+ return ret;
+ }
+ if (curr == target)
+ return 0;
+// step = curr < target ? 1 : -1;
+// while (!ret && curr != target) {
+/* At least 15ms are required, so I take 16ms :-) */
+// msleep(1);
+// ret = MASK_SET_REG(reg, mask, (curr += step) << shift);
+// }
+ ret = MASK_SET_REG(reg, mask, target << shift);
+ return ret;
+}
+
+static int ab3550_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ printk(KERN_DEBUG "%s: %s called\n", __FILE__, __func__);
+ return 0;
+}
+
+static int ab3550_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ return 0;
+}
+
+static int ab3550_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai* dai)
+{
+ u8 val;
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ MASK_SET_REG(CLOCK, CLOCK_ENABLE_MASK, 1 << CLOCK_ENABLE_SHIFT);
+ /* Turn on the master generator */
+ MASK_SET_REG(INTERFACE0, MASTER_GENx_PWR_MASK,
+ 1 << MASTER_GENx_PWR_SHIFT);
+ GET_REG(CLOCK, &val);
+ printk(KERN_DEBUG "%s: CLOCK = %02x.\n", __func__, val);
+ GET_REG(INTERFACE0, &val);
+ printk(KERN_DEBUG "%s: INTERFACE0 = %02x.\n", __func__, val);
+ GET_REG(RX1, &val);
+ printk(KERN_DEBUG "%s: RX1 = %02x.\n", __func__, val);
+ GET_REG(RX2, &val);
+ printk(KERN_DEBUG "%s: RX2 = %02x.\n", __func__, val);
+ GET_REG(SPKR_ADDER, &val);
+ printk(KERN_DEBUG "%s: SPKR_ADDER = %02x.\n", __func__, val);
+ GET_REG(AUXO1_ADDER, &val);
+ printk(KERN_DEBUG "%s: AUXO1_ADDER = %02x.\n", __func__, val);
+ GET_REG(AUXO2_ADDER, &val);
+ printk(KERN_DEBUG "%s: AUXO2_ADDER = %02x.\n", __func__, val);
+ GET_REG(SPKR, &val);
+ printk(KERN_DEBUG "%s: SPKR = %02x.\n", __func__, val);
+ GET_REG(AUXO1, &val);
+ printk(KERN_DEBUG "%s: AUXO1 = %02x.\n", __func__, val);
+ GET_REG(AUXO2, &val);
+ printk(KERN_DEBUG "%s: AUXO2 = %02x.\n", __func__, val);
+ GET_REG(RX1_DIGITAL_PGA, &val);
+ printk(KERN_DEBUG "%s: RX1_DIGITAL_PGA = %02x.\n", __func__, val);
+ GET_REG(RX2_DIGITAL_PGA, &val);
+ printk(KERN_DEBUG "%s: RX2_DIGITAL_PGA = %02x.\n", __func__, val);
+ return 0;
+}
+
+static void ab3550_pcm_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai* dai)
+{
+ int i;
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ MASK_SET_REG(INTERFACE0, MASTER_GENx_PWR_MASK, 0);
+ MASK_SET_REG(CLOCK, CLOCK_ENABLE_MASK, 0);
+ for (i = 0; i < ARRAY_SIZE(outamp_reg); i++) {
+ MASK_SET_REG(outamp_reg[i], outamp_pwr_mask[i], 0);
+ }
+}
+
+static int set_up_speaker_playback(void)
+{
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ MASK_SET_REG(CLOCK, CLOCK_REF_SELECT_MASK,
+ 1 << CLOCK_REF_SELECT_SHIFT);
+
+ /* Sample rate 48 kHz */
+ SET_REG(INTERFACE0, 0x03);
+ printk(KERN_DEBUG "%s: interface 0 is setup", __func__);
+
+ MASK_SET_REG(RX2, RX2_IF_SELECT_MASK, 0);
+ SET_REG(SPKR_ADDER, 1 << DAC1_TO_ADDER_SHIFT |
+ 1 << DAC2_TO_ADDER_SHIFT);
+/* SET_REG(AUXO1_ADDER, 1 << DAC1_TO_ADDER_SHIFT); */
+/* SET_REG(AUXO2_ADDER, 1 << DAC2_TO_ADDER_SHIFT); */
+ printk(KERN_DEBUG "%s: Routes to the speaker and the AUXO are setup.\n",
+ __func__);
+
+ /* Mute the outamps first */
+ MASK_SET_REG(RX1_DIGITAL_PGA, RXx_PGA_GAIN_MASK, 0);
+ MASK_SET_REG(RX2_DIGITAL_PGA, RXx_PGA_GAIN_MASK, 0);
+/* MASK_SET_REG(AUXO1, AUXOx_GAIN_MASK, 0); */
+/* MASK_SET_REG(AUXO2, AUXOx_GAIN_MASK, 0); */
+ MASK_SET_REG(SPKR, SPKR_GAIN_MASK, 0);
+
+ MASK_SET_REG(RX1, RX1_PWR_MASK | DAC1_PWR_MASK | DAC1_PWR_MODE_MASK,
+ 1 << RX1_PWR_SHIFT | 1 << DAC1_PWR_SHIFT |
+ 1 << DAC1_PWR_MODE_SHIFT);
+ MASK_SET_REG(RX2, RX2_PWR_MASK | DAC2_PWR_MASK | DAC2_PWR_MODE_MASK,
+ 1 << RX2_PWR_SHIFT | 1 << DAC2_PWR_SHIFT |
+ 1 << DAC2_PWR_MODE_SHIFT);
+ printk(KERN_DEBUG "%s: DAC1, DAC2 and RX2 are now powered up.\n",
+ __func__);
+
+ MASK_SET_REG(SPKR, SPKR_PWR_MASK, 1 << SPKR_PWR_SHIFT);
+/* MASK_SET_REG(AUXO1, AUXOx_PWR_MASK, 1 << AUXOx_PWR_SHIFT); */
+/* MASK_SET_REG(AUXO2, AUXOx_PWR_MASK, 1 << AUXOx_PWR_SHIFT); */
+ printk(KERN_DEBUG "%s: The speaker and the AUXO are now powered up.\n",
+ __func__);
+
+ gain_ramp(RX1_DIGITAL_PGA, get_control_value_by_name("RX-DPGA1 Gain"),
+ RXx_PGA_GAIN_SHIFT, RXx_PGA_GAIN_MASK);
+
+ gain_ramp(RX2_DIGITAL_PGA, get_control_value_by_name("RX-DPGA2 Gain"),
+ RXx_PGA_GAIN_SHIFT, RXx_PGA_GAIN_MASK);
+ gain_ramp(SPKR, get_control_value_by_name("SPKR Gain"),
+ SPKR_GAIN_SHIFT, SPKR_GAIN_MASK);
+/* gain_ramp(AUXO1, get_control_value_by_name("AUXO1 Gain"), */
+/* AUXOx_GAIN_SHIFT, AUXOx_GAIN_MASK); */
+/* gain_ramp(AUXO2, get_control_value_by_name("AUXO1 Gain"), */
+/* AUXOx_GAIN_SHIFT, AUXOx_GAIN_MASK); */
+ printk(KERN_DEBUG "%s: return 0.\n", __func__);
+ return 0;
+}
+
+static int ab3550_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params, struct snd_soc_dai* dai)
+{
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ set_up_speaker_playback();
+ return 0;
+}
+
+static int ab3550_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ab3550_dapm_widgets,
+ ARRAY_SIZE(ab3550_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int commit_outamps_routing(void)
+{
+ unsigned long idx;
+ int adjust = 0;
+ static const char *control_names[] = {
+ "DAC1 Routing", "DAC2 Routing", "DAC3 Routing",
+ "APGA1 Destination", "APGA2 Destination"
+ };
+ static const u8 outamp_adder_reg[] = {
+ LINE1_ADDER, LINE2_ADDER, SPKR_ADDER,
+ EAR_ADDER, AUXO1_ADDER, AUXO2_ADDER
+ };
+ static const u8 outamp_0db_gain[] = {
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C
+ };
+
+ for_each_bit(idx, control_changed, ARRAY_SIZE(ab3550_ctl)) {
+ if (strcmp(ab3550_ctl[idx].name, "DAC1 Routing") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "DAC2 Routing") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "DAC3 Routing") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "APGA1 Destination") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "APGA2 Destination") == 0
+ ) {
+ adjust = 1;
+ }
+ }
+ if (adjust) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(outamp_reg); i++) {
+ unsigned int connect = 0;
+ int j;
+ for (j = 0; j < ARRAY_SIZE(control_names); j++) {
+ if (get_control_value_by_name(control_names[j])
+ & 1 << i) {
+ connect |= 1 << j;
+ }
+ }
+ /* Ramp down the amplifier if
+ nothing is to be disconnected to it */
+ if (!connect) {
+ u8 val = 0;
+ GET_REG(outamp_reg[i], &val);
+ if (! (val & 1 << outamp_pwr_shift[i]))
+ continue;
+ gain_ramp(outamp_reg[i], 0,
+ outamp_gain_shift[i],
+ outamp_gain_mask[i]);
+ MASK_SET_REG(outamp_reg[i],
+ outamp_pwr_mask[i], 0);
+ continue;
+ }
+ MASK_SET_REG(outamp_adder_reg[i],
+ DAC3_TO_ADDER_MASK |
+ DAC2_TO_ADDER_MASK |
+ DAC3_TO_ADDER_MASK,
+ connect);
+ if (connect & 1 << 3) {
+ /* Connect APGA1 */
+ MASK_SET_REG(APGA1_ADDER,
+ 1 << (ARRAY_SIZE(outamp_reg) - i),
+ 1 << (ARRAY_SIZE(outamp_reg) - i));
+ }
+ if (connect & 1 << 4) {
+ /* Connect APGA2 */
+ MASK_SET_REG(APGA2_ADDER,
+ 1 << (ARRAY_SIZE(outamp_reg) - i),
+ 1 << (ARRAY_SIZE(outamp_reg) - i));
+ }
+ MASK_SET_REG(outamp_reg[i],
+ outamp_pwr_mask[i],
+ 1 << outamp_pwr_shift[i]);
+ gain_ramp(outamp_reg[i], outamp_0db_gain[i],
+ outamp_gain_shift[i],
+ outamp_gain_mask[i]);
+ }
+ }
+ return 0;
+}
+
+static int commit_gain(void)
+{
+ unsigned long idx;
+ for_each_bit(idx, control_changed, ARRAY_SIZE(ab3550_ctl)) {
+ if (strcmp(ab3550_ctl[idx].name, "LINE1 Gain") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "LINE2 Gain") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "SPKR Gain") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "EAR Gain") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "AUXO1 Gain") == 0 ||
+ strcmp(ab3550_ctl[idx].name, "AUXO2 Gain") == 0
+ ) {
+ /* Ramping is needed if powered up */
+ int i = idx - get_control_index("LINE1 Gain");
+ u8 val = GET_REG(outamp_reg[i], &val);
+ if (val & 1 << outamp_pwr_shift[i]) {
+ gain_ramp(outamp_reg[i], ab3550_ctl[idx].value,
+ outamp_gain_shift[i],
+ outamp_gain_mask[i]);
+ continue;
+ }
+ /* Othwise set directly */
+ MASK_SET_REG(outamp_reg[i], outamp_gain_mask[i],
+ ab3550_ctl[idx].value);
+ } else if (strcmp(ab3550_ctl[idx].name, "RX-DPGA1 Gain") == 0) {
+ gain_ramp(RX1_DIGITAL_PGA,
+ get_control_value_by_name("RX-DPGA1 Gain"),
+ RXx_PGA_GAIN_SHIFT, RXx_PGA_GAIN_MASK);
+
+ } else if (strcmp(ab3550_ctl[idx].name, "RX-DPGA2 Gain") == 0) {
+ gain_ramp(RX2_DIGITAL_PGA,
+ get_control_value_by_name("RX-DPGA2 Gain"),
+ RXx_PGA_GAIN_SHIFT, RXx_PGA_GAIN_MASK);
+
+ } else if (strcmp(ab3550_ctl[idx].name, "RX-DPGA3 Gain") == 0) {
+ gain_ramp(RX3_DIGITAL_PGA,
+ get_control_value_by_name("RX-DPGA3 Gain"),
+ RXx_PGA_GAIN_SHIFT, RXx_PGA_GAIN_MASK);
+
+ }
+ }
+ return 0;
+}
+
+static int ab3550_set_control_value(struct snd_soc_codec *codec,
+ unsigned int ctl, unsigned int value)
+{
+ int ret = 0;
+
+ if (ctl >= ARRAY_SIZE(ab3550_ctl))
+ return -EINVAL;
+ if (ab3550_ctl[ctl].value == value)
+ return 0;
+ ab3550_ctl[ctl].value = value;
+ if (strcmp(ab3550_ctl[ctl].name, "Commit") != 0)
+ return 0;
+ /* Commit the changes */
+ commit_outamps_routing();
+ commit_gain();
+ memset(control_changed, 0, sizeof(control_changed));
+
+
+ return ret;
+}
+
+static int ab3550_add_controls(struct snd_soc_codec *codec)
+{
+ int err = 0, i, n = ARRAY_SIZE(ab3550_snd_controls);
+
+ printk(KERN_DEBUG "%s: %s called.\n", __FILE__, __func__);
+ for (i = 0; i < n; i++) {
+ /* Initialize the control indice */
+ if (! ab3550_ctl[i].is_enum) {
+ ab3550_snd_controls[i].private_value |= i;
+ } else {
+ struct soc_enum *p = (struct soc_enum *)
+ ab3550_snd_controls[i].private_value;
+ p->reg = i;
+ }
+ printk(KERN_DEBUG "%s: %s.reg = %d\n", __func__,
+ ab3550_ctl[i].name, i);
+ err = snd_ctl_add(codec->card,
+ snd_ctl_new1(&ab3550_snd_controls[i], codec));
+ if (err < 0) {
+ printk(KERN_DEBUG "%s failed to add control No.%d of %d.\n",
+ __func__, i, n);
+ return err;
+ }
+ }
+ /* initialize control values */
+ printk(KERN_DEBUG "%s: %s to return %d.\n", __FILE__, __func__, err);
+ return err;
+}
+
+static int ab3550_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret;
+
+ printk(KERN_INFO "%s called. pdev = %p.\n", __func__, pdev);
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+ codec->name = "AB3550";
+ codec->owner = THIS_MODULE;
+ /* TODO: add codec dai */
+ codec->dai = ab3550_codec_dai;
+ codec->num_dai = 2;
+ codec->read = ab3550_get_control_value;
+ codec->write = ab3550_set_control_value;
+ codec->reg_cache_size = ARRAY_SIZE(ab3550_ctl);
+ codec->reg_cache = kmemdup(ab3550_ctl, sizeof(ab3550_ctl), GFP_KERNEL);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ socdev->codec = codec;
+ mutex_init(&codec->mutex);
+ printk(KERN_DEBUG "%s: %s: snd_soc_new_pcms to be called.\n",
+ __FILE__, __func__);
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: Failed to create a new card "
+ "and new PCMs. error %d\n", __func__, ret);
+ goto err1;
+ }
+ ret = snd_soc_init_card(socdev);
+
+ if (ret < 0) {
+ printk(KERN_ERR "%s: failed to register card. error %d.\n",
+ __func__, ret);
+ goto err2;
+ }
+ /* Add controls */
+ if (ab3550_add_controls(socdev->codec) < 0)
+ goto err2;
+ ab3550_add_widgets(codec);
+ return 0;
+
+err2:
+ snd_soc_free_pcms(socdev);
+err1:
+ kfree(codec);
+ return ret;
+}
+
+static int ab3550_codec_remove(struct platform_device *pdev)
+{
+ printk(KERN_DEBUG "%s : pdev=%p.\n", __func__, pdev);
+ return 0;
+}
+
+static int ab3550_codec_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ printk(KERN_DEBUG "%s : pdev=%p.\n", __func__, pdev);
+ return 0;
+}
+
+static int ab3550_codec_resume(struct platform_device *pdev)
+{
+ printk(KERN_DEBUG "%s : pdev=%p.\n", __func__, pdev);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ab3550 = {
+ .probe = ab3550_codec_probe,
+ .remove = ab3550_codec_remove,
+ .suspend = ab3550_codec_suspend,
+ .resume = ab3550_codec_resume
+};
+
+EXPORT_SYMBOL_GPL(soc_codec_dev_ab3550);
+
+/* struct snd_soc_dai ab3550_codec_dai = { */
+/* .name = "AB3550", */
+/* .playback = { */
+/* .stream_name = "Playback", */
+/* .channels_min = 1, */
+/* .channels_max = 2, */
+/* .rates = AB3550_RATES, */
+/* .formats = AB3550_FORMATS,}, */
+/* .capture = { */
+/* .stream_name = "Capture", */
+/* .channels_min = 1, */
+/* .channels_max = 2, */
+/* .rates = AB3550_RATES, */
+/* .formats = AB3550_FORMATS,}, */
+/* .ops = { */
+/* .prepare = ab3550_pcm_prepare, */
+/* .hw_params = ab3550_hw_params, */
+/* .shutdown = ab3550_shutdown, */
+/* }, */
+/* .dai_ops = { */
+/* .digital_mute = ab3550_mute, */
+/* .set_sysclk = ab3550_set_dai_sysclk, */
+/* .set_fmt = ab3550_set_dai_fmt, */
+/* } */
+/* }; */
+/* EXPORT_SYMBOL_GPL(ab3550_codec_dai); */
+static int ab3550_platform_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ printk(KERN_DEBUG "%s invoked with pdev = %p.\n", __func__, pdev);
+ ab3550_dev = platform_get_drvdata(pdev);
+ return ret;
+}
+
+static int ab3550_platform_remove(struct platform_device *pdev)
+{
+ int ret;
+ printk(KERN_DEBUG "%s called.\n", __func__);
+ if ((ret = soc_codec_dev_ab3550.remove(NULL)))
+ printk(KERN_ERR "%s failed with error code %d.\n",
+ __func__, ret);
+ return ret;
+}
+
+static int ab3550_platform_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ int ret;
+ printk(KERN_DEBUG "%s called.\n", __func__);
+ if ((ret = soc_codec_dev_ab3550.suspend(NULL, state)))
+ printk(KERN_ERR "%s failed with error code %d.\n",
+ __func__, ret);
+ return ret;
+}
+
+static int ab3550_platform_resume(struct platform_device *pdev)
+{
+ int ret;
+ printk(KERN_DEBUG "%s called.\n", __func__);
+ if ((ret = soc_codec_dev_ab3550.resume(NULL)))
+ printk(KERN_ERR "%s failed with error code %d.\n",
+ __func__, ret);
+ return ret;
+}
+
+static struct platform_driver ab3550_platform_driver = {
+ .driver = {
+ .name = "ab3550-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab3550_platform_probe,
+ .remove = ab3550_platform_remove,
+ .suspend = ab3550_platform_suspend,
+ .resume = ab3550_platform_resume,
+};
+
+
+static int __devinit ab3550_init(void)
+{
+ int ret;
+ printk(KERN_DEBUG "%s called.\n", __func__);
+ ret= platform_driver_register(&ab3550_platform_driver);
+ printk(KERN_DEBUG "%s finished with error code %d.\n",
+ __func__, ret);
+ return ret;
+}
+
+static void ab3550_exit(void)
+{
+ printk(KERN_DEBUG "%s called.\n", __func__);
+ platform_driver_unregister(&ab3550_platform_driver);
+}
+
+module_init(ab3550_init);
+module_exit(ab3550_exit);
+
+MODULE_DESCRIPTION("AB3550 Codec driver");
+MODULE_AUTHOR("Xie Xiaolei, xie.xiaolei@stericsson.com, www.stericsson.com");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ab3550.h b/sound/soc/codecs/ab3550.h
new file mode 100755
index 00000000000..754f5422ea6
--- /dev/null
+++ b/sound/soc/codecs/ab3550.h
@@ -0,0 +1,291 @@
+#ifndef AB3550_CODEC_REGISTERS_H
+#define AB3550_CODEC_REGISTERS_H
+
+/* MIC BIAS */
+#define MIC_BIAS1 0X31
+#define MIC_BIAS2 0X32
+#define MBIAS2_OUT_V_MASK 0x04
+#define MBIAS2_OUT_V_SHIFT 2
+#define MBIAS_PWR_MASK 0x02
+#define MBIAS_PWR_SHIFT 1
+#define MBIAS_PDN_IMP_MASK 0x01
+#define MBIAS_PDN_IMP_SHIFT 0
+
+#define MIC_BIAS2_VAD 0x33
+#define MBIAS2_R_INT_MASK 0x01
+#define MBIAS2_R_INT_SHIFT 0
+
+/* MIC */
+#define MIC1_GAIN 0x34
+#define MIC2_GAIN 0x35
+#define MICx_GAIN_MASK 0xF0
+#define MICx_GAIN_SHIFT 4
+#define MICx_IN_IMP_MASK 0x0C
+#define MICx_IN_IMP_SHIFT 2
+#define MICx_PWR_MASK 0x01
+#define MICx_PWR_SHIFT 0
+
+#define MIC1_INPUT_SELECT 0x36
+#define MIC2_INPUT_SELECT 0x37
+#define MICxP1_SEL_MASK 0x80
+#define MICxP1_SEL_SHIFT 7
+#define MICxN1_SEL_MASK 0x40
+#define MICxN1_SEL_SHIFT 6
+#define MICxP2_SEL_MASK 0x20
+#define MICxP2_SEL_SHIFT 5
+#define MICxN2_SEL_MASK 0x10
+#define MICxN2_SEL_SHIFT 4
+#define LINEIN_SEL_MASK 0x03
+#define LINEIN_SEL_SHIFT 0
+
+#define MIC1_VMID_SELECT 0x38
+#define MIC2_VMID_SELECT 0x39
+#define VMIDx_ENABLE_MASK 0xC0
+#define VMIDx_ENABLE_SHIFT 6
+#define VMIDx_LINEIN1_N_MASK 0x20
+#define VMIDx_LINEIN1_N_SHIFT 5
+#define VMIDx_LINEIN2_N_MASK 0x10
+#define VMIDx_LINEIN2_N_SHIFT 4
+#define VMIDx_MICxP1_MASK 0x08
+#define VMIDx_MICxP1_SHIFT 3
+#define VMIDx_MICxP2_MASK 0x04
+#define VMIDx_MICxP2_SHIFT 2
+#define VMIDx_MICxN1_MASK 0x02
+#define VMIDx_MICxN1_SHIFT 1
+#define VMIDx_MICxN2_MASK 0x01
+#define VMIDx_MICxN2_SHIFT 0
+
+#define MIC2_TO_MIC1 0x3A
+#define MIC2P1_MIC1P_SEL_MASK 0x02
+#define MIC2N1_MIC1N_SEL_MASK 0x01
+#define MIC2N1_MIC1N_SEL_SHIFT 0
+
+/* Analog Loop */
+#define ANALOG_LOOP_PGA1 0x3B
+#define ANALOG_LOOP_PGA2 0x3C
+#define APGAx_GAIN_MASK 0xF8
+#define APGAx_GAIN_SHIFT 3
+#define APGAx_PWR_MASK 0x04
+#define APGAx_PWR_SHIFT 2
+#define APGAx_MUX_MASK 0x03
+#define APGAx_MUX_SHIFT 0
+
+#define APGA_VMID_SELECT 0x3D
+#define VMID_APGA1_ENABLE_MASK 0xC0
+#define VMID_APGA1_ENABLE_SHIFT 6
+#define VMID_APGA1_LINEIN1_MASK 0x20
+#define VMID_APGA1_LINEIN1_SHIFT 5
+#define VMID_APGA2_ENABLE_MASK 0x0C
+#define VMID_APGA2_ENABLE_SHIFT 2
+#define VMID_APGA2_LINEIN2_MASK 0x02
+#define VMID_APGA2_LINEIN2_SHIFT 1
+
+/* Output Amplifiers */
+#define EAR 0x3E
+#define EAR_PWR_MODE_MASK 0x20
+#define EAR_PWR_MODE_SHIFT 5
+#define EAR_PWR_MASK 0x10
+#define EAR_PWR_SHIFT 4
+#define EAR_GAIN_MASK 0x0F
+#define EAR_GAIN_SHIFT 0
+
+#define AUXO1 0x3F
+#define AUXO2 0x40
+#define AUXOx_PWR_MASK 0x80
+#define AUXOx_PWR_SHIFT 7
+#define AUXOx_INV_MASK 0x40
+#define AUXOx_INV_SHIFT 6
+#define AUXOx_PULLDWN_MASK 0x20
+#define AUXOx_PULLDWN_SHIFT 5
+#define AUXOx_GAIN_MASK 0x0F
+#define AUXOx_GAIN_SHIFT 0
+
+#define AUXO_PWR_MODE 0x41
+#define AUT_PWR_MODE_MASK 0x04
+#define AUT_PWR_MODE_SHIFT 2
+#define AUXO_PWR_MODE_MASK 0x03
+#define AUXO_PWR_MODE_SHIFT 0
+
+#define OFFSET_CANCEL 0x42
+#define SPKR_OFF_CANC_MASK 0x04
+#define SPKR_OFF_CANC_SHIFT 2
+#define AUXO_OFF_CANC_MASK 0x02
+#define AUXO_OFF_CANC_SHIFT 1
+#define OFFSET_CLOCK_MASK 0x01
+#define OFFSET_CLOCK_SHIFT 0
+
+#define SPKR 0x43
+#define OVR_CURR_PROT_MASK 0x80
+#define OVR_CURR_PROT_SHIFT 7
+#define SPKR_PWR_MASK 0x40
+#define SPKR_PWR_SHIFT 6
+#define SPKR_GAIN_MASK 0x1F
+#define SPKR_GAIN_SHIFT 0
+
+#define LINE1 0x44
+#define LINE2 0x45
+#define LINEx_PWR_MASK 0x80
+#define LINEx_PWR_SHIFT 7
+#define LINEx_INV_MASK 0x40
+#define LINEx_INV_SHIFT 6
+#define VMID_BUFFx_MASK 0x10
+#define VMID_BUFFx_SHIFT 4
+#define LINEx_GAIN_MASK 0x0F
+#define LINEx_GAIN_SHIFT 0
+
+/* Analog loop Routing */
+
+#define APGA1_ADDER 0x46
+#define APGA2_ADDER 0x47
+#define APGAx_TO_LINE1_MASK 0x20
+#define APGAx_TO_LINE1_SHIFT 5
+#define APGAx_TO_LINE2_MASK 0x10
+#define APGAx_TO_LINE2_SHIFT 4
+#define APGAx_TO_SPKR_MASK 0x08
+#define APGAx_TO_SPKR_SHIFT 3
+#define APGAx_TO_EAR_MASK 0x04
+#define APGAx_TO_EAR_SHIFT 2
+#define APGAx_TO_AUXO1_MASK 0x02
+#define APGAx_TO_AUXO1_SHIFT 1
+#define APGAx_TO_AUXO2_MASK 0x01
+#define APGAx_TO_AUXO2_SHIFT 0
+
+/* Output Amplifiers Routing */
+
+#define EAR_ADDER 0x48
+#define AUXO1_ADDER 0x49
+#define AUXO2_ADDER 0x4A
+#define SPKR_ADDER 0x4B
+#define LINE1_ADDER 0x4C
+#define LINE2_ADDER 0x4D
+#define DAC3_TO_ADDER_MASK 0x04
+#define DAC3_TO_ADDER_SHIFT 2
+#define DAC2_TO_ADDER_MASK 0x02
+#define DAC2_TO_ADDER_SHIFT 1
+#define DAC1_TO_ADDER_MASK 0x01
+#define DAC1_TO_ADDER_SHIFT 0
+
+#define EAR_TO_MIC2 0x4E
+#define EAR_TO_MIC2_MASK 0x01
+#define EAR_TO_MIC2_SHIFT 0
+
+#define SPKR_TO_MIC2 0x4F
+#define SPKR_TO_MIC2_MASK 0x01
+#define SPKR_TO_MIC2_SHIFT 0
+
+#define NEGATIVE_CHARGE_PUMP 0x50
+#define NCP_MODE_MASK 0x02
+#define NCP_MODE_SHIFT 1
+#define NCP_PWR_MASK 0x01
+#define NCP_PWR_SHIFT 0
+
+#define TX1 0x51
+#define TX2 0x52
+#define TXx_HP_FILTER_MASK 0x0C
+#define TXx_HP_FILTER_SHIFT 2
+#define TXx_PWR_MASK 0x02
+#define TXx_PWR_SHIFT 1
+#define ADCx_PWR_MASK 0x01
+#define ADCx_PWR_SHIFT 0
+
+#define RX1 0x53
+#define RX1_PWR_MASK 0x08
+#define RX1_PWR_SHIFT 3
+#define DAC1_PWR_MASK 0x04
+#define DAC1_PWR_SHIFT 2
+#define DAC1_PWR_MODE_MASK 0x03
+#define DAC1_PWR_MODE_SHIFT 0
+
+#define RX2 0x54
+#define RX2_IF_SELECT_MASK 0x10
+#define RX2_IF_SELECT_SHIFT 4
+#define RX2_PWR_MASK 0x08
+#define RX2_PWR_SHIFT 3
+#define DAC2_PWR_MASK 0x04
+#define DAC2_PWR_SHIFT 2
+#define DAC2_PWR_MODE_MASK 0x03
+#define DAC2_PWR_MODE_SHIFT 0
+
+#define RX3 0x55
+#define RX3_PWR_MASK 0x08
+#define RX3_PWR_SHIFT 3
+#define DAC3_PWR_MASK 0x04
+#define DAC3_PWR_SHIFT 2
+#define DAC3_PWR_MODE_MASK 0x03
+#define DAC3_PWR_MODE_SHIFT 0
+
+#define TX_DIGITAL_PGA1 0X56
+#define TX_DIGITAL_PGA2 0X57
+#define TXDPGAx_MASK 0x0F
+#define TXDPGAx_SHIFT 0
+
+#define RX1_DIGITAL_PGA 0x58
+#define RX2_DIGITAL_PGA 0x59
+#define RX3_DIGITAL_PGA 0x5A
+#define RXx_PGA_GAIN_MASK 0x7F
+#define RXx_PGA_GAIN_SHIFT 0
+
+#define SIDETONE1_PGA 0x5B
+#define SIDETONE2_PGA 0x5C
+#define STx_HP_FILTER_MASK 0x60
+#define STx_HP_FILTER_SHIFT 5
+#define STx_MUX_MASK 0x10
+#define STx_MUX_SHIFT 4
+#define STx_PGA_MASK 0x0F
+#define STx_PGA_SHIFT 0
+
+/* clock */
+
+#define CLOCK 0x5D
+#define CLOCK_REF_SELECT_MASK 0x02
+#define CLOCK_REF_SELECT_SHIFT 1
+#define CLOCK_ENABLE_MASK 0x01
+#define CLOCK_ENABLE_SHIFT 0
+
+/* Interface */
+
+#define INTERFACE0 0x5E
+#define INTERFACE1 0x60
+#define I2Sx_WORDLENGTH_MASK 0x40
+#define I2Sx_WORDLENGTH_SHIFT 6
+#define MASTER_GENx_PWR_MASK 0x20
+#define MASTER_GENx_PWR_SHIFT 5
+#define I2Sx_MODE_MASK 0x10
+#define I2Sx_MODE_SHIFT 4
+#define I2Sx_TRISTATE_MASK 0x08
+#define I2Sx_TRISTATE_SHIFT 3
+#define I2Sx_PULLDOWN_MASK 0x04
+#define I2Sx_PULLDOWN_SHIFT 2
+#define I2Sx_SR_MASK 0x03
+#define I2Sx_SR_SHIFT 0
+
+#define INTERFACE0_DATA 0x5F
+#define INTERFACE1_DATA 0x61
+#define I2Sx_L_DATA_MASK 0x0C
+#define I2Sx_L_DATA_SHIFT 2
+#define I2Sx_R_DATA_MASK 0x03
+#define I2Sx_R_DATA_SHIFT 0
+
+#define INTERFACE_LOOP 0x62
+#define I2S0_INT_LOOP_MASK 0x08
+#define I2S0_INT_LOOP_SHIFT 3
+#define I2S0_EXT_LOOP_MASK 0x04
+#define I2S0_EXT_LOOP_SHIFT 2
+#define I2S1_INT_LOOP_MASK 0x02
+#define I2S1_INT_LOOP_SHIFT 1
+#define I2S1_EXT_LOOP_MASK 0x01
+#define I2S1_EXT_LOOP_SHIFT 0
+
+#define INTERFACE_SWAP 0x63
+#define RX_SWAP0_MASK 0x10
+#define RX_SWAP0_SHIFT 4
+#define RX_SWAP1_MASK 0x08
+#define RX_SWAP1_SHIFT 3
+#define IF_SWAP_MASK 0x04
+#define IF_SWAP_SHIFT 2
+#define IO_SWAP0_MASK 0x02
+#define IO_SWAP0_SHIFT 1
+#define IO_SWAP1_MASK 0x01
+#define IO_SWAP1_SHIFT 0
+
+#endif /* AB3550_CODEC_REGISTERS_H */
diff --git a/sound/soc/u8500/Kconfig b/sound/soc/u8500/Kconfig
new file mode 100755
index 00000000000..2e008a7031e
--- /dev/null
+++ b/sound/soc/u8500/Kconfig
@@ -0,0 +1,13 @@
+#
+# U8500 SoC audio configuration
+#
+
+config SND_SOC_U8500
+ bool "U8500"
+ depends on SND_SOC
+ default y
+ help
+ Support for sound devices specific to ARM architectures.
+ Drivers that are implemented on ASoC can be found in
+ "ALSA for SoC audio support" section.
+
diff --git a/sound/soc/u8500/Makefile b/sound/soc/u8500/Makefile
new file mode 100755
index 00000000000..438c58f2ecd
--- /dev/null
+++ b/sound/soc/u8500/Makefile
@@ -0,0 +1,12 @@
+# U8500 Platform Support
+
+snd-soc-u8500-objs := u8500_pcm.o u8500_msp_dai.o
+snd-soc-u8500-ab3550-objs := mop500_ab3550.o
+
+obj-$(CONFIG_SND_SOC_U8500) += snd-soc-u8500.o
+obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-u8500-ab3550.o
+
+
+
+
+
diff --git a/sound/soc/u8500/mop500_ab3550.c b/sound/soc/u8500/mop500_ab3550.c
new file mode 100755
index 00000000000..6eec2191350
--- /dev/null
+++ b/sound/soc/u8500/mop500_ab3550.c
@@ -0,0 +1,170 @@
+/*
+ *
+ * sound/soc/u8500/u8500_ab3550.c
+ *
+ *
+ * Copyright (C) 2010 Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * ALSA SoC core support for the ab3550 chip.
+ * Author:
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/io.h>
+
+// SOC framework
+#include <sound/soc.h>
+
+// Platform driver
+#include "u8500_pcm.h"
+#include "u8500_msp_dai.h"
+
+// AB3550 codec
+extern struct snd_soc_dai ab3550_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ab3550;
+
+
+
+
+// Create the snd_soc_dai_link struct ---------------------------------------
+
+static int u8500_ab3550_startup(struct snd_pcm_substream *substream)
+{
+ printk(KERN_DEBUG "MOP500_AB3550: u8500_ab3550_startup\n");
+
+ return 0;
+}
+
+static void u8500_ab3550_shutdown(struct snd_pcm_substream *substream)
+{
+ printk(KERN_DEBUG "MOP500_AB3550: u8500_ab3550_shutdown\n");
+}
+
+static int u8500_ab3550_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+ printk(KERN_DEBUG "MOP500_AB3550: u8500_ab3550_hw_params\n");
+
+ return 0;
+}
+
+static struct snd_soc_ops u8500_ab3550_ops = {
+ .startup = u8500_ab3550_startup,
+ .shutdown = u8500_ab3550_shutdown,
+ .hw_params = u8500_ab3550_hw_params,
+};
+
+struct snd_soc_dai_link u8500_ab3550_dai[2] = {
+ {
+ .name = "ab3550_0",
+ .stream_name = "ab3550_0",
+ .cpu_dai = &u8500_msp_dai[0],
+ .codec_dai = &ab3550_codec_dai,
+ .init = NULL,
+ .ops = &u8500_ab3550_ops,
+ },
+ {
+ .name = "ab3550_1",
+ .stream_name = "ab3550_1",
+ .cpu_dai = &u8500_msp_dai[1],
+ .codec_dai = &ab3550_codec_dai,
+ .init = NULL,
+ .ops = &u8500_ab3550_ops,
+ },
+};
+EXPORT_SYMBOL(u8500_ab3550_dai);
+
+
+
+// Create the snd_soc_device struct ---------------------------------------
+
+static struct snd_soc_card u8500_ab3550 = {
+ .name = "u8500-ab3550",
+ .probe = NULL,
+ .dai_link = u8500_ab3550_dai,
+ .num_links = 2,
+ .platform = &u8500_soc_platform,
+};
+
+struct snd_soc_device u8500_ab3550_snd_devdata = {
+ .card = &u8500_ab3550,
+ .codec_dev = &soc_codec_dev_ab3550,
+};
+EXPORT_SYMBOL(u8500_ab3550_snd_devdata);
+
+
+
+// Machine driver init and exit --------------------------------------------
+
+static struct platform_device *u8500_ab3550_snd_device;
+
+static int __init u8500_ab3550_init(void)
+{
+ int ret, i;
+ struct snd_soc_device *socdev;
+ struct snd_soc_codec *codec;
+
+ printk(KERN_DEBUG "MOP500_AB3550: u8500_ab3550_init\n");
+
+ // Register platform
+ printk(KERN_DEBUG "MOP500_AB3550: Register platform.\n");
+ ret = snd_soc_register_platform(&u8500_soc_platform);
+ if (ret < 0)
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to register platform.\n");
+
+ printk(KERN_DEBUG "MOP500_AB3550: Register codec dai.\n");
+ snd_soc_register_dai(&ab3550_codec_dai);
+ if (ret < 0)
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to register codec dai.\n");
+
+ for (i = 0; i < U8500_NBR_OF_DAI; i++) {
+ printk(KERN_DEBUG "MOP500_AB3550: Register MSP dai %d.\n", i);
+ ret = snd_soc_register_dai(&u8500_msp_dai[i]);
+ if (ret < 0)
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to register MSP dai %d.\n", i);
+ }
+
+ // Allocate platform device
+ printk(KERN_DEBUG "MOP500_AB3550: Allocate platform device.\n");
+ u8500_ab3550_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!u8500_ab3550_snd_device)
+ return -ENOMEM;
+
+ // Set platform drvdata
+ printk(KERN_DEBUG "MOP500_AB3550: Set platform drvdata.\n");
+ platform_set_drvdata(u8500_ab3550_snd_device, &u8500_ab3550_snd_devdata);
+ if (ret < 0)
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to set drvdata.\n");
+
+ // Add platform device
+ printk(KERN_DEBUG "MOP500_AB3550: Add device.\n");
+ u8500_ab3550_snd_devdata.dev = &u8500_ab3550_snd_device->dev;
+ ret = platform_device_add(u8500_ab3550_snd_device);
+ if (ret) {
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to add platform device.\n");
+ platform_device_put(u8500_ab3550_snd_device);
+ }
+
+ // Register IS2-driver
+ printk(KERN_DEBUG "MOP500_AB3550: Register I2S-driver.\n");
+ ret = u8500_platform_registerI2S();
+ if (ret < 0)
+ printk(KERN_DEBUG "MOP500_AB3550: Error: Failed to register I2S-driver.\n");
+
+ return ret;
+}
+
+
+
+static void __exit u8500_ab3550_exit(void)
+{
+ printk(KERN_ALERT "MOP500_AB35500: u8500_ab3550_exit\n");
+
+ platform_device_unregister(u8500_ab3550_snd_device);
+}
+
+module_init(u8500_ab3550_init);
+module_exit(u8500_ab3550_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/u8500/u8500_msp_dai.c b/sound/soc/u8500/u8500_msp_dai.c
new file mode 100755
index 00000000000..585ecc825d5
--- /dev/null
+++ b/sound/soc/u8500/u8500_msp_dai.c
@@ -0,0 +1,504 @@
+/*
+ *
+ * sound/soc/u8500/u8500_msp_dai.c
+ *
+ *
+ * Copyright (C) 2007 Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * ALSA SoC I2S driver for the u8500 platforms.
+ */
+
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <asm/dma.h>
+#include "u8500_msp_dai.h"
+#include "u8500_pcm.h"
+
+#include <mach/msp.h>
+#include <linux/i2s/i2s.h>
+
+struct u8500_dai_private_t{
+ spinlock_t lock;
+ struct i2s_device *i2s;
+ u8 rx_active;
+ u8 tx_active;
+};
+
+struct u8500_dai_private_t msp_dai_private[U8500_NBR_OF_DAI] =
+{
+ /* DAI 0 */
+ {
+ .lock = SPIN_LOCK_UNLOCKED,
+ .i2s = NULL,
+ .tx_active = 0,
+ .rx_active = 0,
+ },
+ /* DAI 1 */
+ {
+ .lock = SPIN_LOCK_UNLOCKED,
+ .i2s = NULL,
+ .tx_active = 0,
+ .rx_active = 0,
+ },
+};
+
+static void compile_msp_config(struct msp_generic_config *msp_config,struct snd_pcm_substream *substream)
+{
+ int channels;
+
+ msp_config->tx_clock_sel = 0;
+ msp_config->rx_clock_sel = 0;
+ msp_config->tx_frame_sync_sel = 0;
+ msp_config->rx_frame_sync_sel = 0;
+ msp_config->input_clock_freq = MSP_INPUT_FREQ_48MHZ;
+ msp_config->srg_clock_sel = 0;
+ msp_config->rx_frame_sync_pol = RX_FIFO_SYNC_HI;
+ msp_config->tx_frame_sync_pol = TX_FIFO_SYNC_HI;
+ msp_config->rx_fifo_config = RX_FIFO_ENABLE;
+ msp_config->tx_fifo_config = TX_FIFO_ENABLE;
+ msp_config->spi_clk_mode = SPI_CLK_MODE_NORMAL;
+ msp_config->spi_burst_mode = 0;
+ msp_config->handler = u8500_pcm_dma_eot_handler;
+ msp_config->tx_callback_data = substream;
+ msp_config->tx_data_enable = 0;
+ msp_config->rx_callback_data = substream;
+ msp_config->loopback_enable = 0;
+ msp_config->multichannel_configured = 0;
+ msp_config->def_elem_len = 0;
+ msp_config->default_protocol_desc = 0;
+ msp_config->protocol_desc.rx_phase_mode = MSP_SINGLE_PHASE;
+ msp_config->protocol_desc.tx_phase_mode = MSP_SINGLE_PHASE;
+ msp_config->protocol_desc.rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ msp_config->protocol_desc.tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ msp_config->protocol_desc.rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ msp_config->protocol_desc.tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ msp_config->protocol_desc. rx_frame_length_1 = MSP_FRAME_LENGTH_2;
+ msp_config->protocol_desc. rx_frame_length_2 = MSP_FRAME_LENGTH_2;
+ msp_config->protocol_desc.tx_frame_length_1 = MSP_FRAME_LENGTH_2;
+ msp_config->protocol_desc.tx_frame_length_2 = MSP_FRAME_LENGTH_2;
+ msp_config->protocol_desc. rx_element_length_1 = MSP_ELEM_LENGTH_32;
+ msp_config->protocol_desc.rx_element_length_2 = MSP_ELEM_LENGTH_32;
+ msp_config->protocol_desc.tx_element_length_1 = MSP_ELEM_LENGTH_32;
+ msp_config->protocol_desc.tx_element_length_2 = MSP_ELEM_LENGTH_32;
+ msp_config->protocol_desc.rx_data_delay = MSP_DELAY_0;
+ msp_config->protocol_desc.tx_data_delay = MSP_DELAY_0;
+ msp_config->protocol_desc.rx_clock_pol = MSP_FALLING_EDGE;
+ msp_config->protocol_desc.tx_clock_pol = MSP_RISING_EDGE;
+ msp_config->protocol_desc. rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_HIGH;
+ msp_config->protocol_desc.tx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_HIGH;
+ msp_config->protocol_desc.rx_half_word_swap = MSP_HWS_NO_SWAP;
+ msp_config->protocol_desc.tx_half_word_swap = MSP_HWS_NO_SWAP;
+ msp_config->protocol_desc.compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ msp_config->protocol_desc.expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ msp_config->protocol_desc.spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ msp_config->protocol_desc.spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ msp_config->protocol_desc.frame_sync_ignore = MSP_FRAME_SYNC_IGNORE;
+ msp_config->protocol_desc.frame_period = 63;
+ msp_config->protocol_desc.frame_width = 31;
+ msp_config->protocol_desc.total_clocks_for_one_frame = 64;
+ msp_config->protocol = MSP_I2S_PROTOCOL; //MSP_PCM_PROTOCOL
+ msp_config->direction = MSP_BOTH_T_R_MODE;
+ msp_config->frame_size = 0;
+ msp_config->frame_freq = 48000;
+ channels = 2;
+ msp_config->data_size = (channels == 1) ? MSP_DATA_SIZE_16BIT : MSP_DATA_SIZE_32BIT;
+ msp_config->work_mode = MSP_DMA_MODE;
+ msp_config->multichannel_configured = 1;
+ msp_config->multichannel_config.tx_multichannel_enable = 1;
+ msp_config->multichannel_config.tx_channel_0_enable = 0x0000003; //Channel 1 and channel 2
+ msp_config->multichannel_config.tx_channel_1_enable = 0x0000000; //Channel 1 and channel 2
+ msp_config->multichannel_config.tx_channel_2_enable = 0x0000000; //Channel 1 and channel 2
+ msp_config->multichannel_config.tx_channel_3_enable = 0x0000000; //Channel 1 and channel 2
+ msp_config->multichannel_config.rx_multichannel_enable = 1;
+ msp_config->multichannel_config.rx_channel_0_enable = 0x0000003; //Channel 1 and channel 2
+ msp_config->multichannel_config.rx_channel_1_enable = 0x0000000; //Channel 1 and channel 2
+ msp_config->multichannel_config.rx_channel_2_enable = 0x0000000; //Channel 1 and channel 2
+ msp_config->multichannel_config.rx_channel_3_enable = 0x0000000; //Channel 1 and channel 2
+}
+
+static void u8500_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *msp_dai)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct u8500_dai_private_t *dai_private;
+
+ printk(KERN_ALERT "STW4500: u8500_dai_shutdown\n");
+
+ if (!(dai_private = msp_dai->private_data))
+ return;
+
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ /* Mark the stopped direction as inactive */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dai_private->tx_active = 0;
+ } else {
+ dai_private->rx_active = 0;
+ }
+
+ if (0 == dai_private->tx_active && 0 == dai_private->rx_active) {
+ msp_dai->private_data = NULL;
+
+
+ ret = i2s_cleanup(dai_private->i2s->controller, DISABLE_ALL);
+
+ if (ret)
+ printk(KERN_WARNING "Error closing i2s\n");
+
+ } else {
+
+ if(dai_private->tx_active)
+ ret = i2s_cleanup(dai_private->i2s->controller, DISABLE_RECEIVE);
+ else
+ ret = i2s_cleanup(dai_private->i2s->controller, DISABLE_TRANSMIT);
+
+ if (ret)
+ printk(KERN_WARNING "Error closing i2s\n");
+ }
+
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+
+ return;
+}
+
+static int u8500_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *msp_dai)
+{
+ struct u8500_dai_private_t *dai_private;
+
+ printk(KERN_ALERT "STW4500: u8500_dai_startup\n");
+
+ dai_private=&msp_dai_private[msp_dai->id];
+ /* Store pointer to private data */
+ msp_dai->private_data = dai_private;
+
+ if (dai_private->i2s == NULL)
+ {
+ printk(KERN_ALERT "u8500_pcm_open: Error: i2sdrv.i2s == NULL.\n");
+ return -1;
+ }
+ if (dai_private->i2s->controller == NULL)
+ {
+ printk(KERN_ALERT "u8500_pcm_open: Error: i2sdrv.i2s->controller == NULL.\n");
+ return -1;
+ }
+
+
+ return 0;
+}
+
+static int u8500_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *msp_dai)
+{
+ int ret = 0;
+ unsigned long flags_private;
+ struct u8500_dai_private_t *dai_private;
+ struct msp_generic_config msp_config;
+
+ printk(KERN_ALERT "STW4500: u8500_dai_prepare\n");
+
+ dai_private = msp_dai->private_data;
+
+ spin_lock_irqsave(&dai_private->lock, flags_private);
+
+ /* Is the other direction already active? */
+ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ dai_private->rx_active) ||
+ (substream->stream == SNDRV_PCM_STREAM_CAPTURE &&
+ dai_private->tx_active)) {
+ /* TODO: Check if sample rate match ! */
+ /*
+ if ( != runtime->rate) {
+ printk(KERN_ERR "CPU_DAI configuration mismatch with already opened stream!\n");
+ ret = -EBUSY;
+ goto cleanup;
+ }
+ */
+ } else if (!dai_private->rx_active && !dai_private->tx_active) {
+ ret = 0;
+
+ compile_msp_config(&msp_config,substream);
+
+ ret = i2s_setup(dai_private->i2s->controller, &msp_config);
+ if (ret < 0) {
+ printk(KERN_ALERT "u8500_dai_prepare: i2s_setup failed! ret = %d\n", ret);
+ goto cleanup;
+ }
+ }
+
+ /* Mark the newly started stream as active */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dai_private->tx_active = 1;
+ } else {
+ dai_private->rx_active = 1;
+ }
+
+cleanup:
+ spin_unlock_irqrestore(&dai_private->lock, flags_private);
+ return ret;
+}
+
+static int u8500_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *msp_dai)
+{
+ printk(KERN_ALERT "STW4500: u8500_dai_hw_params\n");
+
+ return 0;
+}
+
+static int u8500_dai_set_dai_fmt(struct snd_soc_dai *msp_dai, unsigned int fmt)
+{
+ printk(KERN_ALERT "STW4500: u8500_dai_set_dai_fmt\n");
+ return 0;
+}
+
+static int u8500_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *msp_dai)
+{
+ unsigned long flags;
+ int ret = 0;
+ struct u8500_dai_private_t *u8500_dai_private = msp_dai->private_data;
+
+ printk(KERN_ALERT "STW4500: u8500_dai_trigger\n");
+
+ spin_lock_irqsave(&u8500_dai_private->lock, flags);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&u8500_dai_private->lock, flags);
+ return ret;
+}
+
+int u8500_i2s_send_data(void *data, size_t bytes, int dai_idx)
+{
+ unsigned long flags;
+ struct u8500_dai_private_t *dai_private;
+ struct i2s_message message;
+ struct i2s_device *i2s_dev;
+ int ret = 0;
+ dai_private=&msp_dai_private[dai_idx];
+
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ i2s_dev=dai_private->i2s;
+
+
+ if(!dai_private->tx_active)
+ {
+ printk(KERN_ERR "u8500_i2s_send_data: I2S controller not available\n");
+ goto cleanup;
+ }
+
+ message.txbytes = bytes;
+ message.txdata = data;
+ message.rxbytes = 0;
+ message.rxdata = NULL;
+ message.dma_flag = 1;
+
+ ret = i2s_transfer(i2s_dev->controller, &message);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "u8500_i2s_send_data: Error: i2s_transfer failed!\n");
+ goto cleanup;
+ }
+
+cleanup:
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+ return ret;
+}
+
+int u8500_i2s_receive_data(void *data, size_t bytes, int dai_idx)
+{
+ unsigned long flags;
+ struct u8500_dai_private_t *dai_private;
+ struct i2s_message message;
+ struct i2s_device *i2s_dev;
+ int ret = 0;
+ dai_private=&msp_dai_private[dai_idx];
+
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ i2s_dev=dai_private->i2s;
+
+
+ if(!dai_private->rx_active)
+ {
+ printk(KERN_ERR "u8500_i2s_receive_data: I2S controller not available\n");
+ goto cleanup;
+ }
+
+ message.rxbytes = bytes;
+ message.rxdata = data;
+ message.txbytes = 0;
+ message.txdata = NULL;
+ message.dma_flag = 1;
+
+ ret = i2s_transfer(i2s_dev->controller, &message);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "u8500_i2s_receive_data: Error: i2s_transfer failed!\n");
+ goto cleanup;
+ }
+
+cleanup:
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+ return ret;
+}
+
+#define u8500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define u8500_I2S_FORMATS ( SNDRV_PCM_FMTBIT_S16_LE )
+
+#define dai_suspend NULL
+#define dai_resume NULL
+#define u8500_dai_set_dai_sysclk NULL
+
+struct snd_soc_dai u8500_msp_dai[U8500_NBR_OF_DAI] =
+{
+ {
+ .name = "u8500_i2s-0",
+ .id = 0,
+ //.type = SND_SOC_DAI_I2S,
+ .suspend = dai_suspend,
+ .resume = dai_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = u8500_I2S_RATES,
+ .formats = u8500_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = u8500_I2S_RATES,
+ .formats = u8500_I2S_FORMATS,},
+ .ops = {
+ .set_sysclk=u8500_dai_set_dai_sysclk,
+ .set_fmt=u8500_dai_set_dai_fmt,
+ .startup = u8500_dai_startup,
+ .shutdown = u8500_dai_shutdown,
+ .prepare = u8500_dai_prepare,
+ .trigger = u8500_dai_trigger,
+ .hw_params = u8500_dai_hw_params,
+ },
+ .private_data = &msp_dai_private[0],
+ },
+ {
+ .name = "u8500_i2s-1",
+ .id = 1,
+ //.type = SND_SOC_DAI_I2S,
+ .suspend = dai_suspend,
+ .resume = dai_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = u8500_I2S_RATES,
+ .formats = u8500_I2S_FORMATS,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = u8500_I2S_RATES,
+ .formats = u8500_I2S_FORMATS,},
+ .ops = {
+ .set_sysclk=u8500_dai_set_dai_sysclk,
+ .set_fmt=u8500_dai_set_dai_fmt,
+ .startup = u8500_dai_startup,
+ .shutdown = u8500_dai_shutdown,
+ .prepare = u8500_dai_prepare,
+ .trigger = u8500_dai_trigger,
+ .hw_params = u8500_dai_hw_params,
+ },
+ .private_data = &msp_dai_private[1],
+ },
+};
+
+EXPORT_SYMBOL(u8500_msp_dai);
+
+
+
+static int i2sdrv_probe(struct i2s_device *i2s)
+{
+ unsigned long flags;
+ struct u8500_dai_private_t *u8500_dai_private;
+
+ u8500_dai_private=&msp_dai_private[0];
+ printk(KERN_DEBUG "i2sdrv_probe: Enter.\n");
+
+ spin_lock_irqsave(&u8500_dai_private->lock, flags);
+ u8500_dai_private->i2s = i2s;
+ spin_unlock_irqrestore(&u8500_dai_private->lock, flags);
+
+ try_module_get(i2s->controller->dev.parent->driver->owner);
+ i2s_set_drvdata(i2s, (void*)u8500_dai_private);
+
+ return 0;
+}
+
+
+
+static int i2sdrv_remove(struct i2s_device *i2s)
+{
+ unsigned long flags;
+ struct u8500_dai_private_t *u8500_dai_private = i2s_get_drvdata(i2s);
+
+ printk(KERN_DEBUG "U8500_PCM: i2sdrv_remove\n");
+
+ spin_lock_irqsave(&u8500_dai_private->lock, flags);
+
+ u8500_dai_private->i2s = NULL;
+ i2s_set_drvdata(i2s, NULL);
+ spin_unlock_irqrestore(&u8500_dai_private->lock, flags);
+
+
+ printk(KERN_ALERT "i2sdrv_remove: module_put\n");
+ module_put(i2s->controller->dev.parent->driver->owner);
+
+ return 0;
+}
+
+
+
+static const struct i2s_device_id dev_id_table[] = {
+ { "i2s_device.0", 0, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2s, acodec_id_table);
+
+
+
+static struct i2s_driver i2sdrv_i2s = {
+ .driver = {
+ .name = "nomadik_acodec",
+ .owner = THIS_MODULE,
+ },
+ .probe = i2sdrv_probe,
+ .remove = __devexit_p(i2sdrv_remove),
+ .id_table = dev_id_table,
+};
+
+int u8500_platform_registerI2S(void)
+{
+ int ret;
+
+ printk(KERN_ALERT "u8500_platform_registerI2S: Register I2S-driver.\n");
+ ret = i2s_register_driver(&i2sdrv_i2s);
+ if (ret < 0) {
+ printk(KERN_ALERT "u8500_platform_registerI2S: Unable to register I2S-driver\n");
+ }
+
+ return ret;
+}
diff --git a/sound/soc/u8500/u8500_msp_dai.h b/sound/soc/u8500/u8500_msp_dai.h
new file mode 100755
index 00000000000..ab821924ffc
--- /dev/null
+++ b/sound/soc/u8500/u8500_msp_dai.h
@@ -0,0 +1,25 @@
+#ifndef U8500_msp_dai_H
+#define U8500_msp_dai_H
+
+/*
+ *
+ * sound/soc/u8500/u8500_msp_dai.h
+ *
+ *
+ * Copyright (C) 2007 Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * ALSA SoC I2S driver for the U8500 platforms.
+ */
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+
+#define U8500_NBR_OF_DAI 2
+
+extern struct snd_soc_dai u8500_msp_dai[U8500_NBR_OF_DAI];
+
+int u8500_platform_registerI2S(void);
+int u8500_i2s_send_data(void *data, size_t bytes, int dai_idx);
+int u8500_i2s_receive_data(void *data, size_t bytes, int dai_idx);
+
+#endif
diff --git a/sound/soc/u8500/u8500_pcm.c b/sound/soc/u8500/u8500_pcm.c
new file mode 100755
index 00000000000..dc44eb29f76
--- /dev/null
+++ b/sound/soc/u8500/u8500_pcm.c
@@ -0,0 +1,424 @@
+/*
+ *
+ * sound/soc/u8500/u8500_pcm.c
+ *
+ *
+ * Copyright (C) 2010 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * ALSA SoC PCM driver for the u8500 platforms.
+ */
+
+#include <asm/page.h>
+
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "u8500_pcm.h"
+#include "u8500_msp_dai.h"
+
+
+
+// Local variables -------------------------------------------------------------------
+
+// struct dma_chan
+struct u8500_pcm_transfer {
+ spinlock_t lock;
+ dma_addr_t addr;
+ u32 dma_chan_id;
+ u32 period;
+ struct dma_chan *dma_chan;
+};
+
+static struct snd_pcm_hardware u8500_pcm_hw_playback = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = U8500_PLATFORM_MIN_RATE_PLAYBACK,
+ .rate_max = U8500_PLATFORM_MAX_RATE_PLAYBACK,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct snd_pcm_hardware u8500_pcm_hw_capture = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = U8500_PLATFORM_MIN_RATE_CAPTURE,
+ .rate_max = U8500_PLATFORM_MAX_RATE_CAPTURE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct u8500_pcm_private_t u8500_pcm_private;
+
+
+
+// DMA operations -----------------------------------------------------------------------------
+
+/**
+ * nomadik_alsa_dma_start - used to transmit or recive a dma chunk
+ * @stream - specifies the playback/record stream structure
+ */
+static void u8500_pcm_dma_start(struct snd_pcm_substream *substream)
+{
+ unsigned int offset, dma_size;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int stream_id = substream->pstr->stream;
+ u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
+
+ printk(KERN_DEBUG "u8500_pcm_dma_start: Enter.\n");
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * stream_p->period;
+ stream_p->old_offset = offset;
+ printk(KERN_DEBUG "u8500_pcm_dma_start: address = %x size=%d\n", (runtime->dma_addr + offset), dma_size);
+
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ u8500_i2s_send_data((void*)(runtime->dma_addr + offset), dma_size, 0);
+ }
+ else {
+ u8500_i2s_receive_data((void *)(runtime->dma_addr + offset), dma_size, 0);
+ }
+
+ stream_p->period++;
+ stream_p->period %= runtime->periods;
+ stream_p->periods++;
+}
+
+static void u8500_pcm_dma_hw_free(struct device *dev, struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+ if (runtime->dma_area == NULL)
+ return;
+
+ if (buf != &substream->dma_buffer) {
+ dma_free_coherent(buf->dev.dev, buf->bytes, buf->area, buf->addr);
+ kfree(runtime->dma_buffer_p);
+ }
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+}
+
+/**
+ * dma_eot_handler
+ * @data - pointer to structure set in the dma callback handler
+ * @event - specifies the DMA event: transfer complete/error
+ *
+ * This is the PCM tasklet handler linked to a pipe, its role is to tell
+ * the PCM middler layer whene the buffer position goes across the prescribed
+ * period size.To inform of this the snd_pcm_period_elapsed is called.
+ *
+ * this callback will be called in case of DMA_EVENT_TC only
+ */
+irqreturn_t u8500_pcm_dma_eot_handler(void *data, int irq)
+{
+ struct snd_pcm_substream *substream = data;
+ int stream_id = substream->pstr->stream;
+ u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
+
+ /* snd_pcm_period_elapsed() is _not_ to be protected
+ */
+ printk(KERN_DEBUG "u8500_pcm_dma_eot_handler: Enter.\n");
+
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+ if(stream_p->state == ALSA_STATE_PAUSED)
+ return IRQ_HANDLED;
+ if (stream_p->active == 1) {
+ u8500_pcm_dma_start(substream);
+ }
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(u8500_pcm_dma_eot_handler);
+
+
+
+
+// snd_pcm_ops -----------------------------------------------------------------------------
+
+static int u8500_pcm_open(struct snd_pcm_substream *substream)
+{
+ int status;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int stream_id = substream->pstr->stream;
+ u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
+
+ printk(KERN_DEBUG "u8500_pcm_open: Enter.\n");
+
+ status = 0;
+ printk(KERN_DEBUG "u8500_pcm_open: Setting HW-config.\n");
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+ {
+ runtime->hw = u8500_pcm_hw_playback;
+ }
+ else
+ {
+ runtime->hw = u8500_pcm_hw_capture;
+ }
+
+ init_MUTEX(&(stream_p->alsa_sem));
+ init_completion(&(stream_p->alsa_com));
+ stream_p->state = ALSA_STATE_RUNNING;
+ printk(KERN_ALERT "u8500_pcm_open: Exit.\n");
+
+ return 0;
+}
+
+static int u8500_pcm_close(struct snd_pcm_substream *substream)
+{
+ printk(KERN_DEBUG "u8500_pcm_close: Enter.\n");
+
+ return 0;
+}
+
+static int u8500_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+ int ret = 0;
+ int size;
+
+ printk(KERN_DEBUG "u8500_pcm_hw_params: Enter.\n");
+
+ size = params_buffer_bytes(hw_params);
+
+ if (buf) {
+ if (buf->bytes >= size)
+ goto out;
+ u8500_pcm_dma_hw_free(NULL, substream);
+ }
+
+ if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) {
+ buf = &substream->dma_buffer;
+ } else {
+ buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL);
+ if (!buf)
+ goto nomem;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = NULL;
+ buf->area = dma_alloc_coherent(NULL, size, &buf->addr, GFP_KERNEL);
+ buf->bytes = size;
+ buf->private_data = NULL;
+
+ if (!buf->area)
+ goto free;
+ }
+ snd_pcm_set_runtime_buffer(substream, buf);
+ ret = 1;
+ out:
+ runtime->dma_bytes = size;
+ return ret;
+
+ free:
+ kfree(buf);
+ nomem:
+ return -ENOMEM;
+}
+
+static int u8500_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ printk(KERN_ALERT "u8500_pcm_hw_free: Enter.\n");
+
+ u8500_pcm_dma_hw_free(NULL, substream);
+
+ return 0;
+}
+
+static int u8500_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ printk(KERN_DEBUG "u8500_pcm_prepare: Enter.\n");
+
+ return 0;
+}
+
+static int u8500_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int stream_id = substream->pstr->stream;
+ u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
+
+ printk(KERN_DEBUG "u8500_pcm_trigger: Enter.\n");
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ // Start DMA transfer
+ printk(KERN_DEBUG "u8500_pcm_trigger: TRIGGER START\n");
+ if (stream_p->active == 0) {
+ stream_p->active = 1;
+ u8500_pcm_dma_start(substream);
+ break;
+ }
+ printk(KERN_DEBUG "u8500_pcm_trigger: Error: H/w is busy\n");
+ return -EINVAL;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ // Pause DMA transfer
+ printk(KERN_ALERT "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_PAUSE\n");
+ if (stream_p->state == ALSA_STATE_RUNNING) {
+ stream_p->state = ALSA_STATE_PAUSED;
+ }
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ // Resume DMA transfer
+ printk(KERN_DEBUG "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_RESUME\n");
+ if (stream_p->state == ALSA_STATE_PAUSED) {
+ stream_p->state = ALSA_STATE_RUNNING;
+ u8500_pcm_dma_start(substream);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ // Stop DMA transfer
+ printk(KERN_DEBUG "u8500_pcm_trigger: SNDRV_PCM_TRIGGER_STOP\n");
+ if (stream_p->active == 1) {
+ stream_p->active = 0;
+ stream_p->period = 0;
+ }
+ break;
+
+ default:
+ printk(KERN_ERR "u8500_pcm_trigger: Invalid command in pcm trigger.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t u8500_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned int offset;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int stream_id = substream->pstr->stream;
+ u8500_pcm_stream_t* stream_p = &u8500_pcm_private.streams[stream_id];
+
+ printk(KERN_DEBUG "u8500_pcm_pointer: Enter.\n");
+
+ offset = bytes_to_frames(runtime, stream_p->old_offset);
+ if (offset < 0 || stream_p->old_offset < 0)
+ printk(KERN_DEBUG "u8500_pcm_pointer: Offset=%i %i\n", offset, stream_p->old_offset);
+
+ return offset;
+}
+
+static int u8500_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ printk(KERN_DEBUG "u8500_pcm_mmap: Enter.\n");
+
+ return dma_mmap_coherent(NULL, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops u8500_pcm_ops = {
+ .open = u8500_pcm_open,
+ .close = u8500_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = u8500_pcm_hw_params,
+ .hw_free = u8500_pcm_hw_free,
+ .prepare = u8500_pcm_prepare,
+ .trigger = u8500_pcm_trigger,
+ .pointer = u8500_pcm_pointer,
+ .mmap = u8500_pcm_mmap
+};
+
+
+
+// snd_soc_platform -----------------------------------------------------------------------------
+
+int u8500_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int i;
+
+ printk(KERN_DEBUG "u8500_pcm_new: Enter.\n");
+
+ printk(KERN_DEBUG "u8500_pcm_new: pcm = %d\n", (int)pcm);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "nomadik_asoc");
+
+ for (i = 0; i < NO_OF_STREAMS; i++)
+ {
+ ;
+ u8500_pcm_private.streams[i].active = 0;
+ u8500_pcm_private.streams[i].period = 0;
+ u8500_pcm_private.streams[i].periods = 0;
+ u8500_pcm_private.streams[i].old_offset = 0;
+ }
+
+ printk(KERN_DEBUG "u8500_pcm_new: pcm->name = %s\n", pcm->name);
+
+ return 0;
+}
+
+static void u8500_pcm_free(struct snd_pcm *pcm)
+{
+ printk(KERN_DEBUG "u8500_pcm_free: Enter.\n");
+
+ //u8500_pcm_dma_hw_free(NULL, substream);
+}
+
+static int u8500_pcm_suspend(struct snd_soc_dai *dai)
+{
+ printk(KERN_DEBUG "u8500_pcm_suspend: Enter.\n");
+
+ return 0;
+}
+
+static int u8500_pcm_resume(struct snd_soc_dai *dai)
+{
+ printk(KERN_DEBUG "u8500_pcm_resume: Enter.\n");
+
+ return 0;
+}
+
+struct snd_soc_platform u8500_soc_platform = {
+ .name = "u8500-audio",
+ .pcm_ops = &u8500_pcm_ops,
+ .pcm_new = u8500_pcm_new,
+ .pcm_free = u8500_pcm_free,
+ .suspend = u8500_pcm_suspend,
+ .resume = u8500_pcm_resume,
+};
+EXPORT_SYMBOL(u8500_soc_platform);
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sound/soc/u8500/u8500_pcm.h b/sound/soc/u8500/u8500_pcm.h
new file mode 100755
index 00000000000..b5423af1cbb
--- /dev/null
+++ b/sound/soc/u8500/u8500_pcm.h
@@ -0,0 +1,53 @@
+/*
+ *
+ * sound/soc/u8500/u8500_pcm.h
+ *
+ *
+ * Copyright (C) 2010 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * ALSA SoC core support for the stw4500 chip.
+ */
+
+#ifndef U8500_PCM_H
+#define U8500_PCM_H
+
+#include <mach/msp.h>
+
+#define NMDK_BUFFER_SIZE (64*1024)
+
+#define U8500_PLATFORM_MIN_RATE_PLAYBACK 48000
+#define U8500_PLATFORM_MAX_RATE_PLAYBACK 48000
+#define U8500_PLATFORM_MIN_RATE_CAPTURE 48000
+#define U8500_PLATFORM_MAX_RATE_CAPTURE 48000
+#define U8500_PLATFORM_MAX_NO_OF_RATES 1
+
+#define NUMBER_OUTPUT_DEVICE 5
+#define NUMBER_INPUT_DEVICE 10
+
+#define NO_OF_STREAMS 2
+
+enum alsa_state {
+ ALSA_STATE_PAUSED,
+ ALSA_STATE_RUNNING
+};
+
+extern struct snd_soc_platform u8500_soc_platform;
+
+typedef struct {
+ int stream_id; /* stream identifier */
+ int active; /* we are using this stream for transfer now */
+ int period; /* current transfer period */
+ int periods; /* current count of periods registerd in the DMA engine */
+ enum alsa_state state;
+ unsigned int old_offset;
+ struct semaphore alsa_sem;
+ struct completion alsa_com;
+} u8500_pcm_stream_t;
+
+struct u8500_pcm_private_t {
+ u8500_pcm_stream_t streams[2];
+};
+
+irqreturn_t u8500_pcm_dma_eot_handler(void *data, int irq);
+
+#endif