diff options
author | Ola Lilja <ola.o.lilja@stericsson.com> | 2010-01-20 11:37:28 +0100 |
---|---|---|
committer | John Rigby <john.rigby@linaro.org> | 2010-09-02 22:43:43 -0600 |
commit | 9dcf1ca851c3a18c8fe4e586ea848b85a8ba9d45 (patch) | |
tree | 327e01af4d0dc8123657712ae967956b2dae777b /sound | |
parent | e0c67890aa1b309162559414514f4b090fbbb57e (diff) |
New ASoC driver for Fairbanks.
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/Kconfig | 2 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/codecs/Kconfig | 7 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
-rwxr-xr-x | sound/soc/codecs/ab3550.c | 1011 | ||||
-rwxr-xr-x | sound/soc/codecs/ab3550.h | 291 | ||||
-rwxr-xr-x | sound/soc/u8500/Kconfig | 13 | ||||
-rwxr-xr-x | sound/soc/u8500/Makefile | 12 | ||||
-rwxr-xr-x | sound/soc/u8500/mop500_ab3550.c | 170 | ||||
-rwxr-xr-x | sound/soc/u8500/u8500_msp_dai.c | 504 | ||||
-rwxr-xr-x | sound/soc/u8500/u8500_msp_dai.h | 25 | ||||
-rwxr-xr-x | sound/soc/u8500/u8500_pcm.c | 424 | ||||
-rwxr-xr-x | sound/soc/u8500/u8500_pcm.h | 53 |
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 |