summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorJustin Waters <justin.waters@timesys.com>2012-03-21 13:28:20 -0400
committerJustin Waters <justin.waters@timesys.com>2012-03-21 13:28:20 -0400
commitd0183eb2433e3332c2720637238b18b1fdff7946 (patch)
tree36be0be2c433789656750da0ca5991250fc7d3e7 /sound
parent74fca6a42863ffacaf7ba6f1936a9f228950f657 (diff)
Add support for the i.MX28 EVK
This patch was originally put together in January 2011 by Roshni.
Diffstat (limited to 'sound')
-rw-r--r--sound/arm/Kconfig49
-rw-r--r--sound/arm/Makefile11
-rw-r--r--sound/arm/mxc-alsa-common.h68
-rw-r--r--sound/arm/mxc-alsa-mixer.c410
-rw-r--r--sound/arm/mxc-alsa-pmic.c3790
-rw-r--r--sound/arm/mxc-alsa-pmic.h110
-rw-r--r--sound/arm/mxc-alsa-spdif.c2260
-rw-r--r--sound/soc/Kconfig3
-rw-r--r--sound/soc/Makefile3
-rw-r--r--sound/soc/codecs/Kconfig37
-rw-r--r--sound/soc/codecs/Makefile18
-rw-r--r--sound/soc/codecs/ak4647.c799
-rw-r--r--sound/soc/codecs/ak4647.h92
-rw-r--r--sound/soc/codecs/ak5702.c607
-rw-r--r--sound/soc/codecs/ak5702.h130
-rw-r--r--sound/soc/codecs/bluetooth.c161
-rw-r--r--sound/soc/codecs/cs42888.c1196
-rw-r--r--sound/soc/codecs/cs42888.h31
-rw-r--r--sound/soc/codecs/mxs-adc-codec.c1220
-rw-r--r--sound/soc/codecs/mxs-adc-codec.h87
-rw-r--r--sound/soc/codecs/mxs_spdif.c454
-rw-r--r--sound/soc/codecs/mxs_spdif.h172
-rw-r--r--sound/soc/codecs/sgtl5000.c1295
-rw-r--r--sound/soc/codecs/sgtl5000.h406
-rw-r--r--sound/soc/codecs/stmp378x_codec.c1051
-rw-r--r--sound/soc/codecs/stmp378x_codec.h87
-rw-r--r--sound/soc/codecs/stmp3xxx_spdif.c455
-rw-r--r--sound/soc/codecs/stmp3xxx_spdif.h37
-rw-r--r--sound/soc/codecs/wm8350.c39
-rw-r--r--sound/soc/codecs/wm8580.c166
-rw-r--r--sound/soc/codecs/wm8580.h9
-rw-r--r--sound/soc/imx/Kconfig84
-rw-r--r--sound/soc/imx/Makefile27
-rw-r--r--sound/soc/imx/imx-3stack-ak4647.c445
-rw-r--r--sound/soc/imx/imx-3stack-ak5702.c225
-rw-r--r--sound/soc/imx/imx-3stack-bt.c256
-rw-r--r--sound/soc/imx/imx-3stack-bt.h21
-rw-r--r--sound/soc/imx/imx-3stack-cs42888.c410
-rw-r--r--sound/soc/imx/imx-3stack-sgtl5000.c689
-rw-r--r--sound/soc/imx/imx-3stack-wm8350.c696
-rw-r--r--sound/soc/imx/imx-3stack-wm8580.c433
-rw-r--r--sound/soc/imx/imx-ac97.c564
-rw-r--r--sound/soc/imx/imx-esai.c709
-rw-r--r--sound/soc/imx/imx-esai.h314
-rw-r--r--sound/soc/imx/imx-pcm.c688
-rw-r--r--sound/soc/imx/imx-pcm.h83
-rw-r--r--sound/soc/imx/imx-ssi.c784
-rw-r--r--sound/soc/imx/imx-ssi.h244
-rw-r--r--sound/soc/mxs/Kconfig51
-rw-r--r--sound/soc/mxs/Makefile16
-rw-r--r--sound/soc/mxs/mxs-adc.c453
-rw-r--r--sound/soc/mxs/mxs-adc.h21
-rw-r--r--sound/soc/mxs/mxs-dai.c658
-rw-r--r--sound/soc/mxs/mxs-dai.h42
-rw-r--r--sound/soc/mxs/mxs-devb-spdif.c90
-rw-r--r--sound/soc/mxs/mxs-devb.c296
-rw-r--r--sound/soc/mxs/mxs-evk-adc.c198
-rw-r--r--sound/soc/mxs/mxs-pcm.c506
-rw-r--r--sound/soc/mxs/mxs-pcm.h31
-rw-r--r--sound/soc/mxs/mxs-spdif-dai.c203
-rw-r--r--sound/soc/mxs/mxs-spdif-dai.h21
-rw-r--r--sound/soc/stmp3xxx/Kconfig31
-rw-r--r--sound/soc/stmp3xxx/Makefile12
-rw-r--r--sound/soc/stmp3xxx/stmp3780_devb.c92
-rw-r--r--sound/soc/stmp3xxx/stmp3780_devb_spdif.c95
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_dai.c257
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_dai.h21
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_pcm.c461
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_pcm.h30
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c207
-rw-r--r--sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h21
71 files changed, 25719 insertions, 19 deletions
diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig
index 885683a3b0bd..ae88981852b0 100644
--- a/sound/arm/Kconfig
+++ b/sound/arm/Kconfig
@@ -39,5 +39,54 @@ config SND_PXA2XX_AC97
Say Y or M if you want to support any AC97 codec attached to
the PXA2xx AC97 interface.
+config SND_MXC_SPDIF
+ tristate "MXC SPDIF sound card spport"
+ select SND_PCM
+ help
+ Say Y here to enable SPDIF sound card
+
+config SND_MXC_PMIC
+ tristate "MXC PMIC sound system"
+ depends on ARCH_MXC && MXC_DAM && MXC_SSI && \
+ (MXC_MC13783_AUDIO || MXC_PMIC_SC55112_AUDIO)
+ default y
+ select SND_PCM
+ help
+ Say Y here to include support for soundcards based on the
+ MC13783 chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-mc13783.
+
+config SND_MXC_PLAYBACK_MIXING
+ bool "Playback Stream Mixing"
+ depends on (!ARCH_MX27) && (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \
+ (MXC_MC13783_AUDIO)
+ default n
+ select SND_PCM
+ help
+ Say Y here to include support mixing for soundcards based on the
+ MC13783 chip. This supports audio stream mixing on VCODEC for mc13783 based platforms.
+ Analog mixng as well as Digital mixing can be tested on these platforms.
+ As of now , mixing of mono files only are supported in Digital Mixing since it is done on VCODEC.
+ SSI 2 channel mode is used to mix 2 streams on a single SSI. This is supported on all platforms except imx27ads(imx27ads - Analog mixing only).
+
+config HEADSET_DETECT_ENABLE
+ bool "Headset Detect Enable"
+ depends on (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \
+ (MXC_MC13783_AUDIO)
+ default n
+ select SND_PCM
+ help
+ Say Y here to enable Headset Detect Feature.
+
+config SND_MXC_PMIC_IRAM
+ bool "MXC PMIC sound system supports IRAM"
+ depends on SND_MXC_PMIC && SDMA_IRAM
+ default n
+ select SND_PCM
+ help
+ It will use IRAM as the DMA buffer of ALSA playback.
+
endif # SND_ARM
diff --git a/sound/arm/Makefile b/sound/arm/Makefile
index 5a549ed6c8aa..58569cc2db11 100644
--- a/sound/arm/Makefile
+++ b/sound/arm/Makefile
@@ -14,3 +14,14 @@ snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o
obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o
snd-pxa2xx-ac97-objs := pxa2xx-ac97.o
+
+#
+# Define the header file locations for PMIC drivers.
+#
+CFLAGS_mxc-alsa-pmic.o = -I$(srctree)/drivers/mxc
+obj-$(CONFIG_SND_MXC_PMIC) += snd-mxc-alsa.o
+snd-mxc-alsa-objs := mxc-alsa-pmic.o mxc-alsa-mixer.o
+
+CFLGS_mxc_alsa_spdif.o = -I$(TOPDIR)/drivers/mxc
+obj-$(CONFIG_SND_MXC_SPDIF) += snd-spdif.o
+snd-spdif-objs := mxc-alsa-spdif.o
diff --git a/sound/arm/mxc-alsa-common.h b/sound/arm/mxc-alsa-common.h
new file mode 100644
index 000000000000..863c0432510b
--- /dev/null
+++ b/sound/arm/mxc-alsa-common.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+ /*!
+ * @file mxc-alsa-common.h
+ * @brief
+ * @ingroup SOUND_DRV
+ */
+
+#ifndef __MXC_ALSA_COMMON_H__
+#define __MXC_ALSA_COMMON_H__
+
+/* Enums typically used by the Mixer support APIs
+ * Emunerates IP, OP and mixer sources.
+ */
+
+typedef enum {
+ CODEC_DIR_OUT,
+ MIXER_OUT
+} OUTPUT_SOURCE;
+
+typedef enum {
+ OP_NODEV = -1,
+ OP_EARPIECE,
+ OP_HANDSFREE,
+ OP_HEADSET,
+ OP_LINEOUT,
+ OP_MAXDEV,
+ OP_MONO
+} OUTPUT_DEVICES;
+
+typedef enum {
+ IP_NODEV = -1,
+ IP_HANDSET,
+ IP_HEADSET,
+ IP_LINEIN,
+ IP_MAXDEV
+} INPUT_DEVICES;
+
+extern int mxc_alsa_create_ctl(struct snd_card *card, void *p_value);
+
+extern int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src,
+ OUTPUT_DEVICES dev, bool enable);
+extern int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume,
+ OUTPUT_DEVICES dev);
+extern int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev,
+ bool enable);
+extern int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode);
+extern int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val);
+extern int set_mixer_output_balance(int bal);
+
+extern int get_mixer_output_device(void);
+extern int get_mixer_output_volume(void);
+extern int get_mixer_output_mono_adder(void);
+extern int get_mixer_output_balance(void);
+extern int get_mixer_input_gain(void);
+extern int get_mixer_input_device(void);
+#endif /* __MXC_ALSA_COMMON_H__ */
diff --git a/sound/arm/mxc-alsa-mixer.c b/sound/arm/mxc-alsa-mixer.c
new file mode 100644
index 000000000000..c77120cf48eb
--- /dev/null
+++ b/sound/arm/mxc-alsa-mixer.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+ /*!
+ * @file mxc-alsa-mixer.c
+ * @brief this file implements the mxc sound driver mixer interface for ALSA.
+ * The mxc sound driver supports mono/stereo recording (there are
+ * some limitations due to hardware), mono/stereo playback and
+ * audio mixing. This file implements output switching, volume/balance controls
+ * mono adder config, I/P dev switching and gain on the PCM streams.
+ * Recording supports 8000 khz and 16000 khz sample rate.
+ * Playback supports 8000, 11025, 16000, 22050, 24000, 32000,
+ * 44100 and 48000 khz for mono and stereo.
+ *
+ * @ingroup SOUND_DRV
+ */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <linux/soundcard.h>
+#include <mach/pmic_audio.h>
+#include "mxc-alsa-common.h"
+/*!
+ * These are the functions implemented in the ALSA PCM driver that
+ * are used for mixer operations
+ *
+ */
+
+/*!
+ * These are the callback functions for mixer controls
+ *
+ */
+/* Output device control*/
+static int pmic_mixer_output_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 15;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+static int pmic_mixer_output_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int dev, i;
+ dev = uvalue->value.integer.value[0];
+ for (i = OP_EARPIECE; i < OP_MAXDEV; i++) {
+ if (dev & (1 << i)) {
+ set_mixer_output_device(NULL, MIXER_OUT, i, 1);
+ } else {
+ set_mixer_output_device(NULL, MIXER_OUT, i, 0);
+ }
+ }
+ return 0;
+}
+static int pmic_mixer_output_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int val, ret = 0, i = 0;
+ for (i = OP_EARPIECE; i < OP_MAXDEV; i++) {
+ val = get_mixer_output_device();
+ if (val & SOUND_MASK_PHONEOUT)
+ ret = ret | 1;
+ if (val & SOUND_MASK_SPEAKER)
+ ret = ret | 2;
+ if (val & SOUND_MASK_VOLUME)
+ ret = ret | 4;
+ if (val & SOUND_MASK_PCM)
+ ret = ret | 8;
+ uvalue->value.integer.value[0] = ret;
+ }
+ return 0;
+
+}
+
+/* Input gain control*/
+static int pmic_cap_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+static int pmic_cap_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int val;
+ val = get_mixer_input_gain();
+ val = val & 0xFF;
+ uvalue->value.integer.value[0] = val;
+ return 0;
+}
+
+static int pmic_cap_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+
+ int vol;
+ vol = uvalue->value.integer.value[0];
+ vol = vol | (vol << 8);
+ set_mixer_input_gain(NULL, vol);
+ return 0;
+}
+
+/* Mono adder control*/
+static int pmic_pb_monoconfig_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 3;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+static int pmic_pb_monoconfig_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int mono;
+ mono = uvalue->value.integer.value[0];
+ set_mixer_output_mono_adder(mono);
+ return 0;
+}
+static int pmic_pb_monoconfig_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.integer.value[0] = get_mixer_output_mono_adder();
+ return 0;
+}
+
+/*!
+ * These are the ALSA control structures with init values
+ *
+ */
+
+/* Input device control*/
+static int pmic_cap_input_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 7;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+static int pmic_cap_input_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int dev, i;
+ dev = uvalue->value.integer.value[0];
+ for (i = IP_HANDSET; i < IP_MAXDEV; i++) {
+ if (dev & (1 << i)) {
+ set_mixer_input_device(NULL, i, 1);
+ } else {
+ set_mixer_input_device(NULL, i, 0);
+ }
+ }
+ return 0;
+}
+static int pmic_cap_input_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int val, ret = 0, i = 0;
+ for (i = IP_HANDSET; i < IP_MAXDEV; i++) {
+ val = get_mixer_input_device();
+ if (val & SOUND_MASK_PHONEIN)
+ ret = ret | 1;
+ if (val & SOUND_MASK_MIC)
+ ret = ret | 2;
+ if (val & SOUND_MASK_LINE)
+ ret = ret | 4;
+ uvalue->value.integer.value[0] = ret;
+ }
+ return 0;
+}
+
+/* Volume control*/
+static int pmic_pb_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int volume;
+ volume = uvalue->value.integer.value[0];
+ volume = volume | (volume << 8);
+ set_mixer_output_volume(NULL, volume, OP_NODEV);
+ return 0;
+}
+static int pmic_pb_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int pmic_pb_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int val;
+ val = get_mixer_output_volume();
+ val = val & 0xFF;
+ uvalue->value.integer.value[0] = val;
+ return 0;
+}
+
+/* Balance control start */
+static int pmic_pb_balance_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int pmic_pb_balance_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.integer.value[0] = get_mixer_output_balance();
+ return 0;
+
+}
+static int pmic_pb_balance_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int bal;
+ bal = uvalue->value.integer.value[0];
+ set_mixer_output_balance(bal);
+ return 0;
+}
+
+/* Balance control end */
+
+/* loopback control start */
+static int pmic_loopback_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int pmic_loopback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.integer.value[0] = kcontrol->private_value;
+ return 0;
+
+}
+static int pmic_loopback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int changed;
+ long flag = uvalue->value.integer.value[0];
+ changed =
+ (uvalue->value.integer.value[0] == kcontrol->private_value) ? 0 : 1;
+ kcontrol->private_value = uvalue->value.integer.value[0];
+ if (flag)
+ pmic_audio_fm_output_enable(true);
+ else
+ pmic_audio_fm_output_enable(false);
+
+ return changed;
+}
+
+/* Loopback control end */
+
+/* Kcontrol structure definitions */
+struct snd_kcontrol_new pmic_control_pb_vol __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Volume",
+ .index = 0x00,
+ .info = pmic_pb_volume_info,
+ .get = pmic_pb_volume_get,
+ .put = pmic_pb_volume_put,
+ .private_value = 0xffab1,
+};
+
+struct snd_kcontrol_new pmic_control_pb_bal __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Balance Playback Volume",
+ .index = 0x00,
+ .info = pmic_pb_balance_info,
+ .get = pmic_pb_balance_get,
+ .put = pmic_pb_balance_put,
+ .private_value = 0xffab2,
+};
+struct snd_kcontrol_new pmic_control_pb_monoconfig __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Monoconfig Playback Volume",
+ .index = 0x00,
+ .info = pmic_pb_monoconfig_info,
+ .get = pmic_pb_monoconfig_get,
+ .put = pmic_pb_monoconfig_put,
+ .private_value = 0xffab2,
+};
+struct snd_kcontrol_new pmic_control_op_sw __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Output Playback Volume",
+ .index = 0x00,
+ .info = pmic_mixer_output_info,
+ .get = pmic_mixer_output_get,
+ .put = pmic_mixer_output_put,
+ .private_value = 0xffab4,
+};
+
+struct snd_kcontrol_new pmic_control_cap_vol __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Capture Volume",
+ .index = 0x00,
+ .info = pmic_cap_volume_info,
+ .get = pmic_cap_volume_get,
+ .put = pmic_cap_volume_put,
+ .private_value = 0xffab5,
+};
+struct snd_kcontrol_new pmic_control_ip_sw __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Input Capture Volume",
+ .index = 0x00,
+ .info = pmic_cap_input_info,
+ .get = pmic_cap_input_get,
+ .put = pmic_cap_input_put,
+ .private_value = 0xffab5,
+};
+
+struct snd_kcontrol_new pmic_control_loop_out __devinitdata = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Loopback Line-in",
+ .index = 0x00,
+ .info = pmic_loopback_info,
+ .get = pmic_loopback_get,
+ .put = pmic_loopback_put,
+ .private_value = 0,
+};
+
+/*!
+ * This function registers the control components of ALSA Mixer
+ * It is called by ALSA PCM init.
+ *
+ * @param card pointer to the ALSA sound card structure.
+ *
+ * @return 0 on success, -ve otherwise.
+ */
+int __devinit mxc_alsa_create_ctl(struct snd_card *card, void *p_value)
+{
+ int err = 0;
+
+ if ((err =
+ snd_ctl_add(card, snd_ctl_new1(&pmic_control_op_sw, p_value))) < 0)
+ return err;
+
+ if ((err =
+ snd_ctl_add(card,
+ snd_ctl_new1(&pmic_control_pb_vol, p_value))) < 0)
+ return err;
+ if ((err =
+ snd_ctl_add(card,
+ snd_ctl_new1(&pmic_control_pb_monoconfig,
+ p_value))) < 0)
+ return err;
+ if ((err =
+ snd_ctl_add(card,
+ snd_ctl_new1(&pmic_control_pb_bal, p_value))) < 0)
+ return err;
+ if ((err =
+ snd_ctl_add(card,
+ snd_ctl_new1(&pmic_control_cap_vol, p_value))) < 0)
+ return err;
+ if ((err =
+ snd_ctl_add(card, snd_ctl_new1(&pmic_control_ip_sw, p_value))) < 0)
+ return err;
+ err = snd_ctl_add(card, snd_ctl_new1(&pmic_control_loop_out, p_value));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+EXPORT_SYMBOL(mxc_alsa_create_ctl);
diff --git a/sound/arm/mxc-alsa-pmic.c b/sound/arm/mxc-alsa-pmic.c
new file mode 100644
index 000000000000..2d24f458925a
--- /dev/null
+++ b/sound/arm/mxc-alsa-pmic.c
@@ -0,0 +1,3790 @@
+/*
+ * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+ /*!
+ * @defgroup SOUND_DRV MXC Sound Driver for ALSA
+ */
+
+ /*!
+ * @file mxc-alsa-pmic.c
+ * @brief this fle mxc-alsa-pmic.c
+ * @brief this file implements the mxc sound driver interface for ALSA.
+ * The mxc sound driver supports mono/stereo recording (there are
+ * some limitations due to hardware), mono/stereo playback and
+ * audio mixing.
+ * Recording supports 8000 khz and 16000 khz sample rate.
+ * Playback supports 8000, 11025, 16000, 22050, 24000, 32000,
+ * 44100, 48000 and 96000 Hz for mono and stereo.
+ * This file also handles the software mixer and abstraction APIs
+ * that control the volume,balance,mono-adder,input and output
+ * devices for PMIC.
+ * These mixer controls shall be accessible thru alsa as well as
+ * OSS emulation modes
+ *
+ * @ingroup SOUND_DRV
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/soundcard.h>
+#include <linux/pmic_external.h>
+#include <linux/pm.h>
+#include <linux/fs.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+#include <mach/pmic_audio.h>
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+
+#include <ssi/ssi.h>
+#include <ssi/registers.h>
+#include <dam/dam.h>
+
+#include "mxc-alsa-pmic.h"
+#include "mxc-alsa-common.h"
+
+/*
+ * PMIC driver buffer policy.
+ * Customize here if the sound is not correct
+ */
+#define MAX_BUFFER_SIZE (32*1024)
+#define DMA_BUF_SIZE (8*1024)
+#define MIN_PERIOD_SIZE 64
+#define MIN_PERIOD 2
+#define MAX_PERIOD 255
+
+#define AUD_MUX_CONF 0x0031010
+#define MASK_2_TS 0xfffffffc
+#define MASK_1_TS 0xfffffffd
+#define MASK_1_TS_MIX 0xfffffffc
+#define MASK_1_TS_STDAC 0xfffffffe
+#define MASK_1_TS_REC 0xfffffffe
+#define SOUND_CARD_NAME "MXC"
+
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+#define MAX_IRAM_SIZE (IRAM_SIZE - CONFIG_SDMA_IRAM_SIZE)
+#define DMA_IRAM_SIZE (4*1024)
+#define ADMA_BASE_PADDR (IRAM_BASE_ADDR + CONFIG_SDMA_IRAM_SIZE)
+#define ADMA_BASE_VADDR (IRAM_BASE_ADDR_VIRT + CONFIG_SDMA_IRAM_SIZE)
+
+#if (MAX_IRAM_SIZE + CONFIG_SDMA_IRAM_SIZE) > IRAM_SIZE
+#error "The IRAM size required has beyond the limitation of IC spec"
+#endif
+
+#if (MAX_IRAM_SIZE&(DMA_IRAM_SIZE-1))
+#error "The IRAM size for DMA ring buffer should be multiples of dma buffer size"
+#endif
+
+#endif /* CONFIG_SND_MXC_PMIC_IRAM */
+
+/*!
+ * These defines enable DMA chaining for playback
+ * and capture respectively.
+ */
+#define MXC_SOUND_PLAYBACK_CHAIN_DMA_EN 1
+#define MXC_SOUND_CAPTURE_CHAIN_DMA_EN 1
+
+/*!
+ * ID for this card
+ */
+static char *id = NULL;
+
+#define MXC_ALSA_MAX_PCM_DEV 3
+#define MXC_ALSA_MAX_PLAYBACK 3
+#define MXC_ALSA_MAX_CAPTURE 1
+
+struct mxc_audio_platform_data *audio_data;
+/*!
+ * This structure is the global configuration of the soundcard
+ * that are accessed by the mixer as well as by the playback/recording
+ * stream. This contains various volume, balance, mono adder settings
+ *
+ */
+typedef struct audio_mixer_control {
+
+ /*!
+ * This variable holds the current active output device(s)
+ */
+ int output_device;
+
+ /*!
+ * This variable holds the current active input device.
+ */
+ int input_device;
+
+ /* Used only for playback/recording on codec .. Use 1 for playback
+ * and 0 for recording*/
+ int direction;
+
+ /*!
+ * This variable holds the current source for active ouput device(s)
+ */
+ OUTPUT_SOURCE source_for_output[OP_MAXDEV];
+
+ /*!
+ * This variable says if a given output device is part of an ongoing
+ * playback. This variable will be set and reset by the playback stream
+ * when stream is activated and when stream is closed. This shall also
+ * be set and reset my mixer functions for enabling/disabling output devs
+ */
+ int output_active[OP_MAXDEV];
+
+ /*!
+ * This variable holds the current volume for active input device.
+ * This maps to the input gain of recording device
+ */
+ int input_volume;
+
+ /*!
+ * This variable holds the current volume for playback devices.
+ */
+ //int output_volume[OP_MAXDEV];
+ int master_volume_out;
+
+ /*!
+ * This variable holds the balance setting for the mixer out.
+ * The range is 0 to 100. 50 means both L and R equal.
+ * < 50 attenuates left side and > 50 attenualtes right side
+ */
+ int mixer_balance;
+
+ /*!
+ * This variable holds the current mono adder config.
+ */
+ PMIC_AUDIO_MONO_ADDER_MODE mixer_mono_adder;
+
+ /*!
+ * Semaphore used to control the access to this structure.
+ */
+ struct semaphore sem;
+
+ /*!
+ * These variables are set by PCM stream and mixer when the voice codec's / ST dac's outputs are
+ * connected to the analog mixer of PMIC audio chip
+ */
+ int codec_out_to_mixer;
+ int stdac_out_to_mixer;
+
+ int codec_playback_active;
+ int codec_capture_active;
+ int stdac_playback_active;
+ int mixing_active;
+
+ /*!
+ * This variable holds the configuration of the headset which was previously enabled.
+ */
+ int old_prof;
+
+ PMIC_AUDIO_HANDLE stdac_handle;
+ PMIC_AUDIO_HANDLE voice_codec_handle;
+
+} audio_mixer_control_t;
+
+/*!
+ * This structure stores current state of audio configuration
+ * soundcard wrt a specific stream (playback on different DACs, recording on the codec etc).
+ * It is used to set/get current values and are NOT accessed by the Mixer. This structure shall
+ * be retrieved thru pcm substream pointer and hence the mixer component will have no access
+ * to it. There will be as many structures as the number of streams. In our case it's 3. Codec playback
+ * STDAC playback and voice codec recording.
+ * This structure will be used at the beginning of activating a stream to configure audio chip.
+ *
+ */
+typedef struct pmic_audio_device {
+
+ PMIC_AUDIO_HANDLE handle;
+ /*!
+ * This variable holds the sample rate currently being used.
+ */
+ int sample_rate;
+
+ /*!
+ * This variable holds the current protocol PMIC is using.
+ * PMIC can use one of three protocols at any given time:
+ * normal, network and I2S.
+ */
+ int protocol;
+
+ /*!
+ * This variables tells us whether PMIC runs in
+ * master mode (PMIC generates audio clocks)or slave mode (AP side
+ * generates audio clocks)
+ *
+ * Currently the default mode is master mode because PMIC clocks have
+ * higher precision.
+ */
+ int mode;
+
+ /* This variable holds the value representing the
+ * base clock PMIC will use to generate internal
+ * clocks (BCL clock and FrameSync clock)
+ */
+ int pll;
+
+ /*!
+ * This variable holds the SSI to which PMIC is currently connected.
+ */
+ int ssi;
+
+ /*!
+ * This variable tell us whether bit clock is inverted or not.
+ */
+ int bcl_inverted;
+
+ /*!
+ * This variable tell us whether frame clock is inverted or not.
+ */
+ int fs_inverted;
+
+ /*!
+ * This variable holds the pll used for PMIC audio operations.
+ */
+ int pll_rate;
+
+ /*!
+ * This variable holds the filter that PMIC is applying to
+ * CODEC operations.
+ */
+ int codec_filter;
+
+} pmic_audio_device_t;
+
+/*!
+ * This structure represents an audio stream in term of
+ * channel DMA, HW configuration on PMIC and on AudioMux/SSI
+ */
+typedef struct audio_stream {
+ /*!
+ * identification string
+ */
+ char *id;
+
+ /*!
+ * numeric identification
+ */
+ int stream_id;
+
+ /*!
+ * SSI ID on the ARM side
+ */
+ int ssi;
+
+ /*!
+ * DAM port on the ARM side
+ */
+ int dam_port;
+
+ /*!
+ * device identifier for DMA
+ */
+ int dma_wchannel;
+
+ /*!
+ * we are using this stream for transfer now
+ */
+ int active:1;
+
+ /*!
+ * current transfer period
+ */
+ int period;
+
+ /*!
+ * current count of transfered periods
+ */
+ int periods;
+
+ /*!
+ * are we recording - flag used to do DMA trans. for sync
+ */
+ int tx_spin;
+
+ /*!
+ * Previous offset value for resume
+ */
+ unsigned int old_offset;
+#if 0
+ /*!
+ * Path for this stream
+ */
+ device_data_t stream_device;
+#endif
+
+ /*!
+ * pmic audio chip stream specific configuration
+ */
+ pmic_audio_device_t pmic_audio_device;
+
+ /*!
+ * for locking in DMA operations
+ */
+ spinlock_t dma_lock;
+
+ /*!
+ * Alsa substream pointer
+ */
+ struct snd_pcm_substream *stream;
+} audio_stream_t;
+
+/*!
+ * This structure represents the PMIC sound card with its
+ * 2 streams (StDac and Codecs) and its shared parameters
+ */
+typedef struct snd_card_mxc_pmic_audio {
+ /*!
+ * ALSA sound card handle
+ */
+ struct snd_card *card;
+
+ /*!
+ * ALSA pcm driver type handle
+ */
+ struct snd_pcm *pcm[MXC_ALSA_MAX_PCM_DEV];
+
+ /*!
+ * playback & capture streams handle
+ * We can support a maximum of two playback streams (voice-codec
+ * and ST-DAC) and 1 recording stream
+ */
+ audio_stream_t s[MXC_ALSA_MAX_CAPTURE + MXC_ALSA_MAX_PLAYBACK];
+
+} mxc_pmic_audio_t;
+
+/*!
+ * pmic audio chip parameters for IP/OP and volume controls
+ */
+audio_mixer_control_t audio_mixer_control;
+
+/*!
+ * Global variable that represents the PMIC soundcard
+ * with its 2 availables stream devices: stdac and codec
+ */
+mxc_pmic_audio_t *mxc_audio = NULL;
+
+/*!
+ * Supported playback rates array
+ */
+static unsigned int playback_rates_stereo[] = {
+ 8000,
+ 11025,
+ 12000,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 96000,
+};
+
+static unsigned int playback_rates_mono[] = {
+ 8000,
+ 16000,
+};
+
+/*!
+ * Supported capture rates array
+ */
+static unsigned int capture_rates[] = {
+ 8000,
+ 16000,
+};
+
+/*!
+ * this structure represents the sample rates supported
+ * by PMIC for playback operations on StDac.
+ */
+static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = {
+ .count = ARRAY_SIZE(playback_rates_stereo),
+ .list = playback_rates_stereo,
+ .mask = 0,
+};
+
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+static spinlock_t g_audio_iram_lock = SPIN_LOCK_UNLOCKED;
+static int g_audio_iram_en = 1;
+static int g_device_opened = 0;
+extern void flush_cache_range(struct vm_area_struct *vma, unsigned long start,
+ unsigned long end);
+
+static inline int mxc_snd_enable_iram(int enable)
+{
+ int ret = -EBUSY;
+ unsigned long flags;
+ spin_lock_irqsave(&g_audio_iram_lock, flags);
+ if (!g_device_opened) {
+ g_audio_iram_en = (enable != 0);
+ ret = 0;
+ }
+ spin_unlock_irqrestore(&g_audio_iram_lock, flags);
+ return ret;
+}
+
+static inline void mxc_snd_pcm_iram_get(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&g_audio_iram_lock, flags);
+ g_audio_iram_en++;
+ spin_unlock_irqrestore(&g_audio_iram_lock, flags);
+}
+
+static inline void mxc_snd_pcm_iram_put(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&g_audio_iram_lock, flags);
+ g_audio_iram_en--;
+ spin_unlock_irqrestore(&g_audio_iram_lock, flags);
+}
+
+struct snd_dma_buffer g_iram_dmab;
+
+#endif /* CONFIG_SND_MXC_PMIC_IRAM */
+
+/*!
+ * this structure represents the sample rates supported
+ * by PMIC for playback operations on Voice codec.
+ */
+static struct snd_pcm_hw_constraint_list hw_playback_rates_mono = {
+ .count = ARRAY_SIZE(playback_rates_mono),
+ .list = playback_rates_mono,
+ .mask = 0,
+};
+
+/*!
+ * this structure represents the sample rates supported
+ * by PMIC for capture operations on Codec.
+ */
+static struct snd_pcm_hw_constraint_list hw_capture_rates = {
+ .count = ARRAY_SIZE(capture_rates),
+ .list = capture_rates,
+ .mask = 0,
+};
+
+#ifdef CONFIG_HEADSET_DETECT_ENABLE
+static PMIC_HS_STATE hs_state;
+
+/*!
+ *This is used to maintain the state of the Headset*/
+static int headset_state = 0;
+
+/*Callback for headset event. */
+static void HSCallback(const PMIC_HS_STATE hs_st)
+{
+
+ msleep(10);
+
+ if (headset_state == 1) {
+ pmic_audio_output_disable_phantom_ground();
+ headset_state = 0;
+ if (audio_mixer_control.stdac_playback_active)
+ pmic_audio_output_clear_port(audio_mixer_control.
+ stdac_handle,
+ STEREO_HEADSET_LEFT |
+ STEREO_HEADSET_RIGHT);
+ else if (audio_mixer_control.voice_codec_handle)
+ pmic_audio_output_clear_port(audio_mixer_control.
+ voice_codec_handle,
+ STEREO_HEADSET_LEFT |
+ STEREO_HEADSET_RIGHT);
+
+ if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE,
+ 1);
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE,
+ 1);
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) {
+#ifdef CONFIG_HEADSET_DETECT_ENABLE
+ /*This is a temporary workaround which should be removed later */
+ set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1);
+#else
+ set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 1);
+#endif
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 1);
+ }
+
+ } else {
+ headset_state = 1;
+
+ pmic_audio_output_enable_phantom_ground();
+
+ audio_mixer_control.old_prof =
+ audio_mixer_control.output_device;
+
+ if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE,
+ 0);
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE,
+ 0);
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 0);
+ }
+ if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) {
+ set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 0);
+ }
+ /*This is a temporary workaround which should be removed later */
+ set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1);
+
+ }
+}
+
+#endif
+/*!
+ * This function configures audio multiplexer to support
+ * audio data routing in PMIC master mode.
+ *
+ * @param ssi SSI of the ARM to connect to the DAM.
+ */
+void configure_dam_pmic_master(int ssi)
+{
+ int source_port;
+ int target_port;
+
+ if (ssi == SSI1) {
+ pr_debug("DAM: port 1 -> port 4\n");
+ source_port = audio_data->src_port;
+
+ target_port = port_4;
+ } else {
+ pr_debug("DAM: port 2 -> port 5\n");
+ source_port = port_2;
+ target_port = port_5;
+ }
+
+ dam_reset_register(source_port);
+ dam_reset_register(target_port);
+
+ dam_select_mode(source_port, normal_mode);
+ dam_select_mode(target_port, internal_network_mode);
+
+ dam_set_synchronous(source_port, true);
+ dam_set_synchronous(target_port, true);
+
+ dam_select_RxD_source(source_port, target_port);
+ dam_select_RxD_source(target_port, source_port);
+
+ dam_select_TxFS_direction(source_port, signal_out);
+ dam_select_TxFS_source(source_port, false, target_port);
+
+ dam_select_TxClk_direction(source_port, signal_out);
+ dam_select_TxClk_source(source_port, false, target_port);
+
+ dam_select_RxFS_direction(source_port, signal_out);
+ dam_select_RxFS_source(source_port, false, target_port);
+
+ dam_select_RxClk_direction(source_port, signal_out);
+ dam_select_RxClk_source(source_port, false, target_port);
+
+ dam_set_internal_network_mode_mask(target_port, 0xfc);
+
+ writel(AUD_MUX_CONF, IO_ADDRESS(AUDMUX_BASE_ADDR) + 0x38);
+}
+
+/*!
+ * This function configures the SSI in order to receive audio
+ * from PMIC (recording). Configuration of SSI consists mainly in
+ * setting the following:
+ *
+ * 1) SSI to use (SSI1 or SSI2)
+ * 2) SSI mode (normal or network. We use always network mode)
+ * 3) SSI STCCR register settings, which control the sample rate (BCL and
+ * FS clocks)
+ * 4) Watermarks for SSI FIFOs as well as timeslots to be used.
+ * 5) Enable SSI.
+ *
+ * @param substream pointer to the structure of the current stream.
+ */
+void configure_ssi_rx(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ int ssi;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[substream->pstr->stream];
+ ssi = s->ssi;
+
+ pr_debug("configure_ssi_rx: SSI %d\n", ssi + 1);
+
+ ssi_enable(ssi, false);
+ ssi_synchronous_mode(ssi, true);
+ ssi_network_mode(ssi, true);
+
+ if (machine_is_mx27ads()) {
+ ssi_tx_clock_divide_by_two(ssi, 0);
+ ssi_tx_clock_prescaler(ssi, 0);
+ ssi_tx_frame_rate(ssi, 2);
+ }
+ /* OJO */
+ ssi_tx_frame_rate(ssi, 1);
+
+ ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before);
+ ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit);
+ ssi_tx_word_length(ssi, ssi_16_bits);
+
+ ssi_rx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before);
+ ssi_rx_frame_sync_length(ssi, ssi_frame_sync_one_bit);
+ ssi_rx_fifo_enable(ssi, ssi_fifo_0, true);
+ ssi_rx_bit0(ssi, true);
+
+ ssi_rx_fifo_full_watermark(ssi, ssi_fifo_0, RX_WATERMARK);
+
+ /* We never use the divider by 2 implemented in SSI */
+ ssi_rx_clock_divide_by_two(ssi, 0);
+
+ /* Set prescaler range (a fixed divide-by-eight prescaler
+ * in series with the variable prescaler) to 0 as we don't
+ * need it.
+ */
+ ssi_rx_clock_prescaler(ssi, 0);
+
+ /* Currently, only supported sample length is 16 bits */
+ ssi_rx_word_length(ssi, ssi_16_bits);
+
+ /* set direction of clocks ("externally" means that clocks come
+ * from PMIC to MCU)
+ */
+ ssi_rx_frame_direction(ssi, ssi_tx_rx_externally);
+ ssi_rx_clock_direction(ssi, ssi_tx_rx_externally);
+
+ /* Frame Rate Divider Control.
+ * In Normal mode, this ratio determines the word
+ * transfer rate. In Network mode, this ration sets
+ * the number of words per frame.
+ */
+ ssi_tx_frame_rate(ssi, 4);
+ ssi_rx_frame_rate(ssi, 4);
+
+ ssi_enable(ssi, true);
+}
+
+/*!
+ * This function configures the SSI in order to
+ * send data to PMIC. Configuration of SSI consists
+ * mainly in setting the following:
+ *
+ * 1) SSI to use (SSI1 or SSI2)
+ * 2) SSI mode (normal for normal use e.g. playback, network for mixing)
+ * 3) SSI STCCR register settings, which control the sample rate (BCL and
+ * FS clocks)
+ * 4) Watermarks for SSI FIFOs as well as timeslots to be used.
+ * 5) Enable SSI.
+ *
+ * @param substream pointer to the structure of the current stream.
+ */
+void configure_ssi_tx(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ struct snd_pcm_runtime *runtime;
+ int ssi;
+ int device, stream_id = -1;
+ device = substream->pcm->device;
+ if (device == 0)
+ stream_id = 0;
+ else if (device == 1)
+ stream_id = 2;
+ else
+ stream_id = 3;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[stream_id];
+ runtime = substream->runtime;
+ ssi = s->ssi;
+
+ pr_debug("configure_ssi_tx: SSI %d\n", ssi + 1);
+
+ ssi_enable(ssi, false);
+ ssi_synchronous_mode(ssi, true);
+ if (runtime->channels == 1) {
+ if (stream_id == 2) {
+ ssi_network_mode(ssi, true);
+ } else {
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+ ssi_network_mode(ssi, false);
+#endif
+ }
+ } else {
+ ssi_network_mode(ssi, true);
+ }
+
+#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING
+ ssi_two_channel_mode(ssi, true);
+ ssi_tx_fifo_enable(ssi, ssi_fifo_1, true);
+ ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_1, TX_WATERMARK);
+#endif
+
+ ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before);
+ ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit);
+ ssi_tx_fifo_enable(ssi, ssi_fifo_0, true);
+ ssi_tx_bit0(ssi, true);
+
+ ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_0, TX_WATERMARK);
+
+ /* We never use the divider by 2 implemented in SSI */
+ ssi_tx_clock_divide_by_two(ssi, 0);
+
+ ssi_tx_clock_prescaler(ssi, 0);
+
+ /*Currently, only supported sample length is 16 bits */
+ ssi_tx_word_length(ssi, ssi_16_bits);
+
+ /* clocks are being provided by PMIC */
+ ssi_tx_frame_direction(ssi, ssi_tx_rx_externally);
+ ssi_tx_clock_direction(ssi, ssi_tx_rx_externally);
+
+ if (runtime->channels == 1) {
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+ if (stream_id == 2) {
+ ssi_tx_frame_rate(ssi, 4);
+ } else {
+ ssi_tx_frame_rate(ssi, 1);
+ }
+#else
+ if (stream_id == 2) {
+ ssi_tx_frame_rate(ssi, 2);
+ }
+#endif
+
+ } else {
+ ssi_tx_frame_rate(ssi, 2);
+ }
+
+ ssi_enable(ssi, true);
+}
+
+/*!
+ * This function normalizes speed given by the user
+ * if speed is not supported, the function will
+ * calculate the nearest one.
+ *
+ * @param speed speed requested by the user.
+ *
+ * @return The normalized speed.
+ */
+int adapt_speed(int speed)
+{
+
+ /* speeds from 8k to 96k */
+ if (speed >= (64000 + 96000) / 2) {
+ speed = 96000;
+ } else if (speed >= (48000 + 64000) / 2) {
+ speed = 64000;
+ } else if (speed >= (44100 + 48000) / 2) {
+ speed = 48000;
+ } else if (speed >= (32000 + 44100) / 2) {
+ speed = 44100;
+ } else if (speed >= (24000 + 32000) / 2) {
+ speed = 32000;
+ } else if (speed >= (22050 + 24000) / 2) {
+ speed = 24000;
+ } else if (speed >= (16000 + 22050) / 2) {
+ speed = 22050;
+ } else if (speed >= (12000 + 16000) / 2) {
+ speed = 16000;
+ } else if (speed >= (11025 + 12000) / 2) {
+ speed = 12000;
+ } else if (speed >= (8000 + 11025) / 2) {
+ speed = 11025;
+ } else {
+ speed = 8000;
+ }
+ return speed;
+}
+
+/*!
+ * This function get values to be put in PMIC registers.
+ * This values represents the sample rate that PMIC
+ * should use for current playback or recording.
+ *
+ * @param substream pointer to the structure of the current stream.
+ */
+void normalize_speed_for_pmic(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ pmic_audio_device_t *pmic_device;
+ struct snd_pcm_runtime *runtime;
+ int device, stream_id = -1;
+ device = substream->pcm->device;
+ if (device == 0) {
+ if ((audio_mixer_control.codec_capture_active == 1)
+ && (substream->stream == 1)) {
+ stream_id = 1;
+ } else
+ stream_id = 0;
+ } else {
+ stream_id = 2;
+ }
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[stream_id];
+ pmic_device = &s->pmic_audio_device;
+ runtime = substream->runtime;
+
+ /* As the driver allows continuous sample rate, we must adapt the rate */
+ runtime->rate = adapt_speed(runtime->rate);
+
+ if (pmic_device->handle == audio_mixer_control.voice_codec_handle) {
+ switch (runtime->rate) {
+ case 8000:
+ pmic_device->sample_rate = VCODEC_RATE_8_KHZ;
+ break;
+ case 16000:
+ pmic_device->sample_rate = VCODEC_RATE_16_KHZ;
+ break;
+ default:
+ pmic_device->sample_rate = VCODEC_RATE_8_KHZ;
+ break;
+ }
+
+ } else if (pmic_device->handle == audio_mixer_control.stdac_handle) {
+ switch (runtime->rate) {
+ case 8000:
+ pmic_device->sample_rate = STDAC_RATE_8_KHZ;
+ break;
+
+ case 11025:
+ pmic_device->sample_rate = STDAC_RATE_11_025_KHZ;
+ break;
+
+ case 12000:
+ pmic_device->sample_rate = STDAC_RATE_12_KHZ;
+ break;
+
+ case 16000:
+ pmic_device->sample_rate = STDAC_RATE_16_KHZ;
+ break;
+
+ case 22050:
+ pmic_device->sample_rate = STDAC_RATE_22_050_KHZ;
+ break;
+
+ case 24000:
+ pmic_device->sample_rate = STDAC_RATE_24_KHZ;
+ break;
+
+ case 32000:
+ pmic_device->sample_rate = STDAC_RATE_32_KHZ;
+ break;
+
+ case 44100:
+ pmic_device->sample_rate = STDAC_RATE_44_1_KHZ;
+ break;
+
+ case 48000:
+ pmic_device->sample_rate = STDAC_RATE_48_KHZ;
+ break;
+
+ case 64000:
+ pmic_device->sample_rate = STDAC_RATE_64_KHZ;
+ break;
+
+ case 96000:
+ pmic_device->sample_rate = STDAC_RATE_96_KHZ;
+ break;
+
+ default:
+ pmic_device->sample_rate = STDAC_RATE_8_KHZ;
+ }
+ }
+
+}
+
+/*!
+ * This function configures number of channels for next audio operation
+ * (recording/playback) Number of channels define if sound is stereo
+ * or mono.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+void set_pmic_channels(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ struct snd_pcm_runtime *runtime;
+ int device = -1, stream_id = -1;
+
+ chip = snd_pcm_substream_chip(substream);
+ device = substream->pcm->device;
+
+ if (device == 0) {
+ if (substream->pstr->stream == 1) {
+ stream_id = 1;
+ } else {
+ stream_id = 0;
+ }
+ } else {
+ stream_id = 2;
+ }
+ s = &chip->s[stream_id];
+ runtime = substream->runtime;
+
+ if (runtime->channels == 2) {
+ ssi_tx_mask_time_slot(s->ssi, MASK_2_TS);
+ ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC);
+ } else {
+ if (stream_id == 2) {
+#ifdef CONFIG_MXC_PMIC_SC55112
+ ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_REC);
+#else
+
+ if (audio_mixer_control.mixing_active == 1)
+ ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_MIX);
+ else
+ ssi_tx_mask_time_slot(s->ssi, MASK_1_TS);
+
+#endif
+ } else {
+
+ ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_STDAC);
+ }
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+ ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC);
+#endif
+
+ }
+
+}
+
+/*!
+ * This function sets the input device in PMIC. It takes an
+ * ALSA value and modifies registers using pmic-specific values.
+ *
+ * @param handle Handle to the PMIC device opened
+ * @param val ALSA value. This value defines the input device that
+ * PMIC should activate to get audio signal (recording)
+ * @param enable Whether to enable or diable the input
+ */
+int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev,
+ bool enable)
+{
+
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ if (handle != NULL) {
+ if (audio_mixer_control.input_device & SOUND_MASK_PHONEIN) {
+ pmic_audio_vcodec_set_mic(handle, MIC1_LEFT,
+ MIC1_RIGHT_MIC_MONO);
+ pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS1);
+ } else {
+ pmic_audio_vcodec_set_mic_on_off(handle,
+ MIC1_LEFT,
+ MIC1_RIGHT_MIC_MONO);
+ pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS1);
+ }
+ if (audio_mixer_control.input_device & SOUND_MASK_MIC) {
+ pmic_audio_vcodec_set_mic(handle, NO_MIC, MIC2_AUX);
+ pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS2);
+ } else {
+ pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC,
+ MIC2_AUX);
+ pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS2);
+ }
+ if (audio_mixer_control.input_device & SOUND_MASK_LINE) {
+ pmic_audio_vcodec_set_mic(handle, NO_MIC, TXIN_EXT);
+ } else {
+ pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC,
+ TXIN_EXT);
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+
+ }
+ switch (dev) {
+ case IP_HANDSET:
+ pr_debug("Input: SOUND_MASK_PHONEIN \n");
+ if (handle == NULL) {
+ if (enable) {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic(handle,
+ MIC1_LEFT,
+ MIC1_RIGHT_MIC_MONO);
+ pmic_audio_vcodec_enable_micbias(handle,
+ MIC_BIAS1);
+ }
+ audio_mixer_control.input_device |=
+ SOUND_MASK_PHONEIN;
+ } else {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic_on_off(handle,
+ MIC1_LEFT,
+ MIC1_RIGHT_MIC_MONO);
+ pmic_audio_vcodec_disable_micbias
+ (handle, MIC_BIAS1);
+ }
+ audio_mixer_control.input_device &=
+ ~SOUND_MASK_PHONEIN;
+ }
+ }
+ break;
+
+ case IP_HEADSET:
+ if (handle == NULL) {
+ if (enable) {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic(handle,
+ NO_MIC,
+ MIC2_AUX);
+ pmic_audio_vcodec_enable_micbias(handle,
+ MIC_BIAS2);
+ }
+ audio_mixer_control.input_device |=
+ SOUND_MASK_MIC;
+ } else {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic_on_off(handle,
+ NO_MIC,
+ MIC2_AUX);
+ pmic_audio_vcodec_disable_micbias
+ (handle, MIC_BIAS2);
+ }
+ audio_mixer_control.input_device &=
+ ~SOUND_MASK_MIC;
+ }
+ // Enable Mic with MIC2_AUX
+ }
+ break;
+
+ case IP_LINEIN:
+ if (handle == NULL) {
+ if (enable) {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic(handle,
+ NO_MIC,
+ TXIN_EXT);
+ }
+ audio_mixer_control.input_device |=
+ SOUND_MASK_LINE;
+ } else {
+ if (audio_mixer_control.codec_capture_active) {
+ handle =
+ audio_mixer_control.
+ voice_codec_handle;
+ pmic_audio_vcodec_set_mic_on_off(handle,
+ NO_MIC,
+ TXIN_EXT);
+ }
+ audio_mixer_control.input_device &=
+ ~SOUND_MASK_LINE;
+ }
+ }
+ break;
+
+ default:
+ up(&audio_mixer_control.sem);
+ return -1;
+ break;
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(set_mixer_input_device);
+
+int get_mixer_input_device()
+{
+ int val;
+ val = audio_mixer_control.input_device;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_input_device);
+
+/*!
+ * This function sets the PMIC input device's gain.
+ * Note that the gain is the input volume
+ *
+ * @param handle Handle to the opened PMIC device
+ * @param val gain to be applied. This value can go
+ * from 0 (mute) to 100 (max gain)
+ */
+int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val)
+{
+ int leftdb, rightdb;
+ int left, right;
+
+ left = (val & 0x00ff);
+ right = ((val & 0xff00) >> 8);
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ leftdb = (left * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX;
+ rightdb = (right * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX;
+ audio_mixer_control.input_volume = val;
+ if (audio_mixer_control.voice_codec_handle == handle) {
+ pmic_audio_vcodec_set_record_gain(handle, VOLTAGE_TO_VOLTAGE,
+ leftdb, VOLTAGE_TO_VOLTAGE,
+ rightdb);
+ } else if ((handle == NULL)
+ && (audio_mixer_control.codec_capture_active)) {
+ pmic_audio_vcodec_set_record_gain(audio_mixer_control.
+ voice_codec_handle,
+ VOLTAGE_TO_VOLTAGE, leftdb,
+ VOLTAGE_TO_VOLTAGE, rightdb);
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(set_mixer_input_gain);
+
+int get_mixer_input_gain()
+{
+ int val;
+ val = audio_mixer_control.input_volume;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_input_gain);
+
+/*!
+ * This function sets the PMIC output device's volume.
+ *
+ * @param handle Handle to the PMIC device opened
+ * @param volume ALSA value. This value defines the playback volume
+ * @param dev which output device gets affected by this volume
+ *
+ */
+
+int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume,
+ OUTPUT_DEVICES dev)
+{
+ int leftdb, rightdb;
+ int right, left;
+
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ left = (volume & 0x00ff);
+ right = ((volume & 0xff00) >> 8);
+
+ leftdb = (left * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX;
+ rightdb = (right * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX;
+ if (handle == NULL) {
+ /* Invoked by mixer */
+ audio_mixer_control.master_volume_out = volume;
+ if (audio_mixer_control.codec_playback_active)
+ pmic_audio_output_set_pgaGain(audio_mixer_control.
+ voice_codec_handle,
+ rightdb);
+ if (audio_mixer_control.stdac_playback_active)
+ pmic_audio_output_set_pgaGain(audio_mixer_control.
+ stdac_handle, rightdb);
+
+ } else {
+ /* change the required volume */
+ audio_mixer_control.master_volume_out = volume;
+ pmic_audio_output_set_pgaGain(handle, rightdb);
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(set_mixer_output_volume);
+
+int get_mixer_output_volume()
+{
+ int val;
+ val = audio_mixer_control.master_volume_out;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_output_volume);
+
+/*!
+ * This function sets the PMIC output device's balance.
+ *
+ * @param bal Balance to be applied. This value can go
+ * from 0 (Left atten) to 100 (Right atten)
+ * 50 is both equal
+ */
+int set_mixer_output_balance(int bal)
+{
+ int channel = 0;
+ PMIC_AUDIO_OUTPUT_BALANCE_GAIN b_gain;
+ PMIC_AUDIO_HANDLE handle;
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ // Convert ALSA value to PMIC value i.e. atten and channel value
+ if (bal < 0)
+ bal = 0;
+ if (bal > 100)
+ bal = 100;
+ if (bal < 50) {
+ channel = 1;
+ } else {
+ bal = 100 - bal;
+ channel = 0;
+ }
+
+ b_gain = bal / 8;
+
+ audio_mixer_control.mixer_balance = bal;
+ if (audio_mixer_control.codec_playback_active) {
+ handle = audio_mixer_control.voice_codec_handle;
+ // Use codec's handle to set balance
+ } else if (audio_mixer_control.stdac_playback_active) {
+ handle = audio_mixer_control.stdac_handle;
+ // Use STDac's handle to set balance
+ } else {
+ up(&audio_mixer_control.sem);
+ return 0;
+ }
+ if (channel == 0)
+ pmic_audio_output_set_balance(handle, BAL_GAIN_0DB, b_gain);
+ else
+ pmic_audio_output_set_balance(handle, b_gain, BAL_GAIN_0DB);
+ up(&audio_mixer_control.sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(set_mixer_output_balance);
+
+int get_mixer_output_balance()
+{
+ int val;
+ val = audio_mixer_control.mixer_balance;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_output_balance);
+
+/*!
+ * This function sets the PMIC output device's mono adder config.
+ *
+ * @param mode Mono adder mode to be set
+ */
+int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode)
+{
+ PMIC_AUDIO_HANDLE handle;
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ audio_mixer_control.mixer_mono_adder = mode;
+ if (audio_mixer_control.codec_playback_active) {
+ handle = audio_mixer_control.voice_codec_handle;
+ // Use codec's handle to set balance
+ pmic_audio_output_enable_mono_adder(audio_mixer_control.
+ voice_codec_handle, mode);
+ } else if (audio_mixer_control.stdac_playback_active) {
+ handle = audio_mixer_control.stdac_handle;
+ pmic_audio_output_enable_mono_adder(audio_mixer_control.
+ stdac_handle, mode);
+ // Use STDac's handle to set balance
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(set_mixer_output_mono_adder);
+
+int get_mixer_output_mono_adder()
+{
+ int val;
+ val = audio_mixer_control.mixer_mono_adder;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_output_mono_adder);
+
+/*!
+ * This function sets the output device(s) in PMIC. It takes an
+ * ALSA value and modifies registers using PMIC-specific values.
+ *
+ * @param handle handle to the device already opened
+ * @param src Source connected to o/p device
+ * @param dev Output device to be enabled
+ * @param enable Enable or disable the device
+ *
+ */
+int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src,
+ OUTPUT_DEVICES dev, bool enable)
+{
+ PMIC_AUDIO_OUTPUT_PORT port;
+ if (down_interruptible(&audio_mixer_control.sem))
+ return -EINTR;
+ if (!((src == CODEC_DIR_OUT) || (src == MIXER_OUT))) {
+ up(&audio_mixer_control.sem);
+ return -1;
+ }
+ if (handle != (PMIC_AUDIO_HANDLE) NULL) {
+ /* Invoked by playback stream */
+ if (audio_mixer_control.output_device & SOUND_MASK_PHONEOUT) {
+ audio_mixer_control.output_active[OP_EARPIECE] = 1;
+ pmic_audio_output_set_port(handle, MONO_SPEAKER);
+ } else {
+ audio_mixer_control.output_active[OP_EARPIECE] = 0;
+ pmic_audio_output_clear_port(handle, MONO_SPEAKER);
+ }
+ if (audio_mixer_control.output_device & SOUND_MASK_SPEAKER) {
+ audio_mixer_control.output_active[OP_HANDSFREE] = 1;
+ pmic_audio_output_set_port(handle, MONO_LOUDSPEAKER);
+ } else {
+ audio_mixer_control.output_active[OP_HANDSFREE] = 0;
+ pmic_audio_output_clear_port(handle, MONO_LOUDSPEAKER);
+ }
+ if (audio_mixer_control.output_device & SOUND_MASK_VOLUME) {
+ audio_mixer_control.output_active[OP_HEADSET] = 1;
+ if (dev != OP_MONO) {
+ pmic_audio_output_set_port(handle,
+ STEREO_HEADSET_LEFT |
+ STEREO_HEADSET_RIGHT);
+ } else {
+ pmic_audio_output_set_port(handle,
+ STEREO_HEADSET_LEFT);
+ /*This is a temporary workaround which should be removed later */
+ }
+
+ } else {
+ audio_mixer_control.output_active[OP_HEADSET] = 0;
+ pmic_audio_output_clear_port(handle,
+ STEREO_HEADSET_LEFT |
+ STEREO_HEADSET_RIGHT);
+ }
+ if (audio_mixer_control.output_device & SOUND_MASK_PCM) {
+ audio_mixer_control.output_active[OP_LINEOUT] = 1;
+ pmic_audio_output_set_port(handle,
+ STEREO_OUT_LEFT |
+ STEREO_OUT_RIGHT);
+ } else {
+ audio_mixer_control.output_active[OP_LINEOUT] = 0;
+ pmic_audio_output_clear_port(handle,
+ STEREO_OUT_LEFT |
+ STEREO_OUT_RIGHT);
+ }
+ } else {
+ switch (dev) {
+ case OP_EARPIECE:
+ if (enable) {
+ audio_mixer_control.output_device |=
+ SOUND_MASK_PHONEOUT;
+ audio_mixer_control.source_for_output[dev] =
+ src;
+ } else {
+ audio_mixer_control.output_device &=
+ ~SOUND_MASK_PHONEOUT;
+ }
+ port = MONO_SPEAKER;
+ break;
+ case OP_HANDSFREE:
+ if (enable) {
+ audio_mixer_control.output_device |=
+ SOUND_MASK_SPEAKER;
+ audio_mixer_control.source_for_output[dev] =
+ src;
+ } else {
+ audio_mixer_control.output_device &=
+ ~SOUND_MASK_SPEAKER;
+ }
+ port = MONO_LOUDSPEAKER;
+ break;
+ case OP_HEADSET:
+ if (enable) {
+ audio_mixer_control.output_device |=
+ SOUND_MASK_VOLUME;
+ audio_mixer_control.source_for_output[dev] =
+ src;
+ } else {
+ audio_mixer_control.output_device &=
+ ~SOUND_MASK_VOLUME;
+ }
+ port = STEREO_HEADSET_LEFT | STEREO_HEADSET_RIGHT;
+ break;
+ case OP_MONO:
+ /*This is a temporary workaround which should be removed later */
+ if (enable) {
+ audio_mixer_control.output_device |=
+ SOUND_MASK_VOLUME;
+ audio_mixer_control.source_for_output[dev] =
+ src;
+ } else {
+ audio_mixer_control.output_device &=
+ ~SOUND_MASK_VOLUME;
+ }
+ port = STEREO_HEADSET_LEFT;
+ break;
+ case OP_LINEOUT:
+ if (enable) {
+ audio_mixer_control.output_device |=
+ SOUND_MASK_PCM;
+ audio_mixer_control.source_for_output[dev] =
+ src;
+ } else {
+ audio_mixer_control.output_device &=
+ ~SOUND_MASK_PCM;
+ }
+ port = STEREO_OUT_LEFT | STEREO_OUT_RIGHT;
+ break;
+ default:
+ up(&audio_mixer_control.sem);
+ return -1;
+ break;
+ }
+ /* Invoked by mixer .. little tricky to handle over here */
+ if (audio_mixer_control.codec_playback_active) {
+ if (enable) {
+ audio_mixer_control.output_active[dev] = 1;
+ pmic_audio_output_set_port(audio_mixer_control.
+ voice_codec_handle,
+ port);
+ } else {
+ audio_mixer_control.output_active[dev] = 0;
+ pmic_audio_output_clear_port
+ (audio_mixer_control.voice_codec_handle,
+ port);
+ }
+ }
+ if (audio_mixer_control.stdac_playback_active) {
+ if (enable) {
+ audio_mixer_control.output_active[dev] = 1;
+ pmic_audio_output_set_port(audio_mixer_control.
+ stdac_handle, port);
+ } else {
+ audio_mixer_control.output_active[dev] = 0;
+ pmic_audio_output_clear_port
+ (audio_mixer_control.stdac_handle, port);
+ }
+ }
+
+ }
+ up(&audio_mixer_control.sem);
+ return 0;
+ // Set O/P device with handle and port
+
+}
+
+EXPORT_SYMBOL(set_mixer_output_device);
+
+int get_mixer_output_device()
+{
+ int val;
+ val = audio_mixer_control.output_device;
+ return val;
+}
+
+EXPORT_SYMBOL(get_mixer_output_device);
+
+/*!
+ * This function configures the CODEC for playback/recording.
+ *
+ * main configured elements are:
+ * - audio path on PMIC
+ * - external clock to generate BC and FS clocks
+ * - PMIC mode (master or slave)
+ * - protocol
+ * - sample rate
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param stream_id index into the audio_stream array.
+ */
+void configure_codec(struct snd_pcm_substream *substream, int stream_id)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ pmic_audio_device_t *pmic;
+ PMIC_AUDIO_HANDLE handle;
+ int ssi_bus;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[stream_id];
+ pmic = &s->pmic_audio_device;
+ handle = audio_mixer_control.voice_codec_handle;
+
+ ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2;
+
+ pmic_audio_vcodec_set_rxtx_timeslot(handle, USE_TS0);
+ pmic_audio_vcodec_enable_mixer(handle, USE_TS1, VCODEC_MIX_IN_0DB,
+ VCODEC_MIX_OUT_0DB);
+ pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode,
+ USE_4_TIMESLOTS);
+
+ msleep(20);
+ pmic_audio_vcodec_set_clock(handle, pmic->pll, pmic->pll_rate,
+ pmic->sample_rate, NO_INVERT);
+ msleep(20);
+ pmic_audio_vcodec_set_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS);
+ pmic_audio_digital_filter_reset(handle);
+ msleep(15);
+ if (stream_id == 2) {
+ pmic_audio_output_enable_mixer(handle);
+ set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1);
+ set_mixer_output_volume(handle,
+ audio_mixer_control.master_volume_out,
+ OP_HEADSET);
+ } else {
+ set_mixer_input_device(handle, IP_NODEV, 1);
+ set_mixer_input_gain(handle, audio_mixer_control.input_volume);
+ }
+ pmic_audio_enable(handle);
+}
+
+/*!
+ * This function configures the STEREODAC for playback/recording.
+ *
+ * main configured elements are:
+ * - audio path on PMIC
+ * - external clock to generate BC and FS clocks
+ * - PMIC mode (master or slave)
+ * - protocol
+ * - sample rate
+ *
+ * @param substream pointer to the structure of the current stream.
+ */
+void configure_stereodac(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ int stream_id;
+ audio_stream_t *s;
+ pmic_audio_device_t *pmic;
+ int ssi_bus;
+ PMIC_AUDIO_HANDLE handle;
+ struct snd_pcm_runtime *runtime;
+
+ chip = snd_pcm_substream_chip(substream);
+ stream_id = substream->pstr->stream;
+ s = &chip->s[stream_id];
+ pmic = &s->pmic_audio_device;
+ handle = pmic->handle;
+ runtime = substream->runtime;
+
+ if (runtime->channels == 1) {
+ audio_mixer_control.mixer_mono_adder = MONO_ADD_LEFT_RIGHT;
+ } else {
+ audio_mixer_control.mixer_mono_adder = MONO_ADDER_OFF;
+ }
+
+ ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2;
+ pmic_audio_stdac_set_rxtx_timeslot(handle, USE_TS0_TS1);
+ pmic_audio_stdac_enable_mixer(handle, USE_TS2_TS3, STDAC_NO_MIX,
+ STDAC_MIX_OUT_0DB);
+#ifdef CONFIG_MXC_PMIC_SC55112
+ pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode,
+ USE_4_TIMESLOTS);
+#else
+ pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode,
+ USE_2_TIMESLOTS);
+#endif
+ pmic_audio_stdac_set_clock(handle, pmic->pll, pmic->pll_rate,
+ pmic->sample_rate, NO_INVERT);
+
+ pmic_audio_stdac_set_config(handle, STDAC_MASTER_CLOCK_OUTPUTS);
+ pmic_audio_output_enable_mixer(handle);
+ audio_mixer_control.stdac_out_to_mixer = 1;
+ pmic_audio_digital_filter_reset(handle);
+ msleep(10);
+ pmic_audio_output_enable_phantom_ground();
+ set_mixer_output_volume(handle, audio_mixer_control.master_volume_out,
+ OP_HEADSET);
+ pmic_audio_output_enable_mono_adder(handle,
+ audio_mixer_control.
+ mixer_mono_adder);
+#ifdef CONFIG_HEADSET_DETECT_ENABLE
+ set_mixer_output_device(handle, MIXER_OUT, OP_MONO, 1);
+#else
+ set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1);
+#endif
+ pmic_audio_enable(handle);
+
+}
+
+/*!
+ * This function disables CODEC's amplifiers, volume and clock.
+ * @param handle Handle of voice codec
+ */
+
+void disable_codec(PMIC_AUDIO_HANDLE handle)
+{
+ pmic_audio_disable(handle);
+ pmic_audio_vcodec_clear_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS);
+}
+
+/*!
+ * This function disables STEREODAC's amplifiers, volume and clock.
+ * @param handle Handle of STdac
+ * @param
+ */
+
+void disable_stereodac(void)
+{
+
+ audio_mixer_control.stdac_out_to_mixer = 0;
+}
+
+/*!
+ * This function configures PMIC for recording.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+int configure_pmic_recording(struct snd_pcm_substream *substream)
+{
+
+ configure_codec(substream, 1);
+ return 0;
+}
+
+/*!
+ * This function configures PMIC for playing back.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param stream_id Index into the audio_stream array .
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+
+int configure_pmic_playback(struct snd_pcm_substream *substream, int stream_id)
+{
+ if (stream_id == 0) {
+ configure_stereodac(substream);
+ } else if (stream_id == 2 || stream_id == 3) {
+ configure_codec(substream, stream_id);
+ }
+ return 0;
+}
+
+/*!
+ * This function shutsdown the PMIC soundcard.
+ * Nothing to be done here
+ *
+ * @param mxc_audio pointer to the sound card structure.
+ *
+ * @return
+ */
+/*
+static void mxc_pmic_audio_shutdown(mxc_pmic_audio_t * mxc_audio)
+{
+
+}
+*/
+
+/*!
+ * This function configures the DMA channel used to transfer
+ * audio from MCU to PMIC
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param callback pointer to function that will be
+ * called when a SDMA TX transfer finishes.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+configure_write_channel(audio_stream_t * s, mxc_dma_callback_t callback,
+ int stream_id)
+{
+ int ret = -1;
+ int channel = -1;
+#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING
+
+ int channel1 = -1;
+#endif
+
+ if (stream_id == 0) {
+ if (audio_data->ssi_num == 2) {
+ channel =
+ mxc_dma_request(MXC_DMA_SSI2_16BIT_TX0,
+ "ALSA TX DMA");
+ ret =
+ mxc_dma_callback_set(channel,
+ (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel);
+ return -1;
+ }
+ s->dma_wchannel = channel;
+
+ } else {
+ channel =
+ mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0,
+ "ALSA TX DMA");
+ ret =
+ mxc_dma_callback_set(channel,
+ (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel);
+ return -1;
+ }
+ s->dma_wchannel = channel;
+ }
+
+ }
+ if (stream_id == 3) {
+ channel =
+ mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA");
+ if (channel < 0) {
+ pr_debug("error requesting a write dma channel\n");
+ return -1;
+ }
+ ret =
+ mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel);
+
+ return -1;
+ }
+ s->dma_wchannel = channel;
+ }
+#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING
+ else if (stream_id == 2) {
+ channel1 =
+ mxc_dma_request(MXC_DMA_SSI1_16BIT_TX1, "ALSA TX DMA");
+ ret =
+ mxc_dma_callback_set(channel1,
+ (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel1);
+ return -1;
+ }
+ s->dma_wchannel = channel1;
+ }
+#else
+
+ if (stream_id == 2) {
+ channel =
+ mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA");
+ ret =
+ mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel);
+ return -1;
+ }
+ s->dma_wchannel = channel;
+ }
+#endif
+ /*if (channel < 0) {
+ pr_debug("error requesting a write dma channel\n");
+ return -1;
+ } */
+
+ return 0;
+}
+
+/*!
+ * This function configures the DMA channel used to transfer
+ * audio from PMIC to MCU
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param callback pointer to function that will be
+ * called when a SDMA RX transfer finishes.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int configure_read_channel(audio_stream_t * s,
+ mxc_dma_callback_t callback)
+{
+ int ret = -1;
+ int channel = -1;
+
+ channel = mxc_dma_request(MXC_DMA_SSI1_16BIT_RX0, "ALSA RX DMA");
+ if (channel < 0) {
+ pr_debug("error requesting a read dma channel\n");
+ return -1;
+ }
+
+ ret =
+ mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback,
+ (void *)s);
+ if (ret != 0) {
+ mxc_dma_free(channel);
+ return -1;
+ }
+ s->dma_wchannel = channel;
+
+ return 0;
+}
+
+/*!
+ * This function frees the stream structure
+ *
+ * @param s pointer to the structure of the current stream.
+ */
+static void audio_dma_free(audio_stream_t * s)
+{
+ /*
+ * There is nothing to be done here since the dma channel has been
+ * freed either in the callback or in the stop method
+ */
+
+}
+
+/*!
+ * This function gets the dma pointer position during record.
+ * Our DMA implementation does not allow to retrieve this position
+ * when a transfert is active, so, it answers the middle of
+ * the current period beeing transfered
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static u_int audio_get_capture_dma_pos(audio_stream_t * s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int offset;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ offset = 0;
+
+ /* tx_spin value is used here to check if a transfert is active */
+ if (s->tx_spin) {
+ offset = (runtime->period_size * (s->periods)) + 0;
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ pr_debug("MXC: audio_get_dma_pos offset %d\n", offset);
+ } else {
+ offset = (runtime->period_size * (s->periods));
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset);
+ }
+
+ return offset;
+}
+
+/*!
+ * This function gets the dma pointer position during playback.
+ * Our DMA implementation does not allow to retrieve this position
+ * when a transfert is active, so, it answers the middle of
+ * the current period beeing transfered
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static u_int audio_get_playback_dma_pos(audio_stream_t * s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int offset;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ offset = 0;
+
+ /* tx_spin value is used here to check if a transfert is active */
+ if (s->tx_spin) {
+ offset = (runtime->period_size * (s->periods)) + 0;
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ pr_debug("MXC: audio_get_dma_pos offset %d\n", offset);
+ } else {
+ offset = (runtime->period_size * (s->periods));
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset);
+ }
+
+ return offset;
+}
+
+/*!
+ * This function is called whenever a new audio block needs to be
+ * transferred to PMIC. The function receives the address and the size
+ * of the new block and start a new DMA transfer.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static void audio_playback_dma(audio_stream_t * s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size = 0;
+ unsigned int offset;
+ int ret = 0;
+ mxc_dma_requestbuf_t dma_request;
+#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING
+ unsigned int dma_size_mix = 0, offset_mix;
+ mxc_dma_requestbuf_t dma_request_mix;
+ int ret1 = 0;
+#endif
+ int device;
+ int stream_id;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ device = substream->pcm->device;
+ if (device == 0) {
+ stream_id = 0;
+ } else if (device == 1) {
+ stream_id = 2;
+ } else {
+ stream_id = 3;
+ }
+
+ pr_debug("\nDMA direction %d\(0 is playback 1 is capture)\n",
+ s->stream_id);
+
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));
+
+#else
+ if (stream_id == 2) {
+ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));
+ } else if (stream_id == 3) {
+ memset(&dma_request_mix, 0, sizeof(mxc_dma_requestbuf_t));
+ }
+#endif
+ if (s->active) {
+ if (ssi_get_status(s->ssi) & (ssi_transmitter_underrun_0)) {
+ ssi_enable(s->ssi, false);
+ ssi_transmit_enable(s->ssi, false);
+ ssi_enable(s->ssi, true);
+ }
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ pr_debug("s->period (%x) runtime->periods (%d)\n",
+ s->period, runtime->periods);
+ pr_debug("runtime->period_size (%d) dma_size (%d)\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes);
+
+ offset = dma_size * s->period;
+ if (snd_BUG_ON(dma_size > DMA_BUF_SIZE))
+ return;
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+
+ if (g_audio_iram_en && stream_id == 0) {
+ dma_request.src_addr = ADMA_BASE_PADDR + offset;
+ } else
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+ {
+
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset,
+ dma_size, DMA_TO_DEVICE));
+ }
+ if (stream_id == 0) {
+ dma_request.dst_addr =
+ (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1);
+ } else if (stream_id == 2) {
+ dma_request.dst_addr =
+ (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1);
+ }
+ dma_request.num_of_bytes = dma_size;
+ pr_debug("MXC: Start DMA offset (%d) size (%d)\n",
+ offset, runtime->dma_bytes);
+
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ ssi_transmit_enable(s->ssi, true);
+ s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
+
+ if (ret) {
+ pr_debug("audio_process_dma: cannot queue DMA buffer\
+ (%i)\n", ret);
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+#else
+
+ if (stream_id == 2) {
+ dma_size =
+ frames_to_bytes(runtime, runtime->period_size);
+ pr_debug("s->period (%x) runtime->periods (%d)\n",
+ s->period, runtime->periods);
+ pr_debug("runtime->period_size (%d) dma_size (%d)\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes);
+
+ offset = dma_size * s->period;
+ if (snd_BUG_ON(dma_size > DMA_BUF_SIZE))
+ return;
+
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset,
+ dma_size, DMA_TO_DEVICE));
+ dma_request.dst_addr =
+ (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1);
+ dma_request.num_of_bytes = dma_size;
+ pr_debug("MXC: Start DMA offset (%d) size (%d)\n",
+ offset, runtime->dma_bytes);
+
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ ssi_transmit_enable(s->ssi, true);
+ s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
+
+ if (ret) {
+ pr_debug("audio_process_dma: cannot queue DMA buffer\
+ (%i)\n",
+ ret);
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+
+ } else if (stream_id == 3) {
+
+ dma_size_mix =
+ frames_to_bytes(runtime, runtime->period_size);
+ pr_debug("s->period (%x) runtime->periods (%d)\n",
+ s->period, runtime->periods);
+ pr_debug("runtime->period_size (%d) dma_size (%d)\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes);
+
+ offset_mix = dma_size_mix * s->period;
+ if (snd_BUG_ON(dma_size_mix > DMA_BUF_SIZE))
+ return;
+ dma_request_mix.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset_mix,
+ dma_size_mix, DMA_TO_DEVICE));
+ dma_request_mix.dst_addr =
+ (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1);
+ dma_request_mix.num_of_bytes = dma_size_mix;
+
+ pr_debug("MXC: Start DMA offset (%d) size (%d)\n",
+ offset_mix, runtime->dma_bytes);
+
+ mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1,
+ MXC_DMA_MODE_WRITE);
+ ret1 = mxc_dma_enable(s->dma_wchannel);
+ ssi_transmit_enable(s->ssi, true);
+ s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
+
+ if (ret1) {
+ pr_debug("audio_process_dma: cannot queue DMA buffer\
+ (%i)\n",
+ ret1);
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+
+ }
+#endif
+#ifdef MXC_SOUND_PLAYBACK_CHAIN_DMA_EN
+
+ if ((s->period > s->periods) && ((s->period - s->periods) > 1)) {
+ pr_debug
+ ("audio playback chain dma: already double buffered\n");
+ return;
+ }
+
+ if ((s->period < s->periods)
+ && ((s->period + runtime->periods - s->periods) > 1)) {
+ pr_debug
+ ("audio playback chain dma: already double buffered\n");
+ return;
+ }
+
+ if (s->period == s->periods) {
+ pr_debug
+ ("audio playback chain dma: s->period == s->periods\n");
+ return;
+ }
+
+ if (snd_pcm_playback_hw_avail(runtime) <
+ 2 * runtime->period_size) {
+ pr_debug
+ ("audio playback chain dma: available data is not enough\n");
+ return;
+ }
+
+ pr_debug
+ ("audio playback chain dma:to set up the 2nd dma buffer\n");
+
+#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING
+ offset = dma_size * s->period;
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if (g_audio_iram_en && stream_id == 0) {
+ dma_request.src_addr = ADMA_BASE_PADDR + offset;
+ } else
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+ {
+
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset,
+ dma_size, DMA_TO_DEVICE));
+ }
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+#else
+ if (stream_id == 3) {
+ offset_mix = dma_size_mix * s->period;
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL,
+ runtime->dma_area + offset_mix,
+ dma_size, DMA_TO_DEVICE));
+
+ mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1,
+ MXC_DMA_MODE_WRITE);
+ } else {
+ offset = dma_size * s->period;
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL,
+ runtime->dma_area + offset,
+ dma_size, DMA_TO_DEVICE));
+
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+ }
+
+#endif
+ ret = mxc_dma_enable(s->dma_wchannel);
+
+ s->period++;
+ s->period %= runtime->periods;
+#endif /* MXC_SOUND_PLAYBACK_CHAIN_DMA_EN */
+ }
+}
+
+/*!
+ * This function is called whenever a new audio block needs to be
+ * transferred from PMIC. The function receives the address and the size
+ * of the block that will store the audio samples and start a new DMA transfer.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static void audio_capture_dma(audio_stream_t * s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int offset;
+ int ret = 0;
+ mxc_dma_requestbuf_t dma_request;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+
+ pr_debug("\nDMA direction %d\
+ (0 is playback 1 is capture)\n", s->stream_id);
+
+ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));
+
+ if (s->active) {
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ pr_debug("s->period (%x) runtime->periods (%d)\n",
+ s->period, runtime->periods);
+ pr_debug("runtime->period_size (%d) dma_size (%d)\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes);
+
+ offset = dma_size * s->period;
+ snd_BUG_ON(dma_size > DMA_BUF_SIZE);
+
+ dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL,
+ runtime->
+ dma_area +
+ offset,
+ dma_size,
+ DMA_FROM_DEVICE));
+ dma_request.src_addr =
+ (dma_addr_t) get_ssi_fifo_addr(s->ssi, 0);
+ dma_request.num_of_bytes = dma_size;
+
+ pr_debug("MXC: Start DMA offset (%d) size (%d)\n", offset,
+ runtime->dma_bytes);
+
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_READ);
+ ret = mxc_dma_enable(s->dma_wchannel);
+
+ s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
+
+ if (ret) {
+ pr_debug("audio_process_dma: cannot queue DMA buffer\
+ (%i)\n", ret);
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+
+#ifdef MXC_SOUND_CAPTURE_CHAIN_DMA_EN
+ if ((s->period > s->periods) && ((s->period - s->periods) > 1)) {
+ pr_debug
+ ("audio capture chain dma: already double buffered\n");
+ return;
+ }
+
+ if ((s->period < s->periods)
+ && ((s->period + runtime->periods - s->periods) > 1)) {
+ pr_debug
+ ("audio capture chain dma: already double buffered\n");
+ return;
+ }
+
+ if (s->period == s->periods) {
+ pr_debug
+ ("audio capture chain dma: s->period == s->periods\n");
+ return;
+ }
+
+ if (snd_pcm_capture_hw_avail(runtime) <
+ 2 * runtime->period_size) {
+ pr_debug
+ ("audio capture chain dma: available data is not enough\n");
+ return;
+ }
+
+ pr_debug
+ ("audio capture chain dma:to set up the 2nd dma buffer\n");
+ offset = dma_size * s->period;
+ dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL,
+ runtime->
+ dma_area +
+ offset,
+ dma_size,
+ DMA_FROM_DEVICE));
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_READ);
+ ret = mxc_dma_enable(s->dma_wchannel);
+
+ s->period++;
+ s->period %= runtime->periods;
+#endif /* MXC_SOUND_CAPTURE_CHAIN_DMA_EN */
+ }
+}
+
+/*!
+ * This is a callback which will be called
+ * when a TX transfer finishes. The call occurs
+ * in interrupt context.
+ *
+ * @param dat pointer to the structure of the current stream.
+ *
+ */
+static void audio_playback_dma_callback(void *data, int error,
+ unsigned int count)
+{
+ audio_stream_t *s;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int previous_period;
+ unsigned int offset;
+
+ s = data;
+ substream = s->stream;
+ runtime = substream->runtime;
+ previous_period = s->periods;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * previous_period;
+
+ s->tx_spin = 0;
+ s->periods++;
+ s->periods %= runtime->periods;
+
+ /*
+ * Give back to the CPU the access to the non cached memory
+ */
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_TO_DEVICE);
+
+ /*
+ * If we are getting a callback for an active stream then we inform
+ * the PCM middle layer we've finished a period
+ */
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+ spin_lock(&s->dma_lock);
+
+ /*
+ * Trig next DMA transfer
+ */
+ audio_playback_dma(s);
+
+ spin_unlock(&s->dma_lock);
+
+}
+
+/*!
+ * This is a callback which will be called
+ * when a RX transfer finishes. The call occurs
+ * in interrupt context.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static void audio_capture_dma_callback(void *data, int error,
+ unsigned int count)
+{
+ audio_stream_t *s;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int previous_period;
+ unsigned int offset;
+
+ s = data;
+ substream = s->stream;
+ runtime = substream->runtime;
+ previous_period = s->periods;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * previous_period;
+
+ s->tx_spin = 0;
+ s->periods++;
+ s->periods %= runtime->periods;
+
+ /*
+ * Give back to the CPU the access to the non cached memory
+ */
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_FROM_DEVICE);
+
+ /*
+ * If we are getting a callback for an active stream then we inform
+ * the PCM middle layer we've finished a period
+ */
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+
+ spin_lock(&s->dma_lock);
+
+ /*
+ * Trig next DMA transfer
+ */
+ audio_capture_dma(s);
+
+ spin_unlock(&s->dma_lock);
+
+}
+
+/*!
+ * This function is a dispatcher of command to be executed
+ * by the driver for playback.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param cmd command to be executed
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+snd_mxc_audio_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ mxc_pmic_audio_t *chip;
+ int stream_id;
+ audio_stream_t *s;
+ int err;
+ int device;
+ device = substream->pcm->device;
+ if (device == 0) {
+ stream_id = 0;
+ } else if (device == 1) {
+ stream_id = 2;
+ } else {
+ stream_id = 3;
+ }
+
+ chip = snd_pcm_substream_chip(substream);
+ //stream_id = substream->pstr->stream;
+ s = &chip->s[stream_id];
+ err = 0;
+
+ /* note local interrupts are already disabled in the midlevel code */
+ spin_lock(&s->dma_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ s->tx_spin = 0;
+ s->active = 1;
+ audio_playback_dma(s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ s->active = 0;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock(&s->dma_lock);
+ return err;
+}
+
+/*!
+ * This function is a dispatcher of command to be executed
+ * by the driver for capture.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param cmd command to be executed
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+snd_mxc_audio_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ mxc_pmic_audio_t *chip;
+ int stream_id;
+ audio_stream_t *s;
+ int err;
+
+ chip = snd_pcm_substream_chip(substream);
+ stream_id = substream->pstr->stream;
+ s = &chip->s[stream_id];
+ err = 0;
+
+ /* note local interrupts are already disabled in the midlevel code */
+ spin_lock(&s->dma_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ s->tx_spin = 0;
+ s->active = 1;
+ audio_capture_dma(s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ s->active = 0;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock(&s->dma_lock);
+ return err;
+}
+
+/*!
+ * This function configures the hardware to allow audio
+ * playback operations. It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_playback_prepare(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ int ssi;
+ int device = -1, stream_id = -1;
+
+ device = substream->pcm->device;
+ if (device == 0)
+ stream_id = 0;
+ else if (device == 1)
+ stream_id = 2;
+ else if (device == 2)
+ stream_id = 3;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[stream_id];
+ ssi = s->ssi;
+
+ normalize_speed_for_pmic(substream);
+
+ configure_dam_pmic_master(ssi);
+
+ configure_ssi_tx(substream);
+
+ ssi_interrupt_enable(ssi, ssi_tx_dma_interrupt_enable);
+ ssi_interrupt_enable(ssi, ssi_tx_interrupt_enable);
+
+ if (configure_pmic_playback(substream, stream_id) == -1)
+ pr_debug(KERN_ERR "MXC: PMIC Playback Config FAILED\n");
+ ssi_interrupt_enable(ssi, ssi_tx_fifo_0_empty);
+ ssi_interrupt_enable(ssi, ssi_tx_fifo_1_empty);
+ /*
+ ssi_transmit_enable(ssi, true);
+ */
+ msleep(20);
+ set_pmic_channels(substream);
+ s->period = 0;
+ s->periods = 0;
+
+ msleep(100);
+
+ return 0;
+}
+
+/*!
+ * This function gets the current capture pointer position.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static
+snd_pcm_uframes_t snd_mxc_audio_capture_pointer(struct snd_pcm_substream
+ *substream)
+{
+ mxc_pmic_audio_t *chip;
+
+ chip = snd_pcm_substream_chip(substream);
+ return audio_get_capture_dma_pos(&chip->s[substream->pstr->stream]);
+}
+
+/*!
+ * This function gets the current playback pointer position.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static snd_pcm_uframes_t
+snd_mxc_audio_playback_pointer(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ int device;
+ int stream_id;
+ device = substream->pcm->device;
+ if (device == 0)
+ stream_id = 0;
+ else if (device == 1)
+ stream_id = 2;
+ else
+ stream_id = 3;
+ chip = snd_pcm_substream_chip(substream);
+ return audio_get_playback_dma_pos(&chip->s[stream_id]);
+}
+
+/*!
+ * This structure reprensents the capabilities of the driver
+ * in capture mode.
+ * It is used by ALSA framework.
+ */
+static struct snd_pcm_hardware snd_mxc_pmic_capture = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = MAX_BUFFER_SIZE,
+ .period_bytes_min = MIN_PERIOD_SIZE,
+ .period_bytes_max = DMA_BUF_SIZE,
+ .periods_min = MIN_PERIOD,
+ .periods_max = MAX_PERIOD,
+ .fifo_size = 0,
+
+};
+
+/*!
+ * This structure reprensents the capabilities of the driver
+ * in playback mode for ST-Dac.
+ * It is used by ALSA framework.
+ */
+static struct snd_pcm_hardware snd_mxc_pmic_playback_stereo = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = MAX_BUFFER_SIZE,
+ .period_bytes_min = MIN_PERIOD_SIZE,
+ .period_bytes_max = DMA_BUF_SIZE,
+ .periods_min = MIN_PERIOD,
+ .periods_max = MAX_PERIOD,
+ .fifo_size = 0,
+
+};
+
+/*!
+ * This structure reprensents the capabilities of the driver
+ * in playback mode for Voice-codec.
+ * It is used by ALSA framework.
+ */
+static struct snd_pcm_hardware snd_mxc_pmic_playback_mono = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = MAX_BUFFER_SIZE,
+ .period_bytes_min = MIN_PERIOD_SIZE,
+ .period_bytes_max = DMA_BUF_SIZE,
+ .periods_min = MIN_PERIOD,
+ .periods_max = MAX_PERIOD,
+ .fifo_size = 0,
+
+};
+
+/*!
+ * This function opens a PMIC audio device in playback mode
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_audio_playback_open(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ struct snd_pcm_runtime *runtime;
+ int stream_id = -1;
+ int err;
+ audio_stream_t *s;
+ PMIC_AUDIO_HANDLE temp_handle;
+ int device = -1;
+
+ device = substream->pcm->device;
+
+ chip = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+ if (device == 0)
+ stream_id = 0;
+ else if (device == 1)
+ stream_id = 2;
+ else if (device == 2) {
+ stream_id = 3;
+ }
+
+ s = &chip->s[stream_id];
+ err = -1;
+
+ if (stream_id == 0) {
+ if ((audio_data->ssi_num == 1)
+ && (audio_mixer_control.codec_playback_active
+ || audio_mixer_control.codec_capture_active)) {
+ return -EBUSY;
+ }
+
+ if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, STEREO_DAC)) {
+ audio_mixer_control.stdac_handle = temp_handle;
+ audio_mixer_control.stdac_playback_active = 1;
+ chip->s[stream_id].pmic_audio_device.handle =
+ temp_handle;
+ } else {
+ return -EBUSY;
+ }
+ } else if (stream_id == 2) {
+ if ((audio_data->ssi_num == 1)
+ && (audio_mixer_control.stdac_playback_active)) {
+ return -EBUSY;
+ }
+
+ audio_mixer_control.codec_playback_active = 1;
+ if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) {
+ audio_mixer_control.voice_codec_handle = temp_handle;
+ chip->s[stream_id].pmic_audio_device.handle =
+ temp_handle;
+ } else {
+ if (audio_mixer_control.codec_capture_active) {
+ temp_handle =
+ audio_mixer_control.voice_codec_handle;
+ } else {
+ return -EBUSY;
+ }
+ }
+
+ } else if (stream_id == 3) {
+ audio_mixer_control.mixing_active = 1;
+
+ }
+
+ pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW);
+ msleep(250);
+
+ chip->s[stream_id].stream = substream;
+
+ if (stream_id == 0)
+ runtime->hw = snd_mxc_pmic_playback_stereo;
+ else if (stream_id == 2)
+ runtime->hw = snd_mxc_pmic_playback_mono;
+
+ else if (stream_id == 3) {
+ runtime->hw = snd_mxc_pmic_playback_mono;
+
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_playback_rates_mono))
+ < 0)
+ return err;
+ }
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if (g_audio_iram_en && stream_id == 0) {
+ runtime->hw.buffer_bytes_max = MAX_IRAM_SIZE;
+ runtime->hw.period_bytes_max = DMA_IRAM_SIZE;
+ }
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) <
+ 0)
+ goto exit_err;
+ if (stream_id == 0) {
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_playback_rates_stereo))
+ < 0)
+ goto exit_err;
+
+ } else if (stream_id == 2) {
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_playback_rates_mono))
+ < 0)
+ goto exit_err;
+ }
+ msleep(10);
+
+ /* setup DMA controller for playback */
+ if ((err =
+ configure_write_channel(&mxc_audio->s[stream_id],
+ audio_playback_dma_callback,
+ stream_id)) < 0)
+ goto exit_err;
+
+ /* enable ssi clock */
+ clk_enable(audio_data->ssi_clk[s->ssi]);
+
+ return 0;
+ exit_err:
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if (stream_id == 0)
+ mxc_snd_pcm_iram_put();
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+ return err;
+
+}
+
+/*!
+ * This function closes an PMIC audio device for playback.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_audio_playback_close(struct snd_pcm_substream
+ *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ PMIC_AUDIO_HANDLE handle;
+ int ssi;
+ int device, stream_id = -1;
+ handle = (PMIC_AUDIO_HANDLE) NULL;
+ device = substream->pcm->device;
+ if (device == 0) {
+ stream_id = 0;
+ } else if (device == 1) {
+ stream_id = 2;
+ } else
+ stream_id = 3;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[stream_id];
+ ssi = s->ssi;
+
+ if (audio_mixer_control.mixing_active == 1) {
+ goto End;
+
+ } else {
+
+ if (stream_id == 0) {
+ disable_stereodac();
+ audio_mixer_control.stdac_playback_active = 0;
+ handle = audio_mixer_control.stdac_handle;
+ audio_mixer_control.stdac_handle = NULL;
+ chip->s[stream_id].pmic_audio_device.handle = NULL;
+ } else if ((stream_id == 2) || (stream_id == 3)) {
+
+ audio_mixer_control.codec_playback_active = 0;
+ handle = audio_mixer_control.voice_codec_handle;
+ disable_codec(handle);
+ audio_mixer_control.voice_codec_handle = NULL;
+ chip->s[stream_id].pmic_audio_device.handle = NULL;
+ }
+
+ }
+ pmic_audio_close(handle);
+
+ ssi_transmit_enable(ssi, false);
+ ssi_interrupt_disable(ssi, ssi_tx_dma_interrupt_enable);
+ ssi_tx_fifo_enable(ssi, ssi_fifo_0, false);
+ ssi_enable(ssi, false);
+ mxc_dma_free((mxc_audio->s[stream_id]).dma_wchannel);
+
+ chip->s[stream_id].stream = NULL;
+ End:
+ audio_mixer_control.mixing_active = 0;
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if (stream_id == 0)
+ mxc_snd_pcm_iram_put();
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+ /* disable ssi clock */
+ clk_disable(audio_data->ssi_clk[ssi]);
+
+ return 0;
+}
+
+/*!
+ * This function closes a PMIC audio device for capture.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_audio_capture_close(struct snd_pcm_substream *substream)
+{
+ PMIC_AUDIO_HANDLE handle;
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ int ssi;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[substream->pstr->stream];
+ ssi = s->ssi;
+
+ audio_mixer_control.codec_capture_active = 0;
+ handle = audio_mixer_control.voice_codec_handle;
+ disable_codec(handle);
+ audio_mixer_control.voice_codec_handle = NULL;
+ chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle = NULL;
+
+ pmic_audio_close(handle);
+
+ ssi_receive_enable(ssi, false);
+ ssi_interrupt_disable(ssi, ssi_rx_dma_interrupt_enable);
+ ssi_rx_fifo_enable(ssi, ssi_fifo_0, false);
+ ssi_enable(ssi, false);
+ mxc_dma_free((mxc_audio->s[1]).dma_wchannel);
+
+ chip->s[substream->pstr->stream].stream = NULL;
+
+ /* disable ssi clock */
+ clk_disable(audio_data->ssi_clk[ssi]);
+
+ return 0;
+}
+
+/*!
+ * This function configure the Audio HW in terms of memory allocation.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime;
+ int ret = 0, size;
+ int device, stream_id = -1;
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ struct snd_dma_buffer *dmab;
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+
+ runtime = substream->runtime;
+ ret =
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+ if (ret < 0)
+ return ret;
+ size = params_buffer_bytes(hw_params);
+ device = substream->pcm->device;
+ if (device == 0)
+ stream_id = 0;
+ else if (device == 1)
+ stream_id = 2;
+
+ runtime->dma_addr = virt_to_phys(runtime->dma_area);
+
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && g_audio_iram_en
+ && stream_id == 0) {
+ if (runtime->dma_buffer_p
+ && (runtime->dma_buffer_p != &g_iram_dmab)) {
+ snd_pcm_lib_free_pages(substream);
+ }
+ dmab = &g_iram_dmab;
+ dmab->dev = substream->dma_buffer.dev;
+ dmab->area = (char *)ADMA_BASE_VADDR;
+ dmab->addr = ADMA_BASE_PADDR;
+ dmab->bytes = size;
+ snd_pcm_set_runtime_buffer(substream, dmab);
+ runtime->dma_bytes = size;
+ } else
+#endif /* CONFIG_SND_MXC_PMIC_IRAM */
+ {
+ ret = snd_pcm_lib_malloc_pages(substream, size);
+ if (ret < 0)
+ return ret;
+
+ runtime->dma_addr = virt_to_phys(runtime->dma_area);
+ }
+
+ pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_addr 0x(%x)\n",
+ (unsigned int)runtime->dma_addr);
+ pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_area 0x(%x)\n",
+ (unsigned int)runtime->dma_area);
+ pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_bytes 0x(%x)\n",
+ (unsigned int)runtime->dma_bytes);
+
+ return ret;
+}
+
+/*!
+ * This function frees the audio hardware at the end of playback/capture.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_hw_free(struct snd_pcm_substream *substream)
+{
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ if (substream->runtime->dma_buffer_p == &g_iram_dmab) {
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+ } else
+#endif /* CONFIG_SND_MXC_PMIC_IRAM */
+ {
+ return snd_pcm_lib_free_pages(substream);
+ }
+ return 0;
+}
+
+/*!
+ * This function configures the hardware to allow audio
+ * capture operations. It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_capture_prepare(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ audio_stream_t *s;
+ int ssi;
+
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[substream->pstr->stream];
+ ssi = s->ssi;
+
+ normalize_speed_for_pmic(substream);
+
+ pr_debug("substream->pstr->stream %d\n", substream->pstr->stream);
+ pr_debug("SSI %d\n", ssi + 1);
+ configure_dam_pmic_master(ssi);
+
+ configure_ssi_rx(substream);
+
+ ssi_interrupt_enable(ssi, ssi_rx_dma_interrupt_enable);
+
+ if (configure_pmic_recording(substream) == -1)
+ pr_debug(KERN_ERR "MXC: PMIC Record Config FAILED\n");
+
+ ssi_interrupt_enable(ssi, ssi_rx_fifo_0_full);
+ ssi_receive_enable(ssi, true);
+
+ msleep(20);
+ set_pmic_channels(substream);
+
+ s->period = 0;
+ s->periods = 0;
+
+ return 0;
+}
+
+/*!
+ * This function opens an PMIC audio device in capture mode
+ * on Codec.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_audio_capture_open(struct snd_pcm_substream *substream)
+{
+ mxc_pmic_audio_t *chip;
+ struct snd_pcm_runtime *runtime;
+ int stream_id;
+ int err;
+ PMIC_AUDIO_HANDLE temp_handle;
+ audio_stream_t *s;
+
+ chip = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+ stream_id = substream->pstr->stream;
+ s = &chip->s[stream_id];
+ err = -1;
+
+ if ((audio_data->ssi_num == 1)
+ && (audio_mixer_control.stdac_playback_active)) {
+ return -EBUSY;
+ }
+ if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) {
+ audio_mixer_control.voice_codec_handle = temp_handle;
+ audio_mixer_control.codec_capture_active = 1;
+ chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle =
+ temp_handle;
+ } else {
+ if (audio_mixer_control.codec_playback_active) {
+ temp_handle = audio_mixer_control.voice_codec_handle;
+ } else {
+ return -EBUSY;
+ }
+ }
+ pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW);
+
+ chip->s[stream_id].stream = substream;
+
+ if (stream_id == SNDRV_PCM_STREAM_CAPTURE) {
+ runtime->hw = snd_mxc_pmic_capture;
+ } else {
+ return err;
+ }
+
+ if ((err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS)) <
+ 0) {
+ return err;
+ }
+
+ if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_capture_rates)) < 0) {
+ return err;
+ }
+
+ /* setup DMA controller for Record */
+ err = configure_read_channel(&mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE],
+ audio_capture_dma_callback);
+ if (err < 0) {
+ return err;
+ }
+
+ /* enable ssi clock */
+ clk_enable(audio_data->ssi_clk[s->ssi]);
+
+ msleep(50);
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+static struct page *snd_mxc_audio_playback_nopage(struct vm_area_struct *area,
+ unsigned long address,
+ int *type)
+{
+ struct snd_pcm_substream *substream = area->vm_private_data;
+ struct snd_pcm_runtime *runtime;
+ unsigned long offset;
+ struct page *page;
+ void *vaddr;
+ size_t dma_bytes;
+
+ if (substream == NULL)
+ return NOPAGE_OOM;
+ runtime = substream->runtime;
+ if (g_audio_iram_en) {
+ return NOPAGE_SIGBUS;
+ }
+ offset = area->vm_pgoff << PAGE_SHIFT;
+ offset += address - area->vm_start;
+ if (snd_BUG_ON(offset % PAGE_SIZE) != 0)
+ return NOPAGE_OOM;
+ dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+ if (offset > dma_bytes - PAGE_SIZE)
+ return NOPAGE_SIGBUS;
+ if (substream->ops->page) {
+ page = substream->ops->page(substream, offset);
+ if (!page)
+ return NOPAGE_OOM;
+ } else {
+ vaddr = runtime->dma_area + offset;
+ page = virt_to_page(vaddr);
+ }
+ get_page(page);
+ if (type)
+ *type = VM_FAULT_MINOR;
+ return page;
+}
+
+static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = {
+ .open = snd_pcm_mmap_data_open,
+ .close = snd_pcm_mmap_data_close,
+ .nopage = snd_mxc_audio_playback_nopage,
+};
+
+#ifdef CONFIG_ARCH_MX3
+static inline int snd_mxc_set_pte_attr(struct mm_struct *mm,
+ pmd_t * pmd,
+ unsigned long addr, unsigned long end)
+{
+
+ pte_t *pte;
+ spinlock_t *ptl;
+ pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
+
+ if (!pte)
+ return -ENOMEM;
+ do {
+ BUG_ON(pte_none(*pte)); //The mapping is created. It should not be none.
+ *(pte - 512) |= 0x83; //Directly modify to non-shared device
+ } while (pte++, addr += PAGE_SIZE, addr != end);
+
+ pte_unmap_unlock(pte - 1, ptl);
+
+ return 0;
+
+}
+
+static int snd_mxc_set_pmd_attr(struct mm_struct *mm,
+ pud_t * pud,
+ unsigned long addr, unsigned long end)
+{
+
+ pmd_t *pmd;
+ unsigned long next;
+ pmd = pmd_alloc(mm, pud, addr);
+
+ if (!pmd)
+ return -ENOMEM;
+ do {
+
+ next = pmd_addr_end(addr, end);
+ if (snd_mxc_set_pte_attr(mm, pmd, addr, next))
+ return -ENOMEM;
+
+ } while (pmd++, addr = next, addr != end);
+
+ return 0;
+
+}
+
+static int snd_mxc_set_pud_attr(struct mm_struct *mm,
+ pgd_t * pgd,
+ unsigned long addr, unsigned long end)
+{
+
+ pud_t *pud;
+ unsigned long next;
+ pud = pud_alloc(mm, pgd, addr);
+
+ if (!pud)
+ return -ENOMEM;
+ do {
+
+ next = pud_addr_end(addr, end);
+ if (snd_mxc_set_pmd_attr(mm, pud, addr, next))
+ return -ENOMEM;
+
+ } while (pud++, addr = next, addr != end);
+
+ return 0;
+
+}
+
+static inline int snd_mxc_set_pgd_attr(struct vm_area_struct *area)
+{
+
+ int ret = 0;
+ pgd_t *pgd;
+ struct mm_struct *mm = current->mm;
+ unsigned long next, addr = area->vm_start;
+
+ pgd = pgd_offset(mm, addr);
+ flush_cache_range(area, addr, area->vm_end);
+
+ do {
+ if (!pgd_present(*pgd))
+ return -1;
+
+ next = pgd_addr_end(addr, area->vm_end);
+ if ((ret = snd_mxc_set_pud_attr(mm, pgd, addr, next)))
+ break;
+
+ } while (pgd++, addr = next, addr != area->vm_end);
+
+ return ret;
+
+}
+
+#else
+#define snd_mxc_set_page_attr() (0)
+#endif
+
+static int snd_mxc_audio_playback_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+ int ret = 0;
+ area->vm_ops = &snd_mxc_audio_playback_vm_ops;
+ area->vm_private_data = substream;
+ if (g_audio_iram_en) {
+ unsigned long off = area->vm_pgoff << PAGE_SHIFT;
+ unsigned long phys = ADMA_BASE_PADDR + off;
+ unsigned long size = area->vm_end - area->vm_start;
+ if (off + size > MAX_IRAM_SIZE) {
+ return -EINVAL;
+ }
+ area->vm_page_prot = pgprot_nonshareddev(area->vm_page_prot);
+ area->vm_flags |= VM_IO;
+ ret =
+ remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT,
+ size, area->vm_page_prot);
+ if (ret == 0) {
+ ret = snd_mxc_set_pgd_attr(area);
+ }
+
+ } else {
+ area->vm_flags |= VM_RESERVED;
+ }
+ if (ret == 0)
+ area->vm_ops->open(area);
+ return ret;
+}
+
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+
+/*!
+ * This structure is the list of operation that the driver
+ * must provide for the capture interface
+ */
+static struct snd_pcm_ops snd_card_mxc_audio_capture_ops = {
+ .open = snd_card_mxc_audio_capture_open,
+ .close = snd_card_mxc_audio_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mxc_audio_hw_params,
+ .hw_free = snd_mxc_audio_hw_free,
+ .prepare = snd_mxc_audio_capture_prepare,
+ .trigger = snd_mxc_audio_capture_trigger,
+ .pointer = snd_mxc_audio_capture_pointer,
+};
+
+/*!
+ * This structure is the list of operation that the driver
+ * must provide for the playback interface
+ */
+static struct snd_pcm_ops snd_card_mxc_audio_playback_ops = {
+ .open = snd_card_mxc_audio_playback_open,
+ .close = snd_card_mxc_audio_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mxc_audio_hw_params,
+ .hw_free = snd_mxc_audio_hw_free,
+ .prepare = snd_mxc_audio_playback_prepare,
+ .trigger = snd_mxc_audio_playback_trigger,
+ .pointer = snd_mxc_audio_playback_pointer,
+#ifdef CONFIG_SND_MXC_PMIC_IRAM
+ .mmap = snd_mxc_audio_playback_mmap,
+#endif /*CONFIG_SND_MXC_PMIC_IRAM */
+};
+
+/*!
+ * This functions initializes the capture audio device supported by
+ * PMIC IC.
+ *
+ * @param mxc_audio pointer to the sound card structure
+ *
+ */
+void init_device_capture(mxc_pmic_audio_t * mxc_audio)
+{
+ audio_stream_t *audio_stream;
+ pmic_audio_device_t *pmic_device;
+
+ audio_stream = &mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE];
+ pmic_device = &audio_stream->pmic_audio_device;
+
+ /* These parameters defines the identity of
+ * the device (codec or stereodac)
+ */
+ audio_stream->ssi = SSI1;
+ audio_stream->dam_port = DAM_PORT_4;
+ pmic_device->ssi = SSI1;
+
+ pmic_device->mode = BUS_MASTER_MODE;
+ pmic_device->protocol = NETWORK_MODE;
+
+#ifdef CONFIG_MXC_PMIC_SC55112
+ pmic_device->pll = CLOCK_IN_CLKIN;
+ pmic_device->pll_rate = VCODEC_CLI_33_6MHZ;
+#else
+ if (machine_is_mx31ads()) {
+ pmic_device->pll = CLOCK_IN_CLIB;
+ } else {
+ pmic_device->pll = CLOCK_IN_CLIA;
+ }
+ pmic_device->pll_rate = VCODEC_CLI_26MHZ;
+#endif
+ pmic_device->bcl_inverted = 0;
+ pmic_device->fs_inverted = 0;
+
+}
+
+/*!
+ * This functions initializes the playback audio device supported by
+ * PMIC IC.
+ *
+ * @param mxc_audio pointer to the sound card structure.
+ * @param device device ID of PCM instance.
+ *
+ */
+void init_device_playback(mxc_pmic_audio_t * mxc_audio, int device)
+{
+ audio_stream_t *audio_stream;
+ pmic_audio_device_t *pmic_device;
+ if (device == 0)
+ audio_stream = &mxc_audio->s[0];
+ else
+ audio_stream = &mxc_audio->s[2];
+ pmic_device = &audio_stream->pmic_audio_device;
+
+ /* These parameters defines the identity of
+ * the device (codec or stereodac)
+ */
+ if (device == 0) {
+ if (audio_data->ssi_num == 2) {
+ audio_stream->ssi = SSI2;
+ audio_stream->dam_port = DAM_PORT_5;
+ pmic_device->ssi = SSI2;
+ } else {
+ audio_stream->ssi = SSI1;
+ audio_stream->dam_port = DAM_PORT_4;
+ pmic_device->ssi = SSI1;
+ }
+
+ pmic_device->mode = BUS_MASTER_MODE;
+ pmic_device->protocol = NETWORK_MODE;
+
+#ifdef CONFIG_MXC_PMIC_SC55112
+ pmic_device->pll = CLOCK_IN_CLKIN;
+ pmic_device->pll_rate = STDAC_CLI_33_6MHZ;
+#else
+ if (machine_is_mx31ads()) {
+ pmic_device->pll = CLOCK_IN_CLIB;
+ } else {
+ pmic_device->pll = CLOCK_IN_CLIA;
+ }
+ pmic_device->pll_rate = STDAC_CLI_26MHZ;
+#endif
+
+ pmic_device->bcl_inverted = 0;
+ pmic_device->fs_inverted = 0;
+
+ } else if ((device == 1) || (device == 2)) {
+ audio_stream->ssi = SSI1;
+ audio_stream->dam_port = DAM_PORT_4;
+ pmic_device->ssi = SSI1;
+
+ pmic_device->mode = BUS_MASTER_MODE;
+ pmic_device->protocol = NETWORK_MODE;
+
+#ifdef CONFIG_MXC_PMIC_SC55112
+ pmic_device->pll = CLOCK_IN_CLKIN;
+ pmic_device->pll_rate = VCODEC_CLI_33_6MHZ;
+#else
+ if (machine_is_mx31ads()) {
+ pmic_device->pll = CLOCK_IN_CLIB;
+ } else {
+ pmic_device->pll = CLOCK_IN_CLIA;
+ }
+ pmic_device->pll_rate = VCODEC_CLI_26MHZ;
+#endif
+ pmic_device->bcl_inverted = 0;
+ pmic_device->fs_inverted = 0;
+ }
+
+}
+
+/*!
+ * This functions initializes the mixer related information
+ *
+ * @param mxc_audio pointer to the sound card structure.
+ *
+ */
+void mxc_pmic_mixer_controls_init(mxc_pmic_audio_t * mxc_audio)
+{
+ audio_mixer_control_t *audio_control;
+ int i = 0;
+
+ audio_control = &audio_mixer_control;
+
+ memset(audio_control, 0, sizeof(audio_mixer_control_t));
+ sema_init(&audio_control->sem, 1);
+
+ audio_control->input_device = SOUND_MASK_MIC;
+ audio_control->output_device = SOUND_MASK_VOLUME | SOUND_MASK_PCM;
+
+ /* PMIC has to internal sources that can be routed to output
+ One is codec direct out and the other is mixer out
+ Initially we configure all outputs to have no source and
+ will be later configured either by PCM stream handler or mixer */
+ for (i = 0; i < OP_MAXDEV && i != OP_HEADSET; i++) {
+ audio_control->source_for_output[i] = MIXER_OUT;
+ }
+
+ /* These bits are initially reset and set when playback begins */
+ audio_control->codec_out_to_mixer = 0;
+ audio_control->stdac_out_to_mixer = 0;
+
+ audio_control->mixer_balance = 50;
+ if (machine_is_mx31ads())
+ audio_control->mixer_mono_adder = STEREO_OPPOSITE_PHASE;
+ else
+ audio_control->mixer_mono_adder = MONO_ADDER_OFF;
+ /* Default values for input and output */
+ audio_control->input_volume = ((40 << 8) & 0xff00) | (40 & 0x00ff);
+ audio_control->master_volume_out = ((50 << 8) & 0xff00) | (50 & 0x00ff);
+
+ if (PMIC_SUCCESS != pmic_audio_set_autodetect(1))
+ msleep(30);
+}
+
+/*!
+ * This functions initializes the 2 audio devices supported by
+ * PMIC IC. The parameters define the type of device (CODEC or STEREODAC)
+ *
+ * @param mxc_audio pointer to the sound card structure.
+ * @param device device id of the PCM stream.
+ *
+ */
+void mxc_pmic_audio_init(mxc_pmic_audio_t * mxc_audio, int device)
+{
+ if (device == 0) {
+ mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Audio out";
+ mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id =
+ SNDRV_PCM_STREAM_PLAYBACK;
+ mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].id = "Audio in";
+ mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].stream_id =
+ SNDRV_PCM_STREAM_CAPTURE;
+ } else if (device == 1) {
+ mxc_audio->s[2].id = "Audio out";
+ mxc_audio->s[2].stream_id = 2;
+ } else if (device == 2) {
+ mxc_audio->s[2].id = "Audio out";
+ mxc_audio->s[2].stream_id = 3;
+ }
+
+ init_device_playback(mxc_audio, device);
+ if (!device) {
+ init_device_capture(mxc_audio);
+ }
+}
+
+/*!
+ * This function the soundcard structure.
+ *
+ * @param mxc_audio pointer to the sound card structure.
+ * @param device the device index (zero based)
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int __devinit snd_card_mxc_audio_pcm(mxc_pmic_audio_t *mxc_audio,
+ int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ /*
+ * Create a new PCM instance with 1 capture stream and 1 playback substream
+ */
+ if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 1, &pcm)) < 0) {
+ return err;
+ }
+
+ /*
+ * this sets up our initial buffers and sets the dma_type to isa.
+ * isa works but I'm not sure why (or if) it's the right choice
+ * this may be too large, trying it for now
+ */
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL), MAX_BUFFER_SIZE * 2,
+ MAX_BUFFER_SIZE * 2);
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_card_mxc_audio_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_card_mxc_audio_capture_ops);
+
+ pcm->private_data = mxc_audio;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name));
+ mxc_audio->pcm[device] = pcm;
+ mxc_pmic_audio_init(mxc_audio, device);
+
+ /* Allocating a second device for PCM playback on voice codec */
+ device = 1;
+ if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) {
+ return err;
+ }
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL), MAX_BUFFER_SIZE * 2,
+ MAX_BUFFER_SIZE * 2);
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_card_mxc_audio_playback_ops);
+ pcm->private_data = mxc_audio;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name));
+ mxc_audio->pcm[device] = pcm;
+ mxc_pmic_audio_init(mxc_audio, device);
+
+ device = 2;
+ if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) {
+ return err;
+ }
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL), MAX_BUFFER_SIZE * 2,
+ MAX_BUFFER_SIZE * 2);
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_card_mxc_audio_playback_ops);
+ pcm->private_data = mxc_audio;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name));
+ mxc_audio->pcm[device] = pcm;
+
+ /* End of allocation */
+ /* FGA for record and not hard coded playback */
+ mxc_pmic_mixer_controls_init(mxc_audio);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*!
+ * This function suspends all active streams.
+ *
+ * TBD
+ *
+ * @param card pointer to the sound card structure.
+ * @param state requested state
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ struct snd_card *card = platform_get_drvdata(dev);
+ mxc_pmic_audio_t *chip = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ snd_pcm_suspend_all(chip->pcm[0]);
+ //mxc_alsa_audio_shutdown(chip);
+
+ return 0;
+}
+
+/*!
+ * This function resumes all suspended streams.
+ *
+ * TBD
+ *
+ * @param card pointer to the sound card structure.
+ * @param state requested state
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_audio_resume(struct platform_device *dev)
+{
+ struct snd_card *card = platform_get_drvdata(dev);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+ return 0;
+}
+#endif /* COMFIG_PM */
+
+/*!
+ * This function frees the sound card structure
+ *
+ * @param card pointer to the sound card structure.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+void snd_mxc_audio_free(struct snd_card *card)
+{
+ mxc_pmic_audio_t *chip;
+
+ chip = card->private_data;
+ audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]);
+ audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]);
+ mxc_audio = NULL;
+ card->private_data = NULL;
+ kfree(chip);
+
+}
+
+/*!
+ * This function initializes the driver in terms of memory of the soundcard
+ * and some basic HW clock settings.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int __devinit mxc_alsa_audio_probe(struct platform_device *pdev)
+{
+ int err;
+ struct snd_card *card;
+
+ audio_data = (struct mxc_audio_platform_data *)pdev->dev.platform_data;
+ if (!audio_data) {
+ dev_err(&pdev->dev, "Can't get the platform data for ALSA\n");
+ return -EINVAL;
+ }
+ /* register the soundcard */
+ err = snd_card_create(-1, id, THIS_MODULE, sizeof(mxc_pmic_audio_t), &card);
+ if (err < 0) {
+ return -ENOMEM;
+ }
+
+ mxc_audio = kcalloc(1, sizeof(*mxc_audio), GFP_KERNEL);
+ if (mxc_audio == NULL) {
+ return -ENOMEM;
+ }
+
+ card->private_data = (void *)mxc_audio;
+ card->private_free = snd_mxc_audio_free;
+
+ mxc_audio->card = card;
+ card->dev = &pdev->dev;
+ if ((err = snd_card_mxc_audio_pcm(mxc_audio, 0)) < 0) {
+ goto nodev;
+ }
+
+ if (0 == mxc_alsa_create_ctl(card, (void *)&audio_mixer_control))
+ printk(KERN_INFO "Control ALSA component registered\n");
+
+#ifdef CONFIG_HEADSET_DETECT_ENABLE
+ pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW);
+ pmic_audio_set_callback((PMIC_AUDIO_CALLBACK) HSCallback,
+ HEADSET_DETECTED | HEADSET_REMOVED, &hs_state);
+#endif
+ /* Set autodetect feature in order to allow audio operations */
+
+ spin_lock_init(&(mxc_audio->s[0].dma_lock));
+ spin_lock_init(&(mxc_audio->s[1].dma_lock));
+ spin_lock_init(&(mxc_audio->s[2].dma_lock));
+
+ strcpy(card->driver, "MXC");
+ strcpy(card->shortname, "PMIC-audio");
+ sprintf(card->longname, "MXC Freescale with PMIC");
+
+ if ((err = snd_card_register(card)) == 0) {
+ pr_debug(KERN_INFO "MXC audio support initialized\n");
+ platform_set_drvdata(pdev, card);
+ return 0;
+ }
+
+ nodev:
+ snd_card_free(card);
+ return err;
+}
+
+static int __devexit mxc_alsa_audio_remove(struct platform_device *dev)
+{
+ snd_card_free(mxc_audio->card);
+ kfree(mxc_audio);
+ platform_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver mxc_alsa_audio_driver = {
+ .probe = mxc_alsa_audio_probe,
+ .remove = __devexit_p(mxc_alsa_audio_remove),
+#ifdef CONFIG_PM
+ .suspend = snd_mxc_audio_suspend,
+ .resume = snd_mxc_audio_resume,
+#endif
+ .driver = {
+ .name = "mxc_alsa",
+ },
+};
+
+static int __init mxc_alsa_audio_init(void)
+{
+ return platform_driver_register(&mxc_alsa_audio_driver);
+}
+
+/*!
+ * This function frees the sound driver structure.
+ *
+ */
+
+static void __exit mxc_alsa_audio_exit(void)
+{
+ platform_driver_unregister(&mxc_alsa_audio_driver);
+}
+
+module_init(mxc_alsa_audio_init);
+module_exit(mxc_alsa_audio_exit);
+
+MODULE_AUTHOR("FREESCALE SEMICONDUCTOR");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MXC driver for ALSA");
+MODULE_SUPPORTED_DEVICE("{{PMIC}}");
+
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for MXC + PMIC soundcard.");
diff --git a/sound/arm/mxc-alsa-pmic.h b/sound/arm/mxc-alsa-pmic.h
new file mode 100644
index 000000000000..46c5ca4ae1ab
--- /dev/null
+++ b/sound/arm/mxc-alsa-pmic.h
@@ -0,0 +1,110 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+ /*!
+ * @file mxc-alsa-pmic.h
+ * @brief
+ * @ingroup SOUND_DRV
+ */
+
+#ifndef __MXC_ALSA_PMIC_H__
+#define __MXC_ALSA_PMIC_H__
+
+#ifdef __KERNEL__
+
+ /**/
+#define PMIC_MASTER 0x1
+#define PMIC_SLAVE 0x2
+ /**/
+#define DAM_PORT_4 port_4
+#define DAM_PORT_5 port_5
+ /**/
+#define PMIC_STEREODAC 0x1
+#define PMIC_CODEC 0x2
+ /**/
+#define PMIC_AUDIO_ADDER_STEREO 0x1
+#define PMIC_AUDIO_ADDER_STEREO_OPPOSITE 0x2
+#define PMIC_AUDIO_ADDER_MONO 0x4
+#define PMIC_AUDIO_ADDER_MONO_OPPOSITE 0x8
+#define TX_WATERMARK 0x4
+#define RX_WATERMARK 0x6
+ /**/
+#define SSI_DEFAULT_FIFO 0x0
+#define DEFAULT_MASTER_CLOCK 0x1
+ /**/
+#define SDMA_TXFIFO_WATERMARK 0x4
+#define SDMA_RXFIFO_WATERMARK 0x6
+ /**/
+#define TIMESLOTS_2 0x3
+#define TIMESLOTS_4 0x2
+#define SAMPLE_RATE_MAX 0x9
+ /**/
+#define CLK_SELECT_SLAVE_BCL 0x7
+#define CLK_SELECT_SLAVE_CLI 0x5
+#define CLK_SELECT_MASTER_CLI 0x4
+/* Volume to balance ratio */
+#define VOLUME_BALANCE_RATIO ((6 + 39) / (21 + 21))
+/* -21dB */
+#define PMIC_BALANCE_MIN 0x0
+/* 0dB*/
+#define PMIC_BALANCE_MAX 0x7
+/* -21dB left */
+#define BALANCE_MIN 0x0
+/* 0db, no balance */
+#define NO_BALANCE 0x32
+/* -21dB right*/
+#define BALANCE_MAX 0x64
+/* -8dB */
+#define PMIC_INPUT_VOLUME_MIN 0x0
+/* +23dB */
+#define PMIC_INPUT_VOLUME_MAX 0x1f
+/* -39dB */
+#define PMIC_OUTPUT_VOLUME_MIN PMIC_INPUT_VOLUME_MIN
+/* +6dB */
+#define PMIC_OUTPUT_VOLUME_MAX 0xd
+/* -8dB */
+#define INPUT_VOLUME_MIN 0x0
+/* +23dB */
+#define INPUT_VOLUME_MAX 0x64
+/* -39dB */
+#define OUTPUT_VOLUME_MIN 0x0
+/* +6dB */
+#define OUTPUT_VOLUME_MAX 0x64
+/* 96 Khz */
+#define SAMPLE_FREQ_MAX 96000
+/* 8 Khz */
+#define SAMPLE_FREQ_MIN 8000
+/*!
+ * Define channels available on MC13783. This is mainly used
+ * in mixer interface to control different input/output
+ * devices
+ */
+#define MXC_STEREO_OUTPUT ( SOUND_MASK_VOLUME | SOUND_MASK_PCM )
+#define MXC_STEREO_INPUT ( SOUND_MASK_PHONEIN )
+#define MXC_MONO_OUTPUT ( SOUND_MASK_PHONEOUT | SOUND_MASK_SPEAKER )
+#define MXC_MONO_INPUT ( SOUND_MASK_LINE | SOUND_MASK_MIC )
+#define SNDCTL_CLK_SET_MASTER _SIOR('Z', 30, int)
+#define SNDCTL_PMIC_READ_OUT_BALANCE _SIOR('Z', 8, int)
+//#define SNDCTL_MC13783_WRITE_OUT_BALANCE _SIOWR('Z', 9, int)
+#define SNDCTL_PMIC_WRITE_OUT_ADDER _SIOWR('Z', 10, int)
+#define SNDCTL_PMIC_READ_OUT_ADDER _SIOR('Z', 11, int)
+#define SNDCTL_PMIC_WRITE_CODEC_FILTER _SIOWR('Z', 12, int)
+#define SNDCTL_PMIC_READ_CODEC_FILTER _SIOR('Z', 13, int)
+#define SNDCTL_DAM_SET_OUT_PORT _SIOWR('Z', 14, int)
+#endif /* __KERNEL__ */
+#endif /* __MXC_ALSA_PMIC_H__ */
diff --git a/sound/arm/mxc-alsa-spdif.c b/sound/arm/mxc-alsa-spdif.c
new file mode 100644
index 000000000000..93a34cd03e11
--- /dev/null
+++ b/sound/arm/mxc-alsa-spdif.c
@@ -0,0 +1,2260 @@
+/*
+ * Copyright (C) 2007-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @defgroup SOUND_DRV MXC Sound Driver for ALSA
+ */
+
+/*!
+ * @file mxc-alsa-spdif.c
+ * @brief this fle mxc-alsa-spdif.c
+ * @brief this file implements mxc alsa driver for spdif.
+ * The spdif tx supports consumer channel for linear PCM and
+ * compressed audio data. The supported sample rates are
+ * 48KHz, 44.1KHz and 32KHz.
+ *
+ * @ingroup SOUND_DRV
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/soundcard.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+#include <mach/dma.h>
+
+#define MXC_SPDIF_NAME "MXC_SPDIF"
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for spdif sound card.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for spdif sound card.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable spdif sound card.");
+
+#define SPDIF_MAX_BUF_SIZE (32*1024)
+#define SPDIF_DMA_BUF_SIZE (8*1024)
+#define SPDIF_MIN_PERIOD_SIZE 64
+#define SPDIF_MIN_PERIOD 2
+#define SPDIF_MAX_PERIOD 255
+
+#define SPDIF_REG_SCR 0x00
+#define SPDIF_REG_SRCD 0x04
+#define SPDIF_REG_SRPC 0x08
+#define SPDIF_REG_SIE 0x0C
+#define SPDIF_REG_SIS 0x10
+#define SPDIF_REG_SIC 0x10
+#define SPDIF_REG_SRL 0x14
+#define SPDIF_REG_SRR 0x18
+#define SPDIF_REG_SRCSLH 0x1C
+#define SPDIF_REG_SRCSLL 0x20
+#define SPDIF_REG_SQU 0x24
+#define SPDIF_REG_SRQ 0x28
+#define SPDIF_REG_STL 0x2C
+#define SPDIF_REG_STR 0x30
+#define SPDIF_REG_STCSCH 0x34
+#define SPDIF_REG_STCSCL 0x38
+#define SPDIF_REG_SRFM 0x44
+#define SPDIF_REG_STC 0x50
+
+/* SPDIF Configuration register */
+#define SCR_RXFIFO_CTL_ZERO (1 << 23)
+#define SCR_RXFIFO_OFF (1 << 22)
+#define SCR_RXFIFO_RST (1 << 21)
+#define SCR_RXFIFO_FSEL_BIT (19)
+#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_BIT)
+#define SCR_RXFIFO_AUTOSYNC (1 << 18)
+#define SCR_TXFIFO_AUTOSYNC (1 << 17)
+#define SCR_TXFIFO_ESEL_BIT (15)
+#define SCR_TXFIFO_ESEL_MASK (0x3 << SCR_TXFIFO_FSEL_BIT)
+#define SCR_LOW_POWER (1 << 13)
+#define SCR_SOFT_RESET (1 << 12)
+#define SCR_TXFIFO_ZERO (0 << 10)
+#define SCR_TXFIFO_NORMAL (1 << 10)
+#define SCR_TXFIFO_ONESAMPLE (1 << 11)
+#define SCR_DMA_RX_EN (1 << 9)
+#define SCR_DMA_TX_EN (1 << 8)
+#define SCR_VAL_CLEAR (1 << 5)
+#define SCR_TXSEL_OFF (0 << 2)
+#define SCR_TXSEL_RX (1 << 2)
+#define SCR_TXSEL_NORMAL (0x5 << 2)
+#define SCR_USRC_SEL_NONE (0x0)
+#define SCR_USRC_SEL_RECV (0x1)
+#define SCR_USRC_SEL_CHIP (0x3)
+
+/* SPDIF CDText control */
+#define SRCD_CD_USER_OFFSET 1
+#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET)
+
+/* SPDIF Phase Configuration register */
+#define SRPC_DPLL_LOCKED (1 << 6)
+#define SRPC_CLKSRC_SEL_OFFSET 7
+#define SRPC_CLKSRC_SEL_LOCKED 5
+/* gain sel */
+#define SRPC_GAINSEL_OFFSET 3
+
+enum spdif_gainsel {
+ GAINSEL_MULTI_24 = 0,
+ GAINSEL_MULTI_16,
+ GAINSEL_MULTI_12,
+ GAINSEL_MULTI_8,
+ GAINSEL_MULTI_6,
+ GAINSEL_MULTI_4,
+ GAINSEL_MULTI_3,
+ GAINSEL_MULTI_MAX,
+};
+
+#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8
+
+/* SPDIF interrupt mask define */
+#define INT_DPLL_LOCKED (1 << 20)
+#define INT_TXFIFO_UNOV (1 << 19)
+#define INT_TXFIFO_RESYNC (1 << 18)
+#define INT_CNEW (1 << 17)
+#define INT_VAL_NOGOOD (1 << 16)
+#define INT_SYM_ERR (1 << 15)
+#define INT_BIT_ERR (1 << 14)
+#define INT_URX_FUL (1 << 10)
+#define INT_URX_OV (1 << 9)
+#define INT_QRX_FUL (1 << 8)
+#define INT_QRX_OV (1 << 7)
+#define INT_UQ_SYNC (1 << 6)
+#define INT_UQ_ERR (1 << 5)
+#define INT_RX_UNOV (1 << 4)
+#define INT_RX_RESYNC (1 << 3)
+#define INT_LOSS_LOCK (1 << 2)
+#define INT_TX_EMPTY (1 << 1)
+#define INT_RXFIFO_FUL (1 << 0)
+
+/* SPDIF Clock register */
+#define STC_SYSCLK_DIV_OFFSET 11
+#define STC_TXCLK_SRC_OFFSET 8
+#define STC_TXCLK_DIV_OFFSET 0
+
+#define SPDIF_CSTATUS_BYTE 6
+#define SPDIF_UBITS_SIZE 96
+#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE/8)
+
+enum spdif_clk_accuracy {
+ SPDIF_CLK_ACCURACY_LEV2 = 0,
+ SPDIF_CLK_ACCURACY_LEV1 = 2,
+ SPDIF_CLK_ACCURACY_LEV3 = 1,
+ SPDIF_CLK_ACCURACY_RESV = 3
+};
+
+/* SPDIF clock source */
+enum spdif_clk_src {
+ SPDIF_CLK_SRC1 = 0,
+ SPDIF_CLK_SRC2,
+ SPDIF_CLK_SRC3,
+ SPDIF_CLK_SRC4,
+ SPDIF_CLK_SRC5,
+};
+
+enum spdif_max_wdl {
+ SPDIF_MAX_WDL_20,
+ SPDIF_MAX_WDL_24
+};
+
+enum spdif_wdl {
+ SPDIF_WDL_DEFAULT = 0,
+ SPDIF_WDL_FIFTH = 4,
+ SPDIF_WDL_FOURTH = 3,
+ SPDIF_WDL_THIRD = 2,
+ SPDIF_WDL_SECOND = 1,
+ SPDIF_WDL_MAX = 5
+};
+
+static unsigned int gainsel_multi[GAINSEL_MULTI_MAX] = {
+ 24 * 1024, 16 * 1024, 12 * 1024,
+ 8 * 1024, 6 * 1024, 4 * 1024,
+ 3 * 1024,
+};
+
+/*!
+ * SPDIF control structure
+ * Defines channel status, subcode and Q sub
+ */
+struct spdif_mixer_control {
+
+ /* spinlock to access control data */
+ spinlock_t ctl_lock;
+ /* IEC958 channel tx status bit */
+ unsigned char ch_status[4];
+ /* User bits */
+ unsigned char subcode[2 * SPDIF_UBITS_SIZE];
+ /* Q subcode part of user bits */
+ unsigned char qsub[2 * SPDIF_QSUB_SIZE];
+ /* buffer ptrs for writer */
+ unsigned int upos;
+ unsigned int qpos;
+ /* ready buffer index of the two buffers */
+ unsigned int ready_buf;
+};
+
+/*!
+ * This structure represents an audio stream in term of
+ * channel DMA, HW configuration on spdif controller.
+ */
+struct mxc_spdif_stream {
+
+ /*!
+ * identification string
+ */
+ char *id;
+ /*!
+ * device identifier for DMA
+ */
+ int dma_wchannel;
+ /*!
+ * we are using this stream for transfer now
+ */
+ int active:1;
+ /*!
+ * current transfer period
+ */
+ int period;
+ /*!
+ * current count of transfered periods
+ */
+ int periods;
+ /*!
+ * for locking in DMA operations
+ */
+ spinlock_t dma_lock;
+ /*!
+ * Alsa substream pointer
+ */
+ struct snd_pcm_substream *stream;
+};
+
+struct mxc_spdif_device {
+ /*!
+ * SPDIF module register base address
+ */
+ unsigned long __iomem *reg_base;
+
+ /*!
+ * spdif tx available or not
+ */
+ int mxc_spdif_tx;
+
+ /*!
+ * spdif rx available or not
+ */
+ int mxc_spdif_rx;
+
+ /*!
+ * spdif 44100 clock src
+ */
+ int spdif_txclk_44100;
+
+ /*!
+ * spdif 48000 clock src
+ */
+ int spdif_txclk_48000;
+
+ /*!
+ * ALSA SPDIF sound card handle
+ */
+ struct snd_card *card;
+
+ /*!
+ * ALSA spdif driver type handle
+ */
+ struct snd_pcm *pcm;
+
+ /*!
+ * DPLL locked status
+ */
+ atomic_t dpll_locked;
+
+ /*!
+ * Playback/Capture substream
+ */
+ struct mxc_spdif_stream s[2];
+};
+
+static struct spdif_mixer_control mxc_spdif_control;
+
+static unsigned long spdif_base_addr;
+
+/* define each spdif interrupt handlers */
+typedef void (*spdif_irq_func_t) (unsigned int bit, void *desc);
+
+/* spdif irq functions declare */
+static void spdif_irq_fifo(unsigned int bit, void *devid);
+static void spdif_irq_dpll_lock(unsigned int bit, void *devid);
+static void spdif_irq_uq(unsigned int bit, void *devid);
+static void spdif_irq_bit_error(unsigned int bit, void *devid);
+static void spdif_irq_sym_error(unsigned int bit, void *devid);
+static void spdif_irq_valnogood(unsigned int bit, void *devid);
+static void spdif_irq_cnew(unsigned int bit, void *devid);
+
+/* irq function list */
+static spdif_irq_func_t spdif_irq_handlers[] = {
+ spdif_irq_fifo,
+ spdif_irq_fifo,
+ spdif_irq_dpll_lock,
+ NULL,
+ spdif_irq_fifo,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ spdif_irq_uq,
+ NULL,
+ NULL,
+ NULL,
+ spdif_irq_bit_error,
+ spdif_irq_sym_error,
+ spdif_irq_valnogood,
+ spdif_irq_cnew,
+ NULL,
+ spdif_irq_fifo,
+ spdif_irq_dpll_lock,
+};
+
+/*!
+ * @brief Enable/Disable spdif DMA request
+ *
+ * This function is called to enable or disable the dma transfer
+ */
+static void spdif_dma_enable(int txrx, int enable)
+{
+ unsigned long value;
+
+ value = __raw_readl(SPDIF_REG_SCR + spdif_base_addr) & 0xfffeff;
+ if (enable)
+ value |= txrx;
+
+ __raw_writel(value, SPDIF_REG_SCR + spdif_base_addr);
+
+}
+
+/*!
+ * @brief Enable spdif interrupt
+ *
+ * This function is called to enable relevant interrupts.
+ */
+static void spdif_intr_enable(unsigned long intr, int enable)
+{
+ unsigned long value;
+
+ value = __raw_readl(spdif_base_addr + SPDIF_REG_SIE) & 0xffffff;
+ if (enable)
+ value |= intr;
+ else
+ value &= ~intr;
+
+ __raw_writel(value, spdif_base_addr + SPDIF_REG_SIE);
+}
+
+/*!
+ * @brief Set the clock accuracy level in the channel bit
+ *
+ * This function is called to set the clock accuracy level
+ */
+static int spdif_set_clk_accuracy(enum spdif_clk_accuracy level)
+{
+ unsigned long value;
+
+ value = __raw_readl(SPDIF_REG_STCSCL + spdif_base_addr) & 0xffffcf;
+ value |= (level << 4);
+ __raw_writel(value, SPDIF_REG_STCSCL + spdif_base_addr);
+
+ return 0;
+}
+
+/*!
+ * set SPDIF PhaseConfig register for rx clock
+ */
+static int spdif_set_rx_clksrc(enum spdif_clk_src clksrc,
+ enum spdif_gainsel gainsel, int dpll_locked)
+{
+ unsigned long value;
+ if (clksrc > SPDIF_CLK_SRC5 || gainsel > GAINSEL_MULTI_3)
+ return 1;
+
+ value = (dpll_locked ? (clksrc) :
+ (clksrc + SRPC_CLKSRC_SEL_LOCKED)) << SRPC_CLKSRC_SEL_OFFSET |
+ (gainsel << SRPC_GAINSEL_OFFSET);
+ __raw_writel(value, spdif_base_addr + SPDIF_REG_SRPC);
+
+ return 0;
+}
+
+/*!
+ * Get RX data clock rate
+ * given the SPDIF bus_clk
+ */
+static int spdif_get_rxclk_rate(struct clk *bus_clk, enum spdif_gainsel gainsel)
+{
+ unsigned long freqmeas, phaseconf, busclk_freq = 0;
+ u64 tmpval64;
+ enum spdif_clk_src clksrc;
+
+ freqmeas = __raw_readl(spdif_base_addr + SPDIF_REG_SRFM);
+ phaseconf = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC);
+
+ clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0x0F;
+ if (clksrc < 5 && (phaseconf & SRPC_DPLL_LOCKED)) {
+ /* get bus clock from system */
+ busclk_freq = clk_get_rate(bus_clk);
+ }
+
+ /* FreqMeas_CLK = (BUS_CLK*FreqMeas[23:0])/2^10/GAINSEL/128 */
+ tmpval64 = (u64) busclk_freq * freqmeas;
+ do_div(tmpval64, gainsel_multi[gainsel]);
+ do_div(tmpval64, 128 * 1024);
+ return (int)tmpval64;
+}
+
+/*!
+ * @brief Set the audio sample rate in the channel status bit
+ *
+ * This function is called to set the audio sample rate to be transfered.
+ */
+static int spdif_set_sample_rate(int src_44100, int src_48000, int sample_rate)
+{
+ unsigned long cstatus, stc;
+
+ cstatus = __raw_readl(SPDIF_REG_STCSCL + spdif_base_addr) & 0xfffff0;
+ stc = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF;
+
+ switch (sample_rate) {
+ case 44100:
+ __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr);
+ stc |= (src_44100 << 8) | 0x07;
+ __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr);
+ pr_debug("set sample rate to 44100\n");
+ break;
+ case 48000:
+ cstatus |= 0x04;
+ __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr);
+ stc |= (src_48000 << 8) | 0x07;
+ __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr);
+ pr_debug("set sample rate to 48000\n");
+ break;
+ case 32000:
+ cstatus |= 0x0c;
+ __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr);
+ stc |= (src_48000 << 8) | 0x0b;
+ __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr);
+ pr_debug("set sample rate to 32000\n");
+ break;
+ }
+
+ return 0;
+}
+
+/*!
+ * @brief Set the lchannel status bit
+ *
+ * This function is called to set the channel status
+ */
+static int spdif_set_channel_status(int value, unsigned long reg)
+{
+ __raw_writel(value & 0xffffff, reg + spdif_base_addr);
+
+ return 0;
+}
+
+/*!
+ * @brief Get spdif interrupt status and clear the interrupt
+ *
+ * This function is called to check relevant interrupt status
+ */
+static int spdif_intr_status(void)
+{
+ unsigned long value;
+
+ value = __raw_readl(SPDIF_REG_SIS + spdif_base_addr) & 0xffffff;
+ value &= __raw_readl(spdif_base_addr + SPDIF_REG_SIE);
+ __raw_writel(value, SPDIF_REG_SIC + spdif_base_addr);
+
+ return value;
+}
+
+/*!
+ * @brief spdif interrupt handler
+ */
+static irqreturn_t spdif_isr(int irq, void *dev_id)
+{
+ unsigned long int_stat;
+ int line;
+
+ int_stat = spdif_intr_status();
+
+ while ((line = ffs(int_stat)) != 0) {
+ int_stat &= ~(1UL << (line - 1));
+ if (spdif_irq_handlers[line - 1] != NULL)
+ spdif_irq_handlers[line - 1] (line - 1, dev_id);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*!
+ * Interrupt handlers
+ *
+ */
+/*!
+ * FIFO related interrupts handler
+ *
+ * Rx FIFO Full, Underrun/Overrun interrupts
+ * Tx FIFO Empty, Underrun/Overrun interrupts
+ */
+static void spdif_irq_fifo(unsigned int bit, void *devid)
+{
+
+}
+
+/*!
+ * DPLL lock related interrupts handler
+ *
+ * DPLL locked and lock loss interrupts
+ */
+static void spdif_irq_dpll_lock(unsigned int bit, void *devid)
+{
+ struct mxc_spdif_device *chip = (struct mxc_spdif_device *)devid;
+ unsigned int locked = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC);
+
+ if (locked & SRPC_DPLL_LOCKED) {
+ pr_debug("SPDIF Rx dpll locked\n");
+ atomic_set(&chip->dpll_locked, 1);
+ } else {
+ /* INT_LOSS_LOCK */
+ pr_debug("SPDIF Rx dpll loss lock\n");
+ atomic_set(&chip->dpll_locked, 0);
+ }
+}
+
+/*!
+ * U/Q channel related interrupts handler
+ *
+ * U/QChannel full, overrun interrupts
+ * U/QChannel sync error and frame error interrupts
+ */
+static void spdif_irq_uq(unsigned int bit, void *devid)
+{
+ unsigned long val;
+ int index;
+ struct spdif_mixer_control *ctrl = &mxc_spdif_control;
+
+ bit = 1 << bit;
+ /* get U/Q channel datas */
+ switch (bit) {
+
+ case INT_URX_OV: /* read U data */
+ pr_debug("User bit receive overrun\n");
+ case INT_URX_FUL:
+ pr_debug("U bit receive full\n");
+
+ if (ctrl->upos >= SPDIF_UBITS_SIZE * 2) {
+ ctrl->upos = 0;
+ } else if (unlikely((ctrl->upos % SPDIF_UBITS_SIZE) + 3
+ > SPDIF_UBITS_SIZE)) {
+ pr_err("User bit receivce buffer overflow\n");
+ break;
+ }
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU);
+ ctrl->subcode[ctrl->upos++] = val >> 16;
+ ctrl->subcode[ctrl->upos++] = val >> 8;
+ ctrl->subcode[ctrl->upos++] = val;
+
+ break;
+
+ case INT_QRX_OV: /* read Q data */
+ pr_debug("Q bit receive overrun\n");
+ case INT_QRX_FUL:
+ pr_debug("Q bit receive full\n");
+
+ if (ctrl->qpos >= SPDIF_QSUB_SIZE * 2) {
+ ctrl->qpos = 0;
+ } else if (unlikely((ctrl->qpos % SPDIF_QSUB_SIZE) + 3
+ > SPDIF_QSUB_SIZE)) {
+ pr_err("Q bit receivce buffer overflow\n");
+ break;
+ }
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ);
+ ctrl->qsub[ctrl->qpos++] = val >> 16;
+ ctrl->qsub[ctrl->qpos++] = val >> 8;
+ ctrl->qsub[ctrl->qpos++] = val;
+
+ break;
+
+ case INT_UQ_ERR: /* read U/Q data and do buffer reset */
+ pr_debug("U/Q bit receive error\n");
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU);
+ val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ);
+ /* drop this U/Q buffer */
+ ctrl->ready_buf = ctrl->upos = ctrl->qpos = 0;
+ break;
+
+ case INT_UQ_SYNC: /* U/Q buffer reset */
+ pr_debug("U/Q sync receive\n");
+
+ if (ctrl->qpos == 0)
+ break;
+ index = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE;
+ /* set ready to this buffer */
+ ctrl->ready_buf = index + 1;
+ break;
+ }
+}
+
+/*!
+ * SPDIF receiver found parity bit error interrupt handler
+ */
+static void spdif_irq_bit_error(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt parity bit error\n");
+}
+
+/*!
+ * SPDIF receiver found illegal symbol interrupt handler
+ */
+static void spdif_irq_sym_error(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt symbol error\n");
+}
+
+/*!
+ * SPDIF validity flag no good interrupt handler
+ */
+static void spdif_irq_valnogood(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt validate is not good\n");
+}
+
+/*!
+ * SPDIF receive change in value of control channel
+ */
+static void spdif_irq_cnew(unsigned int bit, void *devid)
+{
+ pr_debug("SPDIF interrupt cstatus new\n");
+}
+
+/*!
+ * Do software reset to SPDIF
+ */
+static void spdif_softreset(void)
+{
+ unsigned long value = 1;
+ int cycle = 0;
+ __raw_writel(SCR_SOFT_RESET, spdif_base_addr + SPDIF_REG_SCR);
+ while (value && (cycle++ < 10)) {
+ value = __raw_readl(spdif_base_addr + SPDIF_REG_SCR) & 0x1000;
+ }
+
+}
+
+/*!
+ * SPDIF RX initial function
+ */
+static void spdif_rx_init(void)
+{
+ unsigned long regval;
+
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+ /**
+ * initial and reset SPDIF configuration:
+ * RxFIFO off
+ * RxFIFO sel to 8 sample
+ * Autosync
+ * Valid bit set
+ */
+ regval &= ~(SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO | SCR_LOW_POWER);
+ regval |= (2 << SCR_RXFIFO_FSEL_BIT) | SCR_RXFIFO_AUTOSYNC;
+ __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR);
+}
+
+/*!
+ * SPDIF RX un-initial function
+ */
+static void spdif_rx_uninit(void)
+{
+ unsigned long regval;
+
+ /* turn off RX fifo, disable dma and autosync */
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+ regval |= SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO;
+ regval &= ~(SCR_DMA_RX_EN | SCR_RXFIFO_AUTOSYNC);
+ __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR);
+}
+
+/*!
+ * @brief Initialize spdif module
+ *
+ * This function is called to set the spdif to initial state.
+ */
+static void spdif_tx_init(void)
+{
+ unsigned long regval;
+
+ regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR);
+
+ regval &= 0xfc32e3;
+ regval |= SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_NORMAL |
+ SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | (2 << SCR_TXFIFO_ESEL_BIT);
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+
+ /* Default clock source from EXTAL, divider by 8, generate 44.1kHz
+ sample rate */
+ regval = 0x07;
+ __raw_writel(regval, SPDIF_REG_STC + spdif_base_addr);
+
+}
+
+/*!
+ * @brief deinitialize spdif module
+ *
+ * This function is called to stop the spdif
+ */
+static void spdif_tx_uninit(void)
+{
+ unsigned long regval;
+
+ regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr) & 0xffffe3;
+ regval |= SCR_TXSEL_OFF;
+ __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr);
+ regval = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF;
+ regval |= (0x7 << STC_TXCLK_SRC_OFFSET);
+ __raw_writel(regval, SPDIF_REG_STC + spdif_base_addr);
+
+}
+
+static unsigned int spdif_playback_rates[] = { 32000, 44100, 48000 };
+static unsigned int spdif_capture_rates[] = {
+ 16000, 32000, 44100, 48000, 64000, 96000
+};
+
+/*!
+ * this structure represents the sample rates supported
+ * by SPDIF
+ */
+static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = {
+ .count = ARRAY_SIZE(spdif_playback_rates),
+ .list = spdif_playback_rates,
+ .mask = 0,
+};
+
+static struct snd_pcm_hw_constraint_list hw_capture_rates_stereo = {
+ .count = ARRAY_SIZE(spdif_capture_rates),
+ .list = spdif_capture_rates,
+ .mask = 0,
+};
+
+/*!
+ * This structure reprensents the capabilities of the driver
+ * in playback mode.
+ */
+static struct snd_pcm_hardware snd_spdif_playback_hw = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_HALF_DUPLEX |
+ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rates =
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = SPDIF_MAX_BUF_SIZE,
+ .period_bytes_min = SPDIF_MIN_PERIOD_SIZE,
+ .period_bytes_max = SPDIF_DMA_BUF_SIZE,
+ .periods_min = SPDIF_MIN_PERIOD,
+ .periods_max = SPDIF_MAX_PERIOD,
+ .fifo_size = 0,
+};
+
+/*!
+ * This structure reprensents the capabilities of the driver
+ * in capture mode.
+ */
+static struct snd_pcm_hardware snd_spdif_capture_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ .rates =
+ (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100
+ | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 |
+ SNDRV_PCM_RATE_96000),
+ .rate_min = 16000,
+ .rate_max = 96000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = SPDIF_MAX_BUF_SIZE,
+ .period_bytes_min = SPDIF_MIN_PERIOD_SIZE,
+ .period_bytes_max = SPDIF_DMA_BUF_SIZE,
+ .periods_min = SPDIF_MIN_PERIOD,
+ .periods_max = SPDIF_MAX_PERIOD,
+ .fifo_size = 0,
+
+};
+
+/*!
+ * This function configures the DMA channel used to transfer
+ * audio from MCU to SPDIF or from SPDIF to MCU
+ *
+ * @param s pointer to the structure of the current stream.
+ * @param callback pointer to function that will be
+ * called when a SDMA TX transfer finishes.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+spdif_configure_dma_channel(struct mxc_spdif_stream *s,
+ mxc_dma_callback_t callback)
+{
+ int ret = -1;
+ int channel = -1;
+
+ if (s->dma_wchannel != 0)
+ mxc_dma_free(s->dma_wchannel);
+
+ if (s->stream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+ if (s->stream->runtime->sample_bits > 16) {
+ channel =
+ mxc_dma_request(MXC_DMA_SPDIF_32BIT_TX,
+ "SPDIF TX DMA");
+ } else {
+ channel =
+ mxc_dma_request(MXC_DMA_SPDIF_16BIT_TX,
+ "SPDIF TX DMA");
+ }
+
+ } else if (s->stream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+
+ channel = mxc_dma_request(MXC_DMA_SPDIF_32BIT_RX,
+ "SPDIF RX DMA");
+
+ }
+
+ pr_debug("spdif_configure_dma_channel: %d\n", channel);
+
+ ret = mxc_dma_callback_set(channel,
+ (mxc_dma_callback_t) callback, (void *)s);
+ if (ret != 0) {
+ pr_info("spdif_configure_dma_channel - err\n");
+ mxc_dma_free(channel);
+ return -1;
+ }
+ s->dma_wchannel = channel;
+ return 0;
+}
+
+/*!
+ * This function gets the dma pointer position during playback/capture.
+ * Our DMA implementation does not allow to retrieve this position
+ * when a transfert is active, so, it answers the middle of
+ * the current period beeing transfered
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static u_int spdif_get_dma_pos(struct mxc_spdif_stream *s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int offset = 0;
+ substream = s->stream;
+ runtime = substream->runtime;
+
+ offset = (runtime->period_size * (s->periods));
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ pr_debug
+ ("MXC: spdif_get_dma_pos BIS offset %d, buffer_size %d\n",
+ offset, (int)runtime->buffer_size);
+ return offset;
+}
+
+/*!
+ * This function stops the current dma transfert for playback
+ * and clears the dma pointers.
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static void spdif_stop_tx(struct mxc_spdif_stream *s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int offset;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * s->periods;
+
+ s->active = 0;
+ s->period = 0;
+ s->periods = 0;
+
+ /* this stops the dma channel and clears the buffer ptrs */
+ mxc_dma_disable(s->dma_wchannel);
+ spdif_dma_enable(SCR_DMA_TX_EN, 0);
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_TO_DEVICE);
+}
+
+/*!
+ * This function is called whenever a new audio block needs to be
+ * transferred to SPDIF. The function receives the address and the size
+ * of the new block and start a new DMA transfer.
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static void spdif_start_tx(struct mxc_spdif_stream *s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size = 0;
+ unsigned int offset;
+ int ret = 0;
+ mxc_dma_requestbuf_t dma_request;
+ substream = s->stream;
+ runtime = substream->runtime;
+ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));
+ if (s->active) {
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * s->period;
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset, dma_size,
+ DMA_TO_DEVICE));
+
+ dma_request.dst_addr = (dma_addr_t) (SPDIF_BASE_ADDR + 0x2c);
+
+ dma_request.num_of_bytes = dma_size;
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ spdif_dma_enable(SCR_DMA_TX_EN, 1);
+ if (ret) {
+ pr_info("audio_process_dma: cannot queue DMA \
+ buffer\n");
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+
+ if ((s->period > s->periods)
+ && ((s->period - s->periods) > 1)) {
+ pr_debug("audio playback chain dma: already double \
+ buffered\n");
+ return;
+ }
+
+ if ((s->period < s->periods)
+ && ((s->period + runtime->periods - s->periods) > 1)) {
+ pr_debug("audio playback chain dma: already double \
+ buffered\n");
+ return;
+ }
+
+ if (s->period == s->periods) {
+ pr_debug("audio playback chain dma: s->period == \
+ s->periods\n");
+ return;
+ }
+
+ if (snd_pcm_playback_hw_avail(runtime) <
+ 2 * runtime->period_size) {
+ pr_debug("audio playback chain dma: available data \
+ is not enough\n");
+ return;
+ }
+
+ pr_debug
+ ("audio playback chain dma:to set up the 2nd dma buffer\n");
+ pr_debug("SCR: 0x%08x\n",
+ __raw_readl(spdif_base_addr + SPDIF_REG_SCR));
+
+ offset = dma_size * s->period;
+ dma_request.src_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset, dma_size,
+ DMA_TO_DEVICE));
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_WRITE);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ s->period++;
+ s->period %= runtime->periods;
+
+ }
+ return;
+}
+
+/*!
+ * This is a callback which will be called
+ * when a TX transfer finishes. The call occurs
+ * in interrupt context.
+ *
+ * @param data pointer to the structure of the current stream
+ * @param error DMA error flag
+ * @param count number of bytes transfered by the DMA
+ */
+static void spdif_tx_callback(void *data, int error, unsigned int count)
+{
+ struct mxc_spdif_stream *s;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int previous_period;
+ unsigned int offset;
+ s = data;
+ substream = s->stream;
+ runtime = substream->runtime;
+ previous_period = s->periods;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * previous_period;
+
+ spin_lock(&s->dma_lock);
+ s->periods++;
+ s->periods %= runtime->periods;
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_TO_DEVICE);
+ spin_unlock(&s->dma_lock);
+
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+
+ spin_lock(&s->dma_lock);
+ spdif_start_tx(s);
+ spin_unlock(&s->dma_lock);
+}
+
+/*!
+ * This function is a dispatcher of command to be executed
+ * by the driver for playback.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param cmd command to be executed
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+snd_mxc_spdif_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct mxc_spdif_device *chip;
+ struct mxc_spdif_stream *s;
+ int err = 0;
+ unsigned long flags;
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[SNDRV_PCM_STREAM_PLAYBACK];
+
+ spin_lock_irqsave(&s->dma_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ s->active = 1;
+ spdif_start_tx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ spdif_stop_tx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ s->active = 0;
+ s->periods = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ s->active = 1;
+ spdif_start_tx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ s->active = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ s->active = 1;
+ spdif_start_tx(s);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&s->dma_lock, flags);
+ return err;
+}
+
+/*!
+ * This function configures the hardware to allow audio
+ * playback operations. It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_spdif_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct snd_pcm_runtime *runtime;
+ int err;
+ unsigned int ch_status;
+
+ chip = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+
+ spdif_tx_init();
+
+ ch_status =
+ ((mxc_spdif_control.ch_status[2] << 16) | (mxc_spdif_control.
+ ch_status[1] << 8) |
+ mxc_spdif_control.ch_status[0]);
+ spdif_set_channel_status(ch_status, SPDIF_REG_STCSCH);
+ ch_status = mxc_spdif_control.ch_status[3];
+ spdif_set_channel_status(ch_status, SPDIF_REG_STCSCL);
+ spdif_intr_enable(INT_TXFIFO_RESYNC, 1);
+ spdif_set_sample_rate(chip->spdif_txclk_44100, chip->spdif_txclk_48000,
+ runtime->rate);
+ spdif_set_clk_accuracy(SPDIF_CLK_ACCURACY_LEV2);
+ /* setup DMA controller for spdif tx */
+ err = spdif_configure_dma_channel(&chip->
+ s[SNDRV_PCM_STREAM_PLAYBACK],
+ spdif_tx_callback);
+ if (err < 0) {
+ pr_info("snd_mxc_spdif_playback_prepare - err < 0\n");
+ return err;
+ }
+
+ /**
+ * FIXME: dump registers
+ */
+ pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR));
+ pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE));
+ pr_debug("STC: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_STC));
+ return 0;
+}
+
+/*!
+ * This function gets the current playback pointer position.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static snd_pcm_uframes_t
+snd_mxc_spdif_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ chip = snd_pcm_substream_chip(substream);
+ return spdif_get_dma_pos(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]);
+}
+
+static int snd_card_mxc_spdif_playback_open(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct snd_pcm_runtime *runtime;
+ int err;
+ struct mxc_spdif_platform_data *spdif_data;
+
+ chip = snd_pcm_substream_chip(substream);
+
+ spdif_data = chip->card->dev->platform_data;
+ /* enable tx clock */
+ clk_enable(spdif_data->spdif_clk);
+ clk_enable(spdif_data->spdif_audio_clk);
+
+ runtime = substream->runtime;
+ chip->s[SNDRV_PCM_STREAM_PLAYBACK].stream = substream;
+ runtime->hw = snd_spdif_playback_hw;
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_playback_rates_stereo);
+ if (err < 0)
+ goto failed;
+ err =
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto failed;
+
+ return 0;
+ failed:
+ clk_disable(spdif_data->spdif_clk);
+ return err;
+}
+
+/*!
+ * This function closes an spdif device for playback.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_spdif_playback_close(struct snd_pcm_substream
+ *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct mxc_spdif_platform_data *spdif_data;
+
+ chip = snd_pcm_substream_chip(substream);
+ spdif_data = chip->card->dev->platform_data;
+
+ pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS));
+
+ spdif_intr_status();
+ spdif_intr_enable(INT_TXFIFO_RESYNC, 0);
+ spdif_tx_uninit();
+ clk_disable(spdif_data->spdif_audio_clk);
+ clk_disable(spdif_data->spdif_clk);
+ mxc_dma_free(chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel);
+ chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel = 0;
+
+ return 0;
+}
+
+/*! TODO: update the dma start/stop callback routine
+ * This function stops the current dma transfert for capture
+ * and clears the dma pointers.
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static void spdif_stop_rx(struct mxc_spdif_stream *s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int offset;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * s->periods;
+
+ s->active = 0;
+ s->period = 0;
+ s->periods = 0;
+
+ /* this stops the dma channel and clears the buffer ptrs */
+ mxc_dma_disable(s->dma_wchannel);
+ spdif_dma_enable(SCR_DMA_RX_EN, 0);
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_FROM_DEVICE);
+}
+
+/*!
+ * This function is called whenever a new audio block needs to be
+ * received from SPDIF. The function receives the address and the size
+ * of the new block and start a new DMA transfer.
+ *
+ * @param s pointer to the structure of the current stream.
+ *
+ */
+static void spdif_start_rx(struct mxc_spdif_stream *s)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size = 0;
+ unsigned int offset;
+ int ret = 0;
+ mxc_dma_requestbuf_t dma_request;
+
+ substream = s->stream;
+ runtime = substream->runtime;
+ memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t));
+
+ if (s->active) {
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ pr_debug("s->period (%x) runtime->periods (%d)\n",
+ s->period, runtime->periods);
+ pr_debug("runtime->period_size (%d) dma_size (%d)\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes);
+
+ offset = dma_size * s->period;
+ dma_request.dst_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset, dma_size,
+ DMA_FROM_DEVICE));
+
+ dma_request.src_addr =
+ (dma_addr_t) (SPDIF_BASE_ADDR + SPDIF_REG_SRL);
+ dma_request.num_of_bytes = dma_size;
+ /* config and enable sdma for RX */
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_READ);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ /* enable SPDIF dma */
+ spdif_dma_enable(SCR_DMA_RX_EN, 1);
+
+ if (ret) {
+ pr_info("audio_process_dma: cannot queue DMA \
+ buffer\n");
+ return;
+ }
+ s->period++;
+ s->period %= runtime->periods;
+
+ if ((s->period > s->periods)
+ && ((s->period - s->periods) > 1)) {
+ pr_debug("audio capture chain dma: already double \
+ buffered\n");
+ return;
+ }
+
+ if ((s->period < s->periods)
+ && ((s->period + runtime->periods - s->periods) > 1)) {
+ pr_debug("audio capture chain dma: already double \
+ buffered\n");
+ return;
+ }
+
+ if (s->period == s->periods) {
+ pr_debug("audio capture chain dma: s->period == \
+ s->periods\n");
+ return;
+ }
+
+ if (snd_pcm_capture_hw_avail(runtime) <
+ 2 * runtime->period_size) {
+ pr_debug("audio capture chain dma: available data \
+ is not enough\n");
+ return;
+ }
+
+ pr_debug
+ ("audio playback chain dma:to set up the 2nd dma buffer\n");
+
+ offset = dma_size * s->period;
+ dma_request.dst_addr =
+ (dma_addr_t) (dma_map_single
+ (NULL, runtime->dma_area + offset, dma_size,
+ DMA_FROM_DEVICE));
+ mxc_dma_config(s->dma_wchannel, &dma_request, 1,
+ MXC_DMA_MODE_READ);
+ ret = mxc_dma_enable(s->dma_wchannel);
+ s->period++;
+ s->period %= runtime->periods;
+
+ }
+ return;
+}
+
+/*!
+ * This is a callback which will be called
+ * when a RX transfer finishes. The call occurs
+ * in interrupt context.
+ *
+ * @param data pointer to the structure of the current stream
+ * @param error DMA error flag
+ * @param count number of bytes transfered by the DMA
+ */
+static void spdif_rx_callback(void *data, int error, unsigned int count)
+{
+ struct mxc_spdif_stream *s;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned int dma_size;
+ unsigned int previous_period;
+ unsigned int offset;
+
+ s = data;
+ substream = s->stream;
+ runtime = substream->runtime;
+ previous_period = s->periods;
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * previous_period;
+
+ spin_lock(&s->dma_lock);
+ s->periods++;
+ s->periods %= runtime->periods;
+
+ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size,
+ DMA_FROM_DEVICE);
+ spin_unlock(&s->dma_lock);
+
+ if (s->active)
+ snd_pcm_period_elapsed(s->stream);
+ spin_lock(&s->dma_lock);
+ spdif_start_rx(s);
+ spin_unlock(&s->dma_lock);
+}
+
+/*!
+ * This function is a dispatcher of command to be executed
+ * by the driver for capture.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param cmd command to be executed
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+snd_mxc_spdif_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct mxc_spdif_device *chip;
+ struct mxc_spdif_stream *s;
+ int err = 0;
+ unsigned long flags;
+ chip = snd_pcm_substream_chip(substream);
+ s = &chip->s[SNDRV_PCM_STREAM_CAPTURE];
+
+ spin_lock_irqsave(&s->dma_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ s->active = 1;
+ spdif_start_rx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ spdif_stop_rx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ s->active = 0;
+ s->periods = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ s->active = 1;
+ spdif_start_rx(s);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ s->active = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ s->active = 1;
+ spdif_start_rx(s);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&s->dma_lock, flags);
+ return err;
+}
+
+/*!
+ * This function configures the hardware to allow audio
+ * capture operations. It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_spdif_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct mxc_spdif_platform_data *spdif_data;
+ struct snd_pcm_runtime *runtime;
+ int err;
+
+ chip = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+ spdif_data = chip->card->dev->platform_data;
+
+ spdif_rx_init();
+ /* enable interrupts, include DPLL lock */
+ spdif_intr_enable(INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |
+ INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |
+ INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC |
+ INT_LOSS_LOCK, 1);
+
+ /* setup rx clock source */
+ spdif_set_rx_clksrc(spdif_data->spdif_clkid, SPDIF_DEFAULT_GAINSEL, 1);
+
+ /* setup DMA controller for spdif rx */
+ err = spdif_configure_dma_channel(&chip->
+ s[SNDRV_PCM_STREAM_CAPTURE],
+ spdif_rx_callback);
+ if (err < 0) {
+ pr_info("snd_mxc_spdif_playback_prepare - err < 0\n");
+ return err;
+ }
+
+ /* Debug: dump registers */
+ pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR));
+ pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE));
+ pr_debug("SRPC: 0x%08x\n",
+ __raw_readl(spdif_base_addr + SPDIF_REG_SRPC));
+ pr_debug("FreqMeas: 0x%08x\n",
+ __raw_readl(spdif_base_addr + SPDIF_REG_SRFM));
+
+ return 0;
+}
+
+/*!
+ * This function gets the current capture pointer position.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ */
+static snd_pcm_uframes_t
+snd_mxc_spdif_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ chip = snd_pcm_substream_chip(substream);
+ return spdif_get_dma_pos(&chip->s[SNDRV_PCM_STREAM_CAPTURE]);
+}
+
+/*!
+ * This function opens a spdif device in capture mode
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_spdif_capture_open(struct snd_pcm_substream *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct snd_pcm_runtime *runtime;
+ int err = 0;
+ struct mxc_spdif_platform_data *spdif_data;
+
+ chip = snd_pcm_substream_chip(substream);
+
+ spdif_data = chip->card->dev->platform_data;
+ /* enable rx bus clock */
+ clk_enable(spdif_data->spdif_clk);
+
+ runtime = substream->runtime;
+ chip->s[SNDRV_PCM_STREAM_CAPTURE].stream = substream;
+ runtime->hw = snd_spdif_capture_hw;
+
+ /* set hw param constraints */
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_capture_rates_stereo);
+ if (err < 0)
+ goto failed;
+ err =
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0)
+ goto failed;
+
+ /* enable spdif dpll lock interrupt */
+ spdif_intr_enable(INT_DPLL_LOCKED, 1);
+
+ return 0;
+
+ failed:
+ clk_disable(spdif_data->spdif_clk);
+ return err;
+}
+
+/*!
+ * This function closes an spdif device for capture.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_spdif_capture_close(struct snd_pcm_substream
+ *substream)
+{
+ struct mxc_spdif_device *chip;
+ struct mxc_spdif_platform_data *spdif_data;
+
+ chip = snd_pcm_substream_chip(substream);
+ spdif_data = chip->card->dev->platform_data;
+
+ pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS));
+ pr_debug("SRPC: 0x%08x\n",
+ __raw_readl(spdif_base_addr + SPDIF_REG_SRPC));
+ pr_debug("FreqMeas: 0x%08x\n",
+ __raw_readl(spdif_base_addr + SPDIF_REG_SRFM));
+
+ spdif_intr_enable(INT_DPLL_LOCKED | INT_SYM_ERR | INT_BIT_ERR |
+ INT_URX_FUL | INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |
+ INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC |
+ INT_LOSS_LOCK, 0);
+ spdif_rx_uninit();
+ clk_disable(spdif_data->spdif_clk);
+ mxc_dma_free(chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel);
+ chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel = 0;
+ return 0;
+}
+
+/*!
+ * This function configure the Audio HW in terms of memory allocation.
+ * It is called by ALSA framework.
+ *
+ * @param substream pointer to the structure of the current stream.
+ * @param hw_params Pointer to hardware paramters structure
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_spdif_hw_params(struct snd_pcm_substream
+ *substream, struct snd_pcm_hw_params
+ *hw_params)
+{
+ struct snd_pcm_runtime *runtime;
+ int ret = 0;
+ runtime = substream->runtime;
+ ret =
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+ if (ret < 0) {
+ pr_info("snd_mxc_spdif_hw_params - ret: %d\n", ret);
+ return ret;
+ }
+ runtime->dma_addr = virt_to_phys(runtime->dma_area);
+ return ret;
+}
+
+/*!
+ * This function frees the spdif hardware at the end of playback.
+ *
+ * @param substream pointer to the structure of the current stream.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_mxc_spdif_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/*!
+ * This structure is the list of operation that the driver
+ * must provide for the playback interface
+ */
+static struct snd_pcm_ops snd_card_mxc_spdif_playback_ops = {
+ .open = snd_card_mxc_spdif_playback_open,
+ .close = snd_card_mxc_spdif_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mxc_spdif_hw_params,
+ .hw_free = snd_mxc_spdif_hw_free,
+ .prepare = snd_mxc_spdif_playback_prepare,
+ .trigger = snd_mxc_spdif_playback_trigger,
+ .pointer = snd_mxc_spdif_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_card_mxc_spdif_capture_ops = {
+ .open = snd_card_mxc_spdif_capture_open,
+ .close = snd_card_mxc_spdif_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_mxc_spdif_hw_params,
+ .hw_free = snd_mxc_spdif_hw_free,
+ .prepare = snd_mxc_spdif_capture_prepare,
+ .trigger = snd_mxc_spdif_capture_trigger,
+ .pointer = snd_mxc_spdif_capture_pointer,
+};
+
+/*!
+ * This functions initializes the playback audio device supported by
+ * spdif
+ *
+ * @param mxc_spdif pointer to the sound card structure.
+ *
+ */
+void mxc_init_spdif_device(struct mxc_spdif_device *mxc_spdif)
+{
+
+ /* initial spinlock for control data */
+ spin_lock_init(&mxc_spdif_control.ctl_lock);
+
+ if (mxc_spdif->mxc_spdif_tx) {
+
+ mxc_spdif->s[SNDRV_PCM_STREAM_PLAYBACK].id = "spdif tx";
+ /* init tx channel status default value */
+ mxc_spdif_control.ch_status[0] =
+ IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS_5015;
+ mxc_spdif_control.ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID;
+ mxc_spdif_control.ch_status[2] = 0x00;
+ mxc_spdif_control.ch_status[3] =
+ IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM;
+ }
+ if (mxc_spdif->mxc_spdif_rx) {
+
+ /* TODO: Add code here if capture is available */
+ mxc_spdif->s[SNDRV_PCM_STREAM_CAPTURE].id = "spdif rx";
+ }
+
+}
+
+/*!
+ * MXC SPDIF IEC958 controller(mixer) functions
+ *
+ * Channel status get/put control
+ * User bit value get/put control
+ * Valid bit value get control
+ * DPLL lock status get control
+ * User bit sync mode selection control
+ *
+ */
+static int mxc_pb_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int mxc_pb_spdif_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.iec958.status[0] = mxc_spdif_control.ch_status[0];
+ uvalue->value.iec958.status[1] = mxc_spdif_control.ch_status[1];
+ uvalue->value.iec958.status[2] = mxc_spdif_control.ch_status[2];
+ uvalue->value.iec958.status[3] = mxc_spdif_control.ch_status[3];
+ return 0;
+}
+
+static int mxc_pb_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ unsigned int ch_status;
+ mxc_spdif_control.ch_status[0] = uvalue->value.iec958.status[0];
+ mxc_spdif_control.ch_status[1] = uvalue->value.iec958.status[1];
+ mxc_spdif_control.ch_status[2] = uvalue->value.iec958.status[2];
+ mxc_spdif_control.ch_status[3] = uvalue->value.iec958.status[3];
+ ch_status =
+ ((mxc_spdif_control.ch_status[2] << 16) | (mxc_spdif_control.
+ ch_status[1] << 8) |
+ mxc_spdif_control.ch_status[0]);
+ spdif_set_channel_status(ch_status, SPDIF_REG_STCSCH);
+ ch_status = mxc_spdif_control.ch_status[3];
+ spdif_set_channel_status(ch_status, SPDIF_REG_STCSCL);
+ return 0;
+}
+
+static int snd_mxc_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+/*!
+ * Get channel status from SPDIF_RX_CCHAN register
+ */
+static int snd_mxc_spdif_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int cstatus;
+
+ if (!(__raw_readl(spdif_base_addr + SPDIF_REG_SIS) & INT_CNEW))
+ return -EAGAIN;
+
+ cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLH);
+ ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF;
+ ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF;
+ ucontrol->value.iec958.status[2] = cstatus & 0xFF;
+ cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLL);
+ ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF;
+ ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF;
+ ucontrol->value.iec958.status[5] = cstatus & 0xFF;
+
+ /* clear intr */
+ __raw_writel(INT_CNEW, spdif_base_addr + SPDIF_REG_SIC);
+
+ return 0;
+}
+
+/*!
+ * Get User bits (subcode) from chip value which readed out
+ * in UChannel register.
+ */
+static int snd_mxc_spdif_subcode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags);
+ if (mxc_spdif_control.ready_buf) {
+ memcpy(&ucontrol->value.iec958.subcode[0],
+ &mxc_spdif_control.
+ subcode[(mxc_spdif_control.ready_buf -
+ 1) * SPDIF_UBITS_SIZE], SPDIF_UBITS_SIZE);
+ } else {
+ ret = -EAGAIN;
+ }
+ spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags);
+
+ return ret;
+}
+
+/*!
+ * Q-subcode infomation.
+ * the byte size is SPDIF_UBITS_SIZE/8
+ */
+static int snd_mxc_spdif_qinfo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = SPDIF_QSUB_SIZE;
+ return 0;
+}
+
+/*!
+ * Get Q subcode from chip value which readed out
+ * in QChannel register.
+ */
+static int snd_mxc_spdif_qget(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags);
+ if (mxc_spdif_control.ready_buf) {
+ memcpy(&ucontrol->value.bytes.data[0],
+ &mxc_spdif_control.
+ qsub[(mxc_spdif_control.ready_buf -
+ 1) * SPDIF_QSUB_SIZE], SPDIF_QSUB_SIZE);
+ } else {
+ ret = -EAGAIN;
+ }
+ spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags);
+
+ return ret;
+}
+
+/*!
+ * Valid bit infomation.
+ */
+static int snd_mxc_spdif_vbit_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+/*!
+ * Get valid good bit from interrupt status register.
+ */
+static int snd_mxc_spdif_vbit_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int int_val;
+
+ int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SIS);
+ ucontrol->value.integer.value[0] = (int_val & INT_VAL_NOGOOD) != 0;
+ __raw_writel(INT_VAL_NOGOOD, spdif_base_addr + SPDIF_REG_SIC);
+
+ return 0;
+}
+
+/*!
+ * DPLL lock infomation.
+ */
+static int snd_mxc_spdif_rxrate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 16000;
+ uinfo->value.integer.max = 96000;
+ return 0;
+}
+
+/*!
+ * Get DPLL lock or not info from stable interrupt status register.
+ * User application must use this control to get locked,
+ * then can do next PCM operation
+ */
+static int snd_mxc_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct mxc_spdif_device *chip = snd_kcontrol_chip(kcontrol);
+ struct mxc_spdif_platform_data *spdif_data;
+
+ spdif_data = chip->card->dev->platform_data;
+
+ if (atomic_read(&chip->dpll_locked)) {
+ ucontrol->value.integer.value[0] =
+ spdif_get_rxclk_rate(spdif_data->spdif_clk,
+ SPDIF_DEFAULT_GAINSEL);
+ } else {
+ ucontrol->value.integer.value[0] = 0;
+ }
+ return 0;
+}
+
+/*!
+ * User bit sync mode info
+ */
+static int snd_mxc_spdif_usync_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+/*!
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int snd_mxc_spdif_usync_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int int_val;
+
+ int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SRCD);
+ ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0;
+ return 0;
+}
+
+/*!
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int snd_mxc_spdif_usync_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int int_val;
+
+ int_val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET;
+ __raw_writel(int_val, spdif_base_addr + SPDIF_REG_SRCD);
+ return 0;
+}
+
+/*!
+ * MXC SPDIF IEC958 controller defines
+ */
+static struct snd_kcontrol_new snd_mxc_spdif_ctrls[] = {
+ /* status cchanel controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mxc_pb_spdif_info,
+ .get = mxc_pb_spdif_get,
+ .put = mxc_pb_spdif_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_info,
+ .get = snd_mxc_spdif_capture_get,
+ },
+ /* user bits controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Subcode Capture Default",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_info,
+ .get = snd_mxc_spdif_subcode_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Q-subcode Capture Default",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_qinfo,
+ .get = snd_mxc_spdif_qget,
+ },
+ /* valid bit error controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 V-Bit Errors",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_vbit_info,
+ .get = snd_mxc_spdif_vbit_get,
+ },
+ /* DPLL lock info get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "RX Sample Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_rxrate_info,
+ .get = snd_mxc_spdif_rxrate_get,
+ },
+ /* User bit sync mode set/get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 USyncMode CDText",
+ .access =
+ SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_mxc_spdif_usync_info,
+ .get = snd_mxc_spdif_usync_get,
+ .put = snd_mxc_spdif_usync_put,
+ },
+};
+
+/*!
+ * This function the soundcard structure.
+ *
+ * @param mxc_spdif pointer to the sound card structure.
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int snd_card_mxc_spdif_pcm(struct mxc_spdif_device *mxc_spdif)
+{
+ struct snd_pcm *pcm;
+ int err;
+ err = snd_pcm_new(mxc_spdif->card, MXC_SPDIF_NAME, 0,
+ mxc_spdif->mxc_spdif_tx,
+ mxc_spdif->mxc_spdif_rx, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL),
+ SPDIF_MAX_BUF_SIZE * 2,
+ SPDIF_MAX_BUF_SIZE * 2);
+ if (mxc_spdif->mxc_spdif_tx)
+ snd_pcm_set_ops(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_card_mxc_spdif_playback_ops);
+ if (mxc_spdif->mxc_spdif_rx)
+ snd_pcm_set_ops(pcm,
+ SNDRV_PCM_STREAM_CAPTURE,
+ &snd_card_mxc_spdif_capture_ops);
+ pcm->private_data = mxc_spdif;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, MXC_SPDIF_NAME, sizeof(pcm->name));
+ mxc_spdif->pcm = pcm;
+ mxc_init_spdif_device(mxc_spdif);
+ return 0;
+}
+
+extern void gpio_spdif_active(void);
+
+/*!
+ * This function initializes the driver in terms of memory of the soundcard
+ * and some basic HW clock settings.
+ *
+ * @param pdev Pointer to the platform device
+ * @return 0 on success, -1 otherwise.
+ */
+static int mxc_alsa_spdif_probe(struct platform_device
+ *pdev)
+{
+ int err, idx, irq;
+ static int dev;
+ struct snd_card *card;
+ struct mxc_spdif_device *chip;
+ struct resource *res;
+ struct snd_kcontrol *kctl;
+ struct mxc_spdif_platform_data *plat_data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ /* register the soundcard */
+ err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+ sizeof(struct mxc_spdif_device), &card);
+ if (err < 0)
+ return err;
+ chip = card->private_data;
+ chip->card = card;
+ card->dev = &pdev->dev;
+ chip->reg_base = ioremap(res->start, res->end - res->start + 1);
+ spdif_base_addr = (unsigned long)chip->reg_base;
+ plat_data = (struct mxc_spdif_platform_data *)pdev->dev.platform_data;
+ chip->mxc_spdif_tx = plat_data->spdif_tx;
+ chip->mxc_spdif_rx = plat_data->spdif_rx;
+ chip->spdif_txclk_44100 = plat_data->spdif_clk_44100;
+ chip->spdif_txclk_48000 = plat_data->spdif_clk_48000;
+ atomic_set(&chip->dpll_locked, 0);
+
+ err = snd_card_mxc_spdif_pcm(chip);
+ if (err < 0)
+ goto nodev;
+
+ /*!
+ * Add controls to the card
+ */
+ for (idx = 0; idx < ARRAY_SIZE(snd_mxc_spdif_ctrls); idx++) {
+
+ kctl = snd_ctl_new1(&snd_mxc_spdif_ctrls[idx], chip);
+ if (kctl == NULL) {
+ err = -ENOMEM;
+ goto nodev;
+ }
+ /* check to add control to corresponding substream */
+ if (strstr(kctl->id.name, "Playback"))
+ kctl->id.device = 0;
+ else
+ kctl->id.device = 1;
+
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto nodev;
+ }
+
+ clk_enable(plat_data->spdif_core_clk);
+ /*!
+ * SPDIF interrupt initialization
+ * software reset to SPDIF
+ */
+ spdif_softreset();
+ /* disable all the interrupts */
+ spdif_intr_enable(0xffffff, 0);
+ /* spdif interrupt register and disable */
+ irq = platform_get_irq(pdev, 0);
+ if ((irq <= 0) || request_irq(irq, spdif_isr, 0, "spdif", chip)) {
+ pr_err("MXC spdif: failed to request irq\n");
+ err = -EBUSY;
+ goto nodev;
+ }
+
+ if (chip->mxc_spdif_tx)
+ spin_lock_init(&chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_lock);
+ if (chip->mxc_spdif_rx)
+ spin_lock_init(&chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_lock);
+ strcpy(card->driver, MXC_SPDIF_NAME);
+ strcpy(card->shortname, "MXC SPDIF TX/RX");
+ sprintf(card->longname, "MXC Freescale with SPDIF");
+
+ err = snd_card_register(card);
+ if (err == 0) {
+ pr_info("MXC spdif support initialized\n");
+ platform_set_drvdata(pdev, card);
+ gpio_spdif_active();
+ return 0;
+ }
+
+ nodev:
+ snd_card_free(card);
+ return err;
+}
+
+extern void gpio_spdif_inactive(void);
+
+/*!
+ * This function releases the sound card and unmap the io address
+ *
+ * @param pdev Pointer to the platform device
+ * @return 0 on success, -1 otherwise.
+ */
+
+static int mxc_alsa_spdif_remove(struct platform_device *pdev)
+{
+ struct mxc_spdif_device *chip;
+ struct snd_card *card;
+ struct mxc_spdif_platform_data *plat_data;
+
+ card = platform_get_drvdata(pdev);
+ plat_data = pdev->dev.platform_data;
+ chip = card->private_data;
+ free_irq(platform_get_irq(pdev, 0), chip);
+ iounmap(chip->reg_base);
+
+ snd_card_free(card);
+ platform_set_drvdata(pdev, NULL);
+
+ clk_disable(plat_data->spdif_core_clk);
+ gpio_spdif_inactive();
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*!
+ * This function suspends all active streams.
+ *
+ * TBD
+ *
+ * @param card pointer to the sound card structure.
+ * @param state requested state
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int mxc_alsa_spdif_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+/*!
+ * This function resumes all suspended streams.
+ *
+ * TBD
+ *
+ * @param card pointer to the sound card structure.
+ * @param state requested state
+ *
+ * @return 0 on success, -1 otherwise.
+ */
+static int mxc_alsa_spdif_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#endif
+
+static struct platform_driver mxc_alsa_spdif_driver = {
+ .probe = mxc_alsa_spdif_probe,
+ .remove = mxc_alsa_spdif_remove,
+#ifdef CONFIG_PM
+ .suspend = mxc_alsa_spdif_suspend,
+ .resume = mxc_alsa_spdif_resume,
+#endif
+ .driver = {
+ .name = "mxc_alsa_spdif",
+ },
+};
+
+/*!
+ * This function registers the sound driver structure.
+ *
+ */
+static int __init mxc_alsa_spdif_init(void)
+{
+ return platform_driver_register(&mxc_alsa_spdif_driver);
+}
+
+/*!
+ * This function frees the sound driver structure.
+ *
+ */
+static void __exit mxc_alsa_spdif_exit(void)
+{
+ platform_driver_unregister(&mxc_alsa_spdif_driver);
+}
+
+module_init(mxc_alsa_spdif_init);
+module_exit(mxc_alsa_spdif_exit);
+MODULE_AUTHOR("FREESCALE SEMICONDUCTOR");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MXC ALSA driver for SPDIF");
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index d3e786a9a0a7..ba22486b92a7 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -35,6 +35,9 @@ source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/imx/Kconfig"
+source "sound/soc/stmp3xxx/Kconfig"
+source "sound/soc/mxs/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 6f1e28de23cf..9545c564211d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -13,3 +13,6 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += imx/
+obj-$(CONFIG_SND_SOC) += stmp3xxx/
+obj-$(CONFIG_SND_SOC) += mxs/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index bbc97fd76648..dd28cad09934 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
select SND_SOC_CS4270 if I2C
+ select SND_SOC_CS42888 if I2C
select SND_SOC_PCM3008
select SND_SOC_SPDIF
select SND_SOC_SSM2602 if I2C
@@ -74,6 +75,10 @@ config SND_SOC_AK4104
config SND_SOC_AK4535
tristate
+config SND_SOC_AK5702
+ tristate
+ depends on I2C
+
# Cirrus Logic CS4270 Codec
config SND_SOC_CS4270
tristate
@@ -86,6 +91,9 @@ config SND_SOC_CS4270_VD33_ERRATA
bool
depends on SND_SOC_CS4270
+config SND_SOC_CS42888
+ tristate
+
config SND_SOC_L3
tristate
@@ -176,3 +184,32 @@ config SND_SOC_WM9712
config SND_SOC_WM9713
tristate
+
+config SND_SOC_SGTL5000
+ tristate
+ depends on I2C
+
+config SND_SOC_AK4647
+ tristate
+ depends on I2C
+
+config SND_SOC_STMP378X_CODEC
+ tristate
+ depends on SND_SOC
+
+config SND_SOC_STMP3XXX_SPDIF
+ tristate
+ depends on SND_SOC
+
+config SND_SOC_MXS_ADC_CODEC
+ tristate
+ depends on SND_SOC
+
+config SND_SOC_BLUETOOTH
+ tristate
+ depends on SND_SOC
+
+config SND_SOC_MXS_SPDIF
+ tristate
+ depends on SND_SOC
+
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 8b7530546f4d..8fd4da08392c 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -3,7 +3,9 @@ snd-soc-ad1980-objs := ad1980.o
snd-soc-ad73311-objs := ad73311.o
snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
+snd-soc-ak5702-objs := ak5702.o
snd-soc-cs4270-objs := cs4270.o
+snd-soc-cs42888-objs := cs42888.o
snd-soc-l3-objs := l3.o
snd-soc-pcm3008-objs := pcm3008.o
snd-soc-spdif-objs := spdif_transciever.o
@@ -34,13 +36,22 @@ snd-soc-wm9081-objs := wm9081.o
snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
+snd-soc-sgtl5000-objs := sgtl5000.o
+snd-soc-ak4647-objs := ak4647.o
+snd-soc-stmp378x-codec-objs := stmp378x_codec.o
+snd-soc-stmp3xxx-spdif-objs := stmp3xxx_spdif.o
+snd-soc-bluetooth-objs := bluetooth.o
+snd-soc-mxs-spdif-objs := mxs_spdif.o
+snd-soc-mxs-adc-objs := mxs-adc-codec.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_AK5702) += snd-soc-ak5702.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CS42888) += snd-soc-cs42888.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
@@ -71,3 +82,10 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o
obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
+obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
+obj-$(CONFIG_SND_SOC_AK4647) += snd-soc-ak4647.o
+obj-$(CONFIG_SND_SOC_STMP378X_CODEC) += snd-soc-stmp378x-codec.o
+obj-$(CONFIG_SND_SOC_STMP3XXX_SPDIF) += snd-soc-stmp3xxx-spdif.o
+obj-$(CONFIG_SND_SOC_BLUETOOTH) += snd-soc-bluetooth.o
+obj-$(CONFIG_SND_SOC_MXS_SPDIF) += snd-soc-mxs-spdif.o
+obj-$(CONFIG_SND_SOC_MXS_ADC_CODEC) += snd-soc-mxs-adc.o
diff --git a/sound/soc/codecs/ak4647.c b/sound/soc/codecs/ak4647.c
new file mode 100644
index 000000000000..c3d14e99be0e
--- /dev/null
+++ b/sound/soc/codecs/ak4647.c
@@ -0,0 +1,799 @@
+/*
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file ak4647.c
+ * @brief Driver for AK4647
+ *
+ * @ingroup Sound
+ */
+
+#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/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ak4647.h"
+
+#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos))
+#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos))
+
+static struct i2c_client *ak4647_i2c_client;
+
+int ak4647_read_reg(unsigned int reg, u8 *value)
+{
+ s32 retval;
+ retval = i2c_smbus_read_byte_data(ak4647_i2c_client, reg);
+ if (retval < 0) {
+ pr_err("%s:read reg errorr:reg=%x,val=%x\n",
+ __func__, reg, retval);
+ return -1;
+ } else {
+ *value = (u8) retval;
+ return 0;
+ }
+}
+
+int ak4647_write_reg(unsigned int reg, u8 value)
+{
+ if (i2c_smbus_write_byte_data(ak4647_i2c_client, reg, value) < 0) {
+ pr_err("%s:write reg errorr:reg=%x,val=%x\n",
+ __func__, reg, value);
+ return -1;
+ }
+ return 0;
+}
+
+static unsigned int ak4647_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 value;
+ ak4647_read_reg(reg, &value);
+ return value;
+}
+
+static int ak4647_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ return ak4647_write_reg(reg, value);
+}
+
+#define DEBUG_AK4647 0
+
+#if DEBUG_AK4647
+
+static char *ak4647_reg_names[] = {
+ "AK4647_PM1",
+ "AK4647_PM2",
+ "AK4647_SIG1",
+ "AK4647_SIG2",
+ "AK4647_MODE1",
+ "AK4647_MODE2",
+ "AK4647_TIMER",
+ "AK4647_ALC1",
+ "AK4647_ALC2",
+ "AK4647_LEFT_INPUT_VOLUME",
+ "AK4647_LEFT_DGT_VOLUME",
+ "AK4647_ALC3",
+ "AK4647_RIGHT_INPUT_VOLUME",
+ "AK4647_RIGHT_DGT_VOLUME",
+ "AK4647_MODE3",
+ "AK4647_MODE4",
+ "AK4647_PM3",
+ "AK4647_DGT_FIL_SEL",
+ "AK4647_FIL3_COEF0",
+ "AK4647_FIL3_COEF1",
+ "AK4647_FIL3_COEF2",
+ "AK4647_FIL3_COEF3",
+ "AK4647_EQ_COEF0",
+ "AK4647_EQ_COEF1",
+ "AK4647_EQ_COEF2",
+ "AK4647_EQ_COEF3",
+ "AK4647_EQ_COEF4",
+ "AK4647_EQ_COEF5",
+ "AK4647_FIL1_COEF0",
+ "AK4647_FIL1_COEF1",
+ "AK4647_FIL1_COEF2",
+ "AK4647_FIL1_COEF3",
+};
+
+static void dump_all_regs(void)
+{
+ int i;
+ u8 value;
+
+ for (i = AK4647_REG_START; i < AK4647_REG_NUMBER; i++) {
+ ak4647_read_reg(i, &value);
+ pr_info("%s = 0x%x\n", ak4647_reg_names[i], value);
+ }
+}
+#endif
+
+static int loopback_xhandle_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+static int loopback_xhandle_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+
+static const char *ak4647_hp_out[] = { "Stereo", "Mono" };
+
+static const char *ak4647_left_in[] = { "LIN1", "LIN2" };
+
+static const char *ak4647_right_in[] = { "RIN1", "RIN2" };
+
+static const char *ak4647_deemp[] = { "44.1kHz", "Off", "48kHz", "32kHz" };
+
+static const struct soc_enum ak4647_enum[] = {
+ SOC_ENUM_SINGLE(AK4647_MODE4, 2, 2, ak4647_hp_out),
+ SOC_ENUM_SINGLE(AK4647_MODE3, 0, 4, ak4647_deemp),
+ SOC_ENUM_SINGLE(AK4647_PM3, 1, 2, ak4647_left_in),
+ SOC_ENUM_SINGLE(AK4647_PM3, 2, 2, ak4647_right_in),
+};
+
+#undef snd_soc_info_bool_ext
+#define snd_soc_info_bool_ext snd_ctl_boolean_mono_info
+static const struct snd_kcontrol_new ak4647_snd_controls[] = {
+ SOC_ENUM("Headphone Output", ak4647_enum[0]),
+ SOC_ENUM("Playback Deemphasis", ak4647_enum[1]),
+ SOC_ENUM("Left Capture Select", ak4647_enum[2]),
+ SOC_ENUM("Right Capture Select", ak4647_enum[3]),
+ SOC_SINGLE("Bass Volume", AK4647_MODE3, 2, 3, 0),
+ SOC_SINGLE("Mic Boost (+20dB) Switch", AK4647_SIG1, 0, 1, 0),
+ SOC_SINGLE("Mic Bias", AK4647_SIG1, 2, 1, 0),
+ SOC_SINGLE("ALC Switch", AK4647_ALC1, 5, 1, 0),
+ SOC_SINGLE("ALC Recovery Time", AK4647_TIMER, 2, 3, 0),
+ SOC_SINGLE("ALC ZC Time", AK4647_TIMER, 4, 3, 0),
+ SOC_SINGLE("ALC Volume", AK4647_ALC2, 0, 127, 0),
+ SOC_SINGLE("Left Capture Volume", AK4647_LEFT_INPUT_VOLUME, 0, 242, 0),
+ SOC_SINGLE("Right Capture Volume",
+ AK4647_RIGHT_INPUT_VOLUME, 0, 242, 0),
+ SOC_SINGLE("Left Playback Volume", AK4647_LEFT_DGT_VOLUME, 0, 255, 1),
+ SOC_SINGLE("Right Playback Volume", AK4647_RIGHT_DGT_VOLUME, 0, 255, 1),
+ SOC_SINGLE_BOOL_EXT("Loopback Line-in", 0,
+ loopback_xhandle_get, loopback_xhandle_put),
+};
+
+/* Stereo Mixer for HP*/
+static const struct snd_kcontrol_new ak4647_hp_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Playback Switch", AK4647_MODE4, 0, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_MODE4, 1, 1, 0),
+};
+
+/* Stereo Mixer for Line out*/
+static const struct snd_kcontrol_new ak4647_line_stereo_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Playback Switch", AK4647_SIG1, 4, 1, 0),
+ SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_SIG2, 2, 1, 0),
+};
+
+/* ak4647 dapm widgets */
+static const struct snd_soc_dapm_widget ak4647_dapm_widgets[] = {
+ SND_SOC_DAPM_MIXER("Headphone Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4647_hp_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4647_hp_stereo_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Lineout Mixer", SND_SOC_NOPM, 0, 0,
+ &ak4647_line_stereo_mixer_controls[0],
+ ARRAY_SIZE(ak4647_line_stereo_mixer_controls)),
+ SND_SOC_DAPM_DAC("DAC", "Playback", AK4647_PM1, 2, 0),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+
+ SND_SOC_DAPM_ADC("Left ADC", "Capture", AK4647_PM1, 0, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Capture", AK4647_PM3, 0, 0),
+ SND_SOC_DAPM_PGA("HP R Amp", AK4647_PM2, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP L Amp", AK4647_PM2, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic", AK4647_PM1, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Line Out Amp", AK4647_PM1, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", AK4647_SIG1, 2, 0),
+ SND_SOC_DAPM_INPUT("Left Input"),
+ SND_SOC_DAPM_INPUT("Right Input"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /*headphone stereo mixer */
+ {"Headphone Mixer", "Playback Switch", "DAC"},
+ {"Headphone Mixer", "Mic Sidetone Switch", "Mic"},
+
+ /*lineout stereo mixer */
+ {"Lineout Mixer", "Playback Switch", "DAC"},
+ {"Lineout Mixer", "Mic Sidetone Switch", "Mic"},
+
+ /* headphone amp */
+ {"HP R Amp", NULL, "Headphone Mixer"},
+ {"HP L Amp", NULL, "Headphone Mixer"},
+
+ /* headphone */
+ {"HPR", NULL, "HP R Amp"},
+ {"HPL", NULL, "HP L Amp"},
+
+ /* line out */
+ {"Line Out Amp", NULL, "Lineout Mixer"},
+ {"LOUT", NULL, "Line Out Amp"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left Input"},
+ {"Right ADC", NULL, "Right Input"},
+
+};
+
+static int ak4647_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ak4647_dapm_widgets,
+ ARRAY_SIZE(ak4647_dapm_widgets));
+
+ /* set up audio path audio_mapnects */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+#define AK4647_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_48000)
+
+#define AK4647_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int ak4647_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u8 value;
+ u8 fs = 0;
+
+ /* FS3 is on D5 */
+ switch (freq) {
+ case 8000:
+ fs = 0x0;
+ break;
+ case 11025:
+ fs = 0x5;
+ break;
+ case 16000:
+ fs = 0x2;
+ break;
+ case 22050:
+ fs = 0x7;
+ break;
+ case 32000:
+ fs = 0x22;
+ break;
+ case 44100:
+ fs = 0x27;
+ break;
+ case 48000:
+ fs = 0x23;
+ break;
+ default:
+ pr_err("unsupported sample rate");
+ return -1;
+ }
+
+ ak4647_read_reg(AK4647_MODE2, &value);
+ value &= 0xC0;
+ value |= fs;
+ ak4647_write_reg(AK4647_MODE2, value);
+ return 0;
+}
+
+static int ak4647_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ u8 reg_mode1, reg_pm2;
+
+ /* get current values */
+ ak4647_read_reg(AK4647_MODE1, &reg_mode1);
+ ak4647_read_reg(AK4647_PM2, &reg_pm2);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ SET_BIT_IN_BYTE(reg_pm2, 3);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ CLEAR_BIT_IN_BYTE(reg_pm2, 3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ SET_BIT_IN_BYTE(reg_mode1, 0);
+ SET_BIT_IN_BYTE(reg_mode1, 1);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ SET_BIT_IN_BYTE(reg_mode1, 0);
+ CLEAR_BIT_IN_BYTE(reg_mode1, 1);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ CLEAR_BIT_IN_BYTE(reg_mode1, 0);
+ SET_BIT_IN_BYTE(reg_mode1, 1);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ default:
+ pr_err("dai format %d not supported", fmt);
+ return -EINVAL;
+ }
+
+ ak4647_write_reg(AK4647_MODE1, reg_mode1);
+ ak4647_write_reg(AK4647_PM2, reg_pm2);
+ return 0;
+}
+
+static int ak4647_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div)
+{
+ int retval = 0;
+ u8 value;
+ if (AK4647_BCLK_CLKDIV == div_id) {
+ ak4647_read_reg(AK4647_MODE1, &value);
+ switch (div) {
+ case AK4647_BCLK_DIV_32:
+ CLEAR_BIT_IN_BYTE(value, 3);
+ ak4647_write_reg(AK4647_MODE1, value);
+ break;
+ case AK4647_BCLK_DIV_64:
+ SET_BIT_IN_BYTE(value, 3);
+ ak4647_write_reg(AK4647_MODE1, value);
+ break;
+ default:
+ retval = -1;
+ pr_err("wrong div value for divid %d", div_id);
+ break;
+ }
+ } else if (AK4647_MCLK_CLKDIV == div_id) {
+ ak4647_read_reg(AK4647_MODE2, &value);
+ switch (div) {
+ case AK4647_MCLK_DIV_32:
+ SET_BIT_IN_BYTE(value, 7);
+ SET_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_MODE2, value);
+ break;
+ case AK4647_MCLK_DIV_64:
+ SET_BIT_IN_BYTE(value, 7);
+ CLEAR_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_MODE2, value);
+ break;
+ case AK4647_MCLK_DIV_128:
+ CLEAR_BIT_IN_BYTE(value, 7);
+ SET_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_MODE2, value);
+ break;
+ case AK4647_MCLK_DIV_256:
+ CLEAR_BIT_IN_BYTE(value, 7);
+ CLEAR_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_MODE2, value);
+ break;
+ default:
+ retval = -1;
+ pr_err("wrong div value for div id %d", div_id);
+ break;
+ }
+ } else {
+ retval = -1;
+ pr_err("wrong div id");
+ }
+
+ return retval;
+}
+
+static int ak4647_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ u8 value;
+
+ ak4647_read_reg(AK4647_MODE3, &value);
+
+ if (mute)
+ SET_BIT_IN_BYTE(value, 5);
+ else
+ CLEAR_BIT_IN_BYTE(value, 5);
+ ak4647_write_reg(AK4647_MODE3, value);
+ return 0;
+}
+
+struct ak4647_state_data {
+ u8 left_capture_vol;
+ u8 right_capture_vol;
+};
+static struct ak4647_state_data ak4647_state;
+
+static int ak4647_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 value;
+ /* for playback, save down capture volume */
+ if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+ ak4647_read_reg(AK4647_LEFT_INPUT_VOLUME, &value);
+ ak4647_state.left_capture_vol = value;
+ ak4647_read_reg(AK4647_RIGHT_INPUT_VOLUME, &value);
+ ak4647_state.right_capture_vol = value;
+
+ /* when PMADL=PMADR=0 set IVL &IVR to be 0x91 (0db) */
+ ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME, 0x91);
+ ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME, 0x91);
+ }
+
+ /* output digital volume independent */
+ ak4647_read_reg(AK4647_MODE3, &value);
+ CLEAR_BIT_IN_BYTE(value, 4);
+ ak4647_write_reg(AK4647_MODE3, value);
+ return 0;
+}
+
+static void ak4647_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 value;
+ ak4647_read_reg(AK4647_PM2, &value);
+ /* mute */
+ CLEAR_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_PM2, value);
+
+ /* after playback, restore capture volume */
+ if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+ ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME,
+ ak4647_state.left_capture_vol);
+ ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME,
+ ak4647_state.right_capture_vol);
+ }
+}
+
+static int ak4647_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 value;
+
+ /* VCOM power on */
+ ak4647_write_reg(AK4647_PM1, 0x44);
+ msleep(30);
+
+ ak4647_read_reg(AK4647_PM2, &value);
+ /* PLL enabled */
+ SET_BIT_IN_BYTE(value, 0);
+
+ ak4647_write_reg(AK4647_PM2, value);
+ /* wait for PLL locked */
+ msleep(40);
+
+ /* don't mute */
+ SET_BIT_IN_BYTE(value, 6);
+ ak4647_write_reg(AK4647_PM2, value);
+ return 0;
+}
+
+struct snd_soc_dai_ops ak4647_ops = {
+ .prepare = ak4647_pcm_prepare,
+ .startup = ak4647_pcm_startup,
+ .shutdown = ak4647_pcm_shutdown,
+ .digital_mute = ak4647_digital_mute,
+ .set_fmt = ak4647_set_dai_fmt,
+ .set_sysclk = ak4647_set_dai_sysclk,
+ .set_clkdiv = ak4647_set_clkdiv,
+};
+
+struct snd_soc_dai ak4647_hifi_dai = {
+ .name = "ak4647",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AK4647_RATES,
+ .formats = AK4647_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4647_RATES,
+ .formats = AK4647_FORMATS,
+ },
+ .ops = &ak4647_ops,
+};
+EXPORT_SYMBOL_GPL(ak4647_hifi_dai);
+
+static struct snd_soc_codec *ak4647_codec;
+
+static int ak4647_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = ak4647_codec;
+ int ret = 0;
+
+ socdev->card->codec = ak4647_codec;
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(&ak4647_i2c_client->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ /* setup init value for audio path controls here */
+ /* enable DAC to headphone */
+ ak4647_write_reg(AK4647_MODE4, 0x9);
+ /* capture source to be LIN2 and RIN2 */
+ ak4647_write_reg(AK4647_PM3, 0x6);
+ /* MPWR pin up */
+ ak4647_write_reg(AK4647_SIG1, 0x5);
+
+ /* MCKI = 12M, default audio interface format as "left-justified" */
+ ak4647_write_reg(AK4647_MODE1, 0x62);
+
+ /* ALC disabled */
+ ak4647_write_reg(AK4647_ALC1, 0x0);
+
+ ak4647_state.left_capture_vol = 0x91;
+ ak4647_state.right_capture_vol = 0x91;
+
+ snd_soc_add_controls(codec, ak4647_snd_controls,
+ ARRAY_SIZE(ak4647_snd_controls));
+ ak4647_add_widgets(codec);
+
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ pr_err("ak4647: failed to register card\n");
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static __devexit int ak4647_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+
+ snd_soc_unregister_dai(&ak4647_hifi_dai);
+ snd_soc_unregister_codec(codec);
+ kfree(codec);
+ ak4647_codec = NULL;
+ return 0;
+}
+
+static int ak4647_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_codec *codec;
+ int ret;
+ u8 val;
+
+ if (ak4647_codec) {
+ dev_err(&client->dev,
+ "Multiple AK4647 devices not supported\n");
+ return -ENOMEM;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ i2c_set_clientdata(client, codec);
+ ak4647_i2c_client = client;
+ codec->control_data = client;
+ ret = ak4647_read_reg(AK4647_REG_START, &val);
+ if (ret < 0) {
+ pr_err("Device with ID register %x is not a AK4647\n", val);
+ return -ENODEV;
+ }
+
+ codec->dev = &client->dev;
+ codec->name = "ak4647", codec->owner = THIS_MODULE;
+ codec->owner = THIS_MODULE;
+ codec->read = ak4647_codec_read;
+ codec->write = ak4647_codec_write;
+ codec->dai = &ak4647_hifi_dai;
+ codec->num_dai = 1;
+
+ ak4647_codec = codec;
+ ak4647_hifi_dai.dev = &client->dev;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_dai(&ak4647_hifi_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct i2c_device_id ak4647_id[] = {
+ {"ak4647-i2c", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, ak4647_id);
+
+static struct i2c_driver ak4647_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ak4647-i2c",
+ },
+ .probe = ak4647_i2c_probe,
+ .remove = __devexit_p(ak4647_i2c_remove),
+ .id_table = ak4647_id,
+};
+
+int pmic_audio_fm_output_enable(bool enable)
+{
+ u8 val;
+ if (enable) {
+ ak4647_set_dai_fmt(NULL,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+ ak4647_set_dai_sysclk(NULL, 0, 44100, 0);
+ ak4647_set_clkdiv(NULL, 0, 0);
+ /* VCOM power on */
+ ak4647_write_reg(AK4647_PM1, 0x44);
+ msleep(30);
+
+ ak4647_read_reg(AK4647_PM2, &val);
+ /* PLL enabled */
+ SET_BIT_IN_BYTE(val, 0);
+
+ ak4647_write_reg(AK4647_PM2, val);
+ /* wait for PLL locked */
+ msleep(40);
+
+ /* don't mute */
+ SET_BIT_IN_BYTE(val, 6);
+ ak4647_write_reg(AK4647_PM2, val);
+
+ /* loopback STDO to DAC */
+ ak4647_read_reg(AK4647_MODE3, &val);
+ SET_BIT_IN_BYTE(val, 6);
+ ak4647_write_reg(AK4647_MODE3, val);
+
+ /* switch to R/L 1 */
+ ak4647_read_reg(AK4647_PM3, &val);
+ CLEAR_BIT_IN_BYTE(val, 1);
+ CLEAR_BIT_IN_BYTE(val, 2);
+ ak4647_write_reg(AK4647_PM3, val);
+
+ /* power up ADC */
+ ak4647_read_reg(AK4647_PM1, &val);
+ SET_BIT_IN_BYTE(val, 0);
+ ak4647_write_reg(AK4647_PM1, val);
+ ak4647_read_reg(AK4647_PM3, &val);
+ SET_BIT_IN_BYTE(val, 0);
+ ak4647_write_reg(AK4647_PM3, val);
+
+ /* power up DAC */
+ ak4647_read_reg(AK4647_PM1, &val);
+ SET_BIT_IN_BYTE(val, 2);
+ ak4647_write_reg(AK4647_PM1, val);
+
+ msleep(30);
+
+ /* headphone output switch on */
+ ak4647_read_reg(AK4647_MODE4, &val);
+ SET_BIT_IN_BYTE(val, 0);
+ ak4647_write_reg(AK4647_MODE4, val);
+
+ /* power on headphone amp */
+ ak4647_read_reg(AK4647_PM2, &val);
+ SET_BIT_IN_BYTE(val, 4);
+ SET_BIT_IN_BYTE(val, 5);
+ ak4647_write_reg(AK4647_PM2, val);
+
+ ak4647_digital_mute(NULL, 0);
+ } else {
+ ak4647_digital_mute(NULL, 1);
+
+ /* disbale loopback */
+ ak4647_read_reg(AK4647_MODE3, &val);
+ CLEAR_BIT_IN_BYTE(val, 6);
+ SET_BIT_IN_BYTE(val, 5);
+ ak4647_write_reg(AK4647_MODE3, val);
+
+ /* switch to R/L 2 */
+ ak4647_read_reg(AK4647_PM3, &val);
+ SET_BIT_IN_BYTE(val, 1);
+ SET_BIT_IN_BYTE(val, 2);
+ ak4647_write_reg(AK4647_PM3, val);
+ }
+ return 0;
+}
+
+static int loopback_xhandle_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kcontrol->private_value;
+ return 0;
+}
+
+static int loopback_xhandle_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int changed;
+ long flag = ucontrol->value.integer.value[0];
+ changed =
+ (ucontrol->value.integer.value[0] ==
+ kcontrol->private_value) ? 0 : 1;
+ kcontrol->private_value = flag;
+ if (flag)
+ pmic_audio_fm_output_enable(true);
+ else
+ pmic_audio_fm_output_enable(false);
+
+ return changed;
+}
+
+/* power down chip */
+static int ak4647_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ i2c_del_driver(&ak4647_i2c_driver);
+ kfree(codec);
+
+ return 0;
+}
+
+static int ak4647_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int ak4647_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ak4647 = {
+ .probe = ak4647_probe,
+ .remove = ak4647_remove,
+ .suspend = ak4647_suspend,
+ .resume = ak4647_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak4647);
+
+static int __init ak4647_modinit(void)
+{
+ return i2c_add_driver(&ak4647_i2c_driver);
+}
+module_init(ak4647_modinit);
+
+static void __exit ak4647_exit(void)
+{
+ i2c_del_driver(&ak4647_i2c_driver);
+}
+module_exit(ak4647_exit);
+
+MODULE_DESCRIPTION("ASoC ak4647 driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4647.h b/sound/soc/codecs/ak4647.h
new file mode 100644
index 000000000000..d88832b1dd09
--- /dev/null
+++ b/sound/soc/codecs/ak4647.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file ak4647.h
+ * @brief Driver for AK4647
+ *
+ * @ingroup Sound
+ */
+#ifndef _AK4647_H_
+#define _AK4647_H_
+
+#ifdef __KERNEL__
+
+/*!
+ * AK4647 registers
+ */
+
+#define AK4647_PM1 0x00
+#define AK4647_PM2 0x01
+#define AK4647_SIG1 0x02
+#define AK4647_SIG2 0x03
+#define AK4647_MODE1 0x04
+#define AK4647_MODE2 0x05
+#define AK4647_TIMER 0x06
+#define AK4647_ALC1 0x07
+#define AK4647_ALC2 0x08
+
+#define AK4647_LEFT_INPUT_VOLUME 0x09
+#define AK4647_LEFT_DGT_VOLUME 0x0A
+#define AK4647_ALC3 0x0B
+#define AK4647_RIGHT_INPUT_VOLUME 0x0C
+#define AK4647_RIGHT_DGT_VOLUME 0x0D
+#define AK4647_MODE3 0x0E
+#define AK4647_MODE4 0x0F
+#define AK4647_PM3 0x10
+#define AK4647_DGT_FIL_SEL 0x11
+
+/* filter 3 coeffecient*/
+
+#define AK4647_FIL3_COEF0 0x12
+#define AK4647_FIL3_COEF1 0x13
+#define AK4647_FIL3_COEF2 0x14
+#define AK4647_FIL3_COEF3 0x15
+
+/* eq coeffecient*/
+
+#define AK4647_EQ_COEF0 0x16
+#define AK4647_EQ_COEF1 0x17
+#define AK4647_EQ_COEF2 0x18
+#define AK4647_EQ_COEF3 0x19
+#define AK4647_EQ_COEF4 0x1A
+#define AK4647_EQ_COEF5 0x1B
+
+/* filter 3 coeffecient*/
+
+#define AK4647_FIL1_COEF0 0x1C
+#define AK4647_FIL1_COEF1 0x1D
+#define AK4647_FIL1_COEF2 0x1E
+#define AK4647_FIL1_COEF3 0x1F
+
+#define AK4647_REG_START 0x00
+#define AK4647_REG_END 0x1F
+#define AK4647_REG_NUMBER 0x20
+
+/* clock divider id's */
+#define AK4647_BCLK_CLKDIV 0
+#define AK4647_MCLK_CLKDIV 1
+
+/* bit clock div values (AK4647_BCLK_CLKDIV)*/
+#define AK4647_BCLK_DIV_32 0
+#define AK4647_BCLK_DIV_64 1
+
+/* m clock div values (AK4647_MCLK_CLKDIV)*/
+#define AK4647_MCLK_DIV_32 0
+#define AK4647_MCLK_DIV_64 1
+#define AK4647_MCLK_DIV_128 2
+#define AK4647_MCLK_DIV_256 3
+
+#endif /* __KERNEL__ */
+
+#endif /* _AK4647_H_ */
diff --git a/sound/soc/codecs/ak5702.c b/sound/soc/codecs/ak5702.c
new file mode 100644
index 000000000000..a34f48e52b3f
--- /dev/null
+++ b/sound/soc/codecs/ak5702.c
@@ -0,0 +1,607 @@
+/*
+ * ak5702.c -- AK5702 Soc Audio driver
+ *
+ * Copyright 2009 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "ak5702.h"
+
+#define AK5702_VERSION "0.1"
+
+/* codec private data */
+struct ak5702_priv {
+ unsigned int sysclk;
+};
+
+/*
+ * ak5702 register cache
+ */
+static const u16 ak5702_reg[AK5702_CACHEREGNUM] = {
+ 0x0000, 0x0024, 0x0000, 0x0001, 0x0023, 0x001f,
+ 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1, 0x0000,
+ 0x00a0, 0x0000, 0x0000, 0x0000, 0x0001, 0x0020,
+ 0x0000, 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1,
+ 0x0000,
+};
+
+/*
+ * read ak5702 register cache
+ */
+static inline unsigned int ak5702_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= AK5702_CACHEREGNUM)
+ return -1;
+ return cache[reg];
+}
+
+static inline unsigned int ak5702_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 data;
+ data = reg;
+
+ if (codec->hw_write(codec->control_data, &data, 1) != 1)
+ return -EIO;
+ if (codec->hw_read(codec->control_data, &data, 1) != 1)
+ return -EIO;
+
+ return data;
+};
+
+/*
+ * write ak5702 register cache
+ */
+static inline void ak5702_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+
+ if (reg >= AK5702_CACHEREGNUM)
+ return;
+ cache[reg] = value;
+}
+
+/*
+ * write to the AK5702 register space
+ */
+static int ak5702_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ ak5702_write_reg_cache(codec, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static const char *ak5702_mic_gain[] = { "0dB", "+15dB", "+30dB", "+36dB" };
+static const char *ak5702_adca_left_type[] =
+ { "Single-ended", "Full-differential" };
+static const char *ak5702_adca_right_type[] =
+ { "Single-ended", "Full-differential" };
+static const char *ak5702_adcb_left_type[] =
+ { "Single-ended", "Full-differential" };
+static const char *ak5702_adcb_right_type[] =
+ { "Single-ended", "Full-differential" };
+static const char *ak5702_adca_left_input[] = { "LIN1", "LIN2" };
+static const char *ak5702_adca_right_input[] = { "RIN1", "RIN2" };
+static const char *ak5702_adcb_left_input[] = { "LIN3", "LIN4" };
+static const char *ak5702_adcb_right_input[] = { "RIN3", "RIN4" };
+
+static const struct soc_enum ak5702_enum[] = {
+ SOC_ENUM_SINGLE(AK5702_MICG1, 0, 4, ak5702_mic_gain),
+ SOC_ENUM_SINGLE(AK5702_MICG2, 0, 4, ak5702_mic_gain),
+
+ SOC_ENUM_SINGLE(AK5702_SIG1, 0, 2, ak5702_adca_left_input),
+ SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adca_right_input),
+ SOC_ENUM_SINGLE(AK5702_SIG2, 0, 2, ak5702_adcb_left_input),
+ SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adcb_right_input),
+
+ SOC_ENUM_SINGLE(AK5702_SIG1, 2, 2, ak5702_adca_left_type),
+ SOC_ENUM_SINGLE(AK5702_SIG1, 3, 2, ak5702_adca_right_type),
+ SOC_ENUM_SINGLE(AK5702_SIG2, 2, 2, ak5702_adcb_left_type),
+ SOC_ENUM_SINGLE(AK5702_SIG2, 3, 2, ak5702_adcb_right_type),
+};
+
+static const struct snd_kcontrol_new ak5702_snd_controls[] = {
+ SOC_SINGLE("ADCA Left Vol", AK5702_LVOL1, 0, 242, 0),
+ SOC_SINGLE("ADCA Right Vol", AK5702_RVOL1, 0, 242, 0),
+ SOC_SINGLE("ADCB Left Vol", AK5702_LVOL2, 0, 242, 0),
+ SOC_SINGLE("ADCB Right Vol", AK5702_RVOL2, 0, 242, 0),
+
+ SOC_ENUM("MIC-AmpA Gain", ak5702_enum[0]),
+ SOC_ENUM("MIC-AmpB Gain", ak5702_enum[1]),
+
+ SOC_ENUM("ADCA Left Source", ak5702_enum[2]),
+ SOC_ENUM("ADCA Right Source", ak5702_enum[3]),
+ SOC_ENUM("ADCB Left Source", ak5702_enum[4]),
+ SOC_ENUM("ADCB Right Source", ak5702_enum[5]),
+
+ SOC_ENUM("ADCA Left Type", ak5702_enum[6]),
+ SOC_ENUM("ADCA Right Type", ak5702_enum[7]),
+ SOC_ENUM("ADCB Left Type", ak5702_enum[8]),
+ SOC_ENUM("ADCB Right Type", ak5702_enum[9]),
+};
+
+/* ak5702 dapm widgets */
+static const struct snd_soc_dapm_widget ak5702_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADCA Left", "Capture", AK5702_PM1, 0, 0),
+ SND_SOC_DAPM_ADC("ADCA Right", "Capture", AK5702_PM1, 1, 0),
+ SND_SOC_DAPM_ADC("ADCB Left", "Capture", AK5702_PM2, 0, 0),
+ SND_SOC_DAPM_ADC("ADCB Right", "Capture", AK5702_PM2, 1, 0),
+
+ SND_SOC_DAPM_INPUT("ADCA Left Input"),
+ SND_SOC_DAPM_INPUT("ADCA Right Input"),
+ SND_SOC_DAPM_INPUT("ADCB Left Input"),
+ SND_SOC_DAPM_INPUT("ADCB Right Input"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"ADCA Left", NULL, "ADCA Left Input"},
+ {"ADCA Right", NULL, "ADCA Right Input"},
+ {"ADCB Left", NULL, "ADCB Left Input"},
+ {"ADCB Right", NULL, "ADCB Right Input"},
+};
+
+static int ak5702_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ak5702_dapm_widgets,
+ ARRAY_SIZE(ak5702_dapm_widgets));
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int ak5702_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 fs = 0;
+ u8 value;
+
+ switch (freq) {
+ case 8000:
+ fs = 0x0;
+ break;
+ case 11025:
+ fs = 0x05;
+ break;
+ case 12000:
+ fs = 0x01;
+ break;
+ case 16000:
+ fs = 0x02;
+ break;
+ case 22050:
+ fs = 0x07;
+ break;
+ case 24000:
+ fs = 0x03;
+ break;
+ case 32000:
+ fs = 0x0a;
+ break;
+ case 44100:
+ fs = 0x0f;
+ break;
+ case 48000:
+ fs = 0x0b;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ value = ak5702_read_reg_cache(codec, AK5702_FS1);
+ value &= (~AK5702_FS1_FS_MASK);
+ value |= fs;
+ ak5702_write(codec, AK5702_FS1, value);
+ return 0;
+}
+
+static int ak5702_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 fmt1 = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ fmt1 = AK5702_FMT1_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ fmt1 = AK5702_FMT1_MSB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ak5702_write(codec, AK5702_FMT1, fmt1);
+ ak5702_write(codec, AK5702_FMT2, AK5702_FMT2_STEREO);
+ return 0;
+}
+
+static int ak5702_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 reg = 0;
+
+ reg = ak5702_read_reg_cache(codec, AK5702_PLL1);
+ switch (pll_id) {
+ case AK5702_PLL_POWERDOWN:
+ reg &= (~AK5702_PLL1_PM_MASK);
+ reg |= AK5702_PLL1_POWERDOWN;
+ break;
+ case AK5702_PLL_MASTER:
+ reg &= (~AK5702_PLL1_MODE_MASK);
+ reg |= AK5702_PLL1_MASTER;
+ reg |= AK5702_PLL1_POWERUP;
+ break;
+ case AK5702_PLL_SLAVE:
+ reg &= (~AK5702_PLL1_MODE_MASK);
+ reg |= AK5702_PLL1_SLAVE;
+ reg |= AK5702_PLL1_POWERUP;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ switch (freq_in) {
+ case 11289600:
+ reg &= (~AK5702_PLL1_PLL_MASK);
+ reg |= AK5702_PLL1_11289600;
+ break;
+ case 12000000:
+ reg &= (~AK5702_PLL1_PLL_MASK);
+ reg |= AK5702_PLL1_12000000;
+ break;
+ case 12288000:
+ reg &= (~AK5702_PLL1_PLL_MASK);
+ reg |= AK5702_PLL1_12288000;
+ break;
+ case 19200000:
+ reg &= (~AK5702_PLL1_PLL_MASK);
+ reg |= AK5702_PLL1_19200000;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ak5702_write(codec, AK5702_PLL1, reg);
+ return 0;
+}
+
+static int ak5702_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 reg = 0;
+
+ if (div_id == AK5702_BCLK_CLKDIV) {
+ reg = ak5702_read_reg_cache(codec, AK5702_FS1);
+ switch (div) {
+ case AK5702_BCLK_DIV_32:
+ reg &= (~AK5702_FS1_BCKO_MASK);
+ reg |= AK5702_FS1_BCKO_32FS;
+ ak5702_write(codec, AK5702_FS1, reg);
+ break;
+ case AK5702_BCLK_DIV_64:
+ reg &= (~AK5702_FS1_BCKO_MASK);
+ reg |= AK5702_FS1_BCKO_64FS;
+ ak5702_write(codec, AK5702_FS1, reg);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int ak5702_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u8 reg = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ reg = ak5702_read_reg_cache(codec, AK5702_PM1);
+ ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM);
+ reg = ak5702_read_reg_cache(codec, AK5702_PLL1);
+ reg = reg | AK5702_PLL1_POWERUP | AK5702_PLL1_MASTER;
+ ak5702_write(codec, AK5702_PLL1, reg);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ reg = ak5702_read_reg_cache(codec, AK5702_PM1);
+ ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM);
+ reg = ak5702_read_reg_cache(codec, AK5702_PLL1);
+ ak5702_write(codec, AK5702_PLL1, reg & (~AK5702_PLL1_POWERUP));
+ break;
+ case SND_SOC_BIAS_OFF:
+ reg = ak5702_read_reg_cache(codec, AK5702_PM1);
+ ak5702_write(codec, AK5702_PM1, reg & (~AK5702_PM1_PMVCM));
+ break;
+ }
+
+ codec->bias_level = level;
+ return 0;
+}
+
+static int ak5702_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ak5702_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int i;
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ ak5702_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ ak5702_set_bias_level(codec, codec->suspend_bias_level);
+
+ /* Sync back everything else */
+ for (i = 0; i < ARRAY_SIZE(ak5702_reg); i++)
+ ak5702_write(codec, i, ak5702_reg[i]);
+
+ return 0;
+}
+
+#define AK5702_RATES SNDRV_PCM_RATE_8000_48000
+#define AK5702_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai_ops ak5702_ops = {
+ .set_fmt = ak5702_set_dai_fmt,
+ .set_sysclk = ak5702_set_dai_sysclk,
+ .set_clkdiv = ak5702_set_dai_clkdiv,
+ .set_pll = ak5702_set_dai_pll,
+};
+
+struct snd_soc_dai ak5702_dai = {
+ .name = "AK5702",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = AK5702_RATES,
+ .formats = AK5702_FORMATS,
+ },
+ .ops = &ak5702_ops,
+};
+EXPORT_SYMBOL_GPL(ak5702_dai);
+
+static struct snd_soc_codec *ak5702_codec;
+
+static int ak5702_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = ak5702_codec;
+ int ret = 0;
+ u8 reg = 0;
+
+ socdev->card->codec = ak5702_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "ak5702: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ /* power on device */
+ reg = ak5702_read_reg_cache(codec, AK5702_PM1);
+ reg |= AK5702_PM1_PMVCM;
+ ak5702_write(codec, AK5702_PM1, reg);
+
+ /* initialize ADC */
+ reg = AK5702_SIG1_L_LIN1 | AK5702_SIG1_R_RIN2;
+ ak5702_write(codec, AK5702_SIG1, reg);
+ reg = AK5702_SIG2_L_LIN3 | AK5702_SIG2_R_RIN4;
+ ak5702_write(codec, AK5702_SIG2, reg);
+
+ reg = ak5702_read_reg_cache(codec, AK5702_PM1);
+ reg = reg | AK5702_PM1_PMADAL | AK5702_PM1_PMADAR;
+ ak5702_write(codec, AK5702_PM1, reg);
+ reg = ak5702_read_reg_cache(codec, AK5702_PM2);
+ reg = reg | AK5702_PM2_PMADBL | AK5702_PM2_PMADBR;
+ ak5702_write(codec, AK5702_PM2, reg);
+
+ /* initialize volume */
+ ak5702_write(codec, AK5702_MICG1, AK5702_MICG1_INIT);
+ ak5702_write(codec, AK5702_MICG2, AK5702_MICG2_INIT);
+ ak5702_write(codec, AK5702_VOL1, AK5702_VOL1_IVOLAC);
+ ak5702_write(codec, AK5702_VOL2, AK5702_VOL2_IVOLBC);
+ ak5702_write(codec, AK5702_LVOL1, AK5702_LVOL1_INIT);
+ ak5702_write(codec, AK5702_RVOL1, AK5702_RVOL1_INIT);
+ ak5702_write(codec, AK5702_LVOL2, AK5702_LVOL2_INIT);
+ ak5702_write(codec, AK5702_RVOL2, AK5702_RVOL2_INIT);
+
+ snd_soc_add_controls(codec, ak5702_snd_controls,
+ ARRAY_SIZE(ak5702_snd_controls));
+ ak5702_add_widgets(codec);
+
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "ak5702: failed to register card\n");
+ goto card_err;
+ }
+
+ return ret;
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ kfree(codec->reg_cache);
+ return ret;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int ak5702_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ak5702_priv *ak5702;
+ struct snd_soc_codec *codec;
+ int ret;
+
+ if (ak5702_codec) {
+ dev_err(&client->dev,
+ "Multiple AK5702 devices not supported\n");
+ return -ENOMEM;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ ak5702 = kzalloc(sizeof(struct ak5702_priv), GFP_KERNEL);
+ if (ak5702 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = ak5702;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ i2c_set_clientdata(client, codec);
+ codec->control_data = client;
+
+ codec->dev = &client->dev;
+ codec->name = "AK5702";
+ codec->owner = THIS_MODULE;
+ codec->read = ak5702_read_reg_cache;
+ codec->write = ak5702_write;
+ codec->set_bias_level = ak5702_set_bias_level;
+ codec->dai = &ak5702_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = ARRAY_SIZE(ak5702_reg);
+ codec->reg_cache = (void *)&ak5702_reg;
+ if (codec->reg_cache == NULL)
+ return -ENOMEM;
+
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->hw_read = (hw_read_t) i2c_master_recv;
+
+ ak5702_codec = codec;
+ ak5702_dai.dev = &client->dev;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_dai(&ak5702_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static __devexit int ak5702_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ struct ak5702_priv *ak5702 = codec->private_data;
+
+ snd_soc_unregister_dai(&ak5702_dai);
+ snd_soc_unregister_codec(codec);
+ kfree(codec);
+ kfree(ak5702);
+ ak5702_codec = NULL;
+ return 0;
+}
+
+static const struct i2c_device_id ak5702_i2c_id[] = {
+ {"ak5702-i2c", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, ak5702_i2c_id);
+
+static struct i2c_driver ak5702_i2c_driver = {
+ .driver = {
+ .name = "ak5702-i2c",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak5702_i2c_probe,
+ .remove = __devexit_p(ak5702_i2c_remove),
+ .id_table = ak5702_i2c_id,
+};
+#endif
+
+static int ak5702_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ if (codec->control_data)
+ ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&ak5702_i2c_driver);
+#endif
+ kfree(codec->private_data);
+ kfree(codec);
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ak5702 = {
+ .probe = ak5702_probe,
+ .remove = ak5702_remove,
+ .suspend = ak5702_suspend,
+ .resume = ak5702_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak5702);
+
+static int __init ak5702_modinit(void)
+{
+ return i2c_add_driver(&ak5702_i2c_driver);
+}
+module_init(ak5702_modinit);
+
+static void __exit ak5702_exit(void)
+{
+ i2c_del_driver(&ak5702_i2c_driver);
+}
+module_exit(ak5702_exit);
+
+MODULE_DESCRIPTION("Soc AK5702 driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak5702.h b/sound/soc/codecs/ak5702.h
new file mode 100644
index 000000000000..97af6e45e498
--- /dev/null
+++ b/sound/soc/codecs/ak5702.h
@@ -0,0 +1,130 @@
+/*
+ * ak5702.h -- AK5702 Soc Audio driver
+ *
+ * Copyright 2009 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK5702_H
+#define _AK5702_H
+
+/* AK5702 register space */
+
+#define AK5702_PM1 0x00
+#define AK5702_PLL1 0x01
+#define AK5702_SIG1 0x02
+#define AK5702_MICG1 0x03
+#define AK5702_FMT1 0x04
+#define AK5702_FS1 0x05
+#define AK5702_CLK1 0x06
+#define AK5702_VOL1 0x07
+#define AK5702_LVOL1 0x08
+#define AK5702_RVOL1 0x09
+#define AK5702_TIMER1 0x0a
+#define AK5702_ALC11 0x0b
+#define AK5702_ALC12 0x0c
+#define AK5702_MODE11 0x0d
+#define AK5702_MODE12 0x0e
+#define AK5702_MODE13 0x0f
+
+#define AK5702_PM2 0x10
+#define AK5702_PLL2 0x11
+#define AK5702_SIG2 0x12
+#define AK5702_MICG2 0x13
+#define AK5702_FMT2 0x14
+#define AK5702_FS2 0x15
+#define AK5702_CLK2 0x16
+#define AK5702_VOL2 0x17
+#define AK5702_LVOL2 0x18
+#define AK5702_RVOL2 0x19
+#define AK5702_TIMER2 0x1a
+#define AK5702_ALC21 0x1b
+#define AK5702_ALC22 0x1c
+#define AK5702_MODE21 0x1d
+#define AK5702_MODE22 0x1e
+
+#define AK5702_CACHEREGNUM 0x1F
+
+#define AK5702_PM1_PMADAL 0x01
+#define AK5702_PM1_PMADAR 0x02
+#define AK5702_PM1_PMVCM 0x04
+#define AK5702_PM2_PMADBL 0x01
+#define AK5702_PM2_PMADBR 0x02
+
+#define AK5702_PLL1_POWERDOWN 0x0
+#define AK5702_PLL1_POWERUP 0x01
+#define AK5702_PLL1_MASTER 0x02
+#define AK5702_PLL1_SLAVE 0x0
+#define AK5702_PLL1_11289600 0x10
+#define AK5702_PLL1_12000000 0x24
+#define AK5702_PLL1_12288000 0x14
+#define AK5702_PLL1_19200000 0x20
+
+#define AK5702_SIG1_L_LIN1 0x0
+#define AK5702_SIG1_L_LIN2 0x01
+#define AK5702_SIG1_R_RIN1 0x0
+#define AK5702_SIG1_R_RIN2 0x02
+#define AK5702_SIG1_PMMPA 0x10
+#define AK5702_SIG2_L_LIN3 0x0
+#define AK5702_SIG2_L_LIN4 0x01
+#define AK5702_SIG2_R_RIN3 0x0
+#define AK5702_SIG2_R_RIN4 0x02
+#define AK5702_SIG2_PMMPB 0x10
+
+#define AK5702_MICG1_INIT 0x0
+#define AK5702_MICG2_INIT 0x0
+
+#define AK5702_FMT1_I2S 0x23
+#define AK5702_FMT1_MSB 0x22
+#define AK5702_FMT2_STEREO 0x20
+#define AK5702_FS1_BCKO_32FS 0x10
+#define AK5702_FS1_BCKO_64FS 0x20
+#define AK5702_CLK1_PS_256FS 0x0
+#define AK5702_CLK1_PS_128FS 0x01
+#define AK5702_CLK1_PS_64FS 0x02
+#define AK5702_CLK1_PS_32FS 0x03
+#define AK5702_VOL1_IVOLAC 0x01
+#define AK5702_VOL2_IVOLBC 0x01
+#define AK5702_LVOL1_INIT 0x91
+#define AK5702_RVOL1_INIT 0x91
+#define AK5702_LVOL2_INIT 0x91
+#define AK5702_RVOL2_INIT 0x91
+
+#define AK5702_PLL1_PM_MASK 0x01
+#define AK5702_PLL1_MODE_MASK 0x02
+#define AK5702_PLL1_PLL_MASK 0x3c
+#define AK5702_FS1_BCKO_MASK 0x30
+#define AK5702_FS1_FS_MASK 0x0f
+#define AK5702_CLK1_PS_MASK 0x03
+
+/* clock divider id */
+#define AK5702_BCLK_CLKDIV 0
+#define AK5702_MCLK_CLKDIV 1
+
+/* bit clock div values */
+#define AK5702_BCLK_DIV_32 0
+#define AK5702_BCLK_DIV_64 1
+
+/* m clock div values */
+#define AK5702_MCLK_DIV_32 0
+#define AK5702_MCLK_DIV_64 1
+#define AK5702_MCLK_DIV_128 2
+#define AK5702_MCLK_DIV_256 3
+
+/* PLL master and slave modes */
+#define AK5702_PLL_POWERDOWN 0
+#define AK5702_PLL_MASTER 1
+#define AK5702_PLL_SLAVE 2
+
+struct ak5702_setup_data {
+ int i2c_bus;
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai ak5702_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ak5702;
+
+#endif
diff --git a/sound/soc/codecs/bluetooth.c b/sound/soc/codecs/bluetooth.c
new file mode 100644
index 000000000000..2d4c13b4f0af
--- /dev/null
+++ b/sound/soc/codecs/bluetooth.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file bluetooth.c
+ * @brief Driver for bluetooth PCM interface
+ *
+ * @ingroup Sound
+ */
+
+#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 <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#define BLUETOOTH_RATES SNDRV_PCM_RATE_8000
+
+#define BLUETOOTH_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai bt_dai = {
+ .name = "bluetooth",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BLUETOOTH_RATES,
+ .formats = BLUETOOTH_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = BLUETOOTH_RATES,
+ .formats = BLUETOOTH_FORMATS,
+ },
+};
+EXPORT_SYMBOL_GPL(bt_dai);
+
+static int bt_init(struct snd_soc_device *socdev)
+{
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int ret = 0;
+
+ codec->name = "bluetooth";
+ codec->owner = THIS_MODULE;
+ codec->dai = &bt_dai;
+ codec->num_dai = 1;
+
+ snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ pr_err("failed to create bluetooth pcms\n");
+ return ret;
+ }
+
+ ret = snd_soc_init_card(socdev);
+ strcpy(codec->card->id, "bluetooth");
+
+ if (ret < 0) {
+ pr_err("bluetooth: failed to register card\n");
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ return ret;
+ }
+ return 0;
+}
+
+static struct snd_soc_device *bt_socdev;
+
+static int bt_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int ret = 0;
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ socdev->card->codec = codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ bt_socdev = socdev;
+
+ ret = bt_init(socdev);
+ if (ret < 0) {
+ pr_err("Bluetooth codec initialisation failed\n");
+ kfree(codec);
+ }
+
+ return ret;
+}
+
+/* power down chip */
+static int bt_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ kfree(codec);
+
+ return 0;
+}
+
+static int bt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int bt_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_bt = {
+ .probe = bt_probe,
+ .remove = bt_remove,
+ .suspend = bt_suspend,
+ .resume = bt_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_bt);
+
+static int __init bluetooth_modinit(void)
+{
+ return snd_soc_register_dai(&bt_dai);
+}
+
+module_init(bluetooth_modinit);
+
+static void __exit bluetooth_exit(void)
+{
+ snd_soc_unregister_dai(&bt_dai);
+}
+
+module_exit(bluetooth_exit);
+
+MODULE_DESCRIPTION("ASoC bluetooth codec driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42888.c b/sound/soc/codecs/cs42888.c
new file mode 100644
index 000000000000..e9288cc9a1ca
--- /dev/null
+++ b/sound/soc/codecs/cs42888.c
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "cs42888.h"
+
+#define CS42888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/* CS42888 registers addresses */
+#define CS42888_CHIPID 0x01 /* Chip ID */
+#define CS42888_PWRCTL 0x02 /* Power Control */
+#define CS42888_MODE 0x03 /* Functional Mode */
+#define CS42888_FORMAT 0x04 /* Interface Formats */
+#define CS42888_ADCCTL 0x05 /* ADC Control */
+#define CS42888_TRANS 0x06 /* Transition Control */
+#define CS42888_MUTE 0x07 /* Mute Control */
+#define CS42888_VOLAOUT1 0x08 /* Volume Control AOUT1*/
+#define CS42888_VOLAOUT2 0x09 /* Volume Control AOUT2*/
+#define CS42888_VOLAOUT3 0x0A /* Volume Control AOUT3*/
+#define CS42888_VOLAOUT4 0x0B /* Volume Control AOUT4*/
+#define CS42888_VOLAOUT5 0x0C /* Volume Control AOUT5*/
+#define CS42888_VOLAOUT6 0x0D /* Volume Control AOUT6*/
+#define CS42888_VOLAOUT7 0x0E /* Volume Control AOUT7*/
+#define CS42888_VOLAOUT8 0x0F /* Volume Control AOUT8*/
+#define CS42888_DACINV 0x10 /* DAC Channel Invert */
+#define CS42888_VOLAIN1 0x11 /* Volume Control AIN1 */
+#define CS42888_VOLAIN2 0x12 /* Volume Control AIN2 */
+#define CS42888_VOLAIN3 0x13 /* Volume Control AIN3 */
+#define CS42888_VOLAIN4 0x14 /* Volume Control AIN4 */
+#define CS42888_ADCINV 0x17 /* ADC Channel Invert */
+#define CS42888_STATUSCTL 0x18 /* Status Control */
+#define CS42888_STATUS 0x19 /* Status */
+#define CS42888_STATUSMASK 0x1A /* Status Mask */
+
+#define CS42888_FIRSTREG 0x01
+#define CS42888_LASTREG 0x1A
+#define CS42888_NUMREGS (CS42888_LASTREG - CS42888_FIRSTREG + 1)
+#define CS42888_I2C_INCR 0x80
+
+/* Bit masks for the CS42888 registers */
+#define CS42888_CHIPID_ID_MASK 0xF0
+#define CS42888_CHIPID_REV 0x0F
+#define CS42888_PWRCTL_PDN_ADC2_OFFSET 6
+#define CS42888_PWRCTL_PDN_ADC1_OFFSET 5
+#define CS42888_PWRCTL_PDN_DAC4_OFFSET 4
+#define CS42888_PWRCTL_PDN_DAC3_OFFSET 3
+#define CS42888_PWRCTL_PDN_DAC2_OFFSET 2
+#define CS42888_PWRCTL_PDN_DAC1_OFFSET 1
+#define CS42888_PWRCTL_PDN_OFFSET 0
+#define CS42888_PWRCTL_PDN_ADC2_MASK (1 << CS42888_PWRCTL_PDN_ADC2_OFFSET)
+#define CS42888_PWRCTL_PDN_ADC1_MASK (1 << CS42888_PWRCTL_PDN_ADC1_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC4_MASK (1 << CS42888_PWRCTL_PDN_DAC4_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC3_MASK (1 << CS42888_PWRCTL_PDN_DAC3_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC2_MASK (1 << CS42888_PWRCTL_PDN_DAC2_OFFSET)
+#define CS42888_PWRCTL_PDN_DAC1_MASK (1 << CS42888_PWRCTL_PDN_DAC1_OFFSET)
+#define CS42888_PWRCTL_PDN_MASK (1 << CS42888_PWRCTL_PDN_OFFSET)
+
+#define CS42888_MODE_SPEED_MASK 0xF0
+#define CS42888_MODE_1X 0x00
+#define CS42888_MODE_2X 0x50
+#define CS42888_MODE_4X 0xA0
+#define CS42888_MODE_SLAVE 0xF0
+#define CS42888_MODE_DIV_MASK 0x0E
+#define CS42888_MODE_DIV1 0x00
+#define CS42888_MODE_DIV2 0x02
+#define CS42888_MODE_DIV3 0x04
+#define CS42888_MODE_DIV4 0x06
+#define CS42888_MODE_DIV5 0x08
+
+#define CS42888_FORMAT_FREEZE_OFFSET 7
+#define CS42888_FORMAT_AUX_DIF_OFFSET 6
+#define CS42888_FORMAT_DAC_DIF_OFFSET 3
+#define CS42888_FORMAT_ADC_DIF_OFFSET 0
+#define CS42888_FORMAT_FREEZE_MASK (1 << CS42888_FORMAT_FREEZE_OFFSET)
+#define CS42888_FORMAT_AUX_DIF_MASK (1 << CS42888_FORMAT_AUX_DIF_OFFSET)
+#define CS42888_FORMAT_DAC_DIF_MASK (7 << CS42888_FORMAT_DAC_DIF_OFFSET)
+#define CS42888_FORMAT_ADC_DIF_MASK (7 << CS42888_FORMAT_ADC_DIF_OFFSET)
+
+#define CS42888_TRANS_DAC_SNGVOL_OFFSET 7
+#define CS42888_TRANS_DAC_SZC_OFFSET 5
+#define CS42888_TRANS_AMUTE_OFFSET 4
+#define CS42888_TRANS_MUTE_ADC_SP_OFFSET 3
+#define CS42888_TRANS_ADC_SNGVOL_OFFSET 2
+#define CS42888_TRANS_ADC_SZC_OFFSET 0
+#define CS42888_TRANS_DAC_SNGVOL_MASK (1 << CS42888_TRANS_DAC_SNGVOL_OFFSET)
+#define CS42888_TRANS_DAC_SZC_MASK (3 << CS42888_TRANS_DAC_SZC_OFFSET)
+#define CS42888_TRANS_AMUTE_MASK (1 << CS42888_TRANS_AMUTE_OFFSET)
+#define CS42888_TRANS_MUTE_ADC_SP_MASK (1 << CS42888_TRANS_MUTE_ADC_SP_OFFSET)
+#define CS42888_TRANS_ADC_SNGVOL_MASK (1 << CS42888_TRANS_ADC_SNGVOL_OFFSET)
+#define CS42888_TRANS_ADC_SZC_MASK (3 << CS42888_TRANS_ADC_SZC_OFFSET)
+
+#define CS42888_MUTE_AOUT8 (0x1 << 7)
+#define CS42888_MUTE_AOUT7 (0x1 << 6)
+#define CS42888_MUTE_AOUT6 (0x1 << 5)
+#define CS42888_MUTE_AOUT5 (0x1 << 4)
+#define CS42888_MUTE_AOUT4 (0x1 << 3)
+#define CS42888_MUTE_AOUT3 (0x1 << 2)
+#define CS42888_MUTE_AOUT2 (0x1 << 1)
+#define CS42888_MUTE_AOUT1 (0x1 << 0)
+#define CS42888_MUTE_ALL (CS42888_MUTE_AOUT1 | CS42888_MUTE_AOUT2 | \
+ CS42888_MUTE_AOUT3 | CS42888_MUTE_AOUT4 | \
+ CS42888_MUTE_AOUT5 | CS42888_MUTE_AOUT6 | \
+ CS42888_MUTE_AOUT7 | CS42888_MUTE_AOUT8)
+
+#define DIF_LEFT_J 0
+#define DIF_I2S 1
+#define DIF_RIGHT_J 2
+#define DIF_TDM 6
+
+/* Private data for the CS42888 */
+struct cs42888_private {
+ struct snd_soc_codec codec;
+ u8 reg_cache[CS42888_NUMREGS];
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+ unsigned int slave_mode;
+ unsigned int manual_mute;
+ struct regulator *regulator_vsd;
+};
+
+static struct i2c_client *cs42888_i2c_client;
+
+int cs42888_read_reg(unsigned int reg, u8 *value)
+{
+ s32 retval;
+ retval = i2c_smbus_read_byte_data(cs42888_i2c_client, reg);
+ if (retval < 0) {
+ pr_err("%s:read reg errorr:reg=%x,val=%x\n",
+ __func__, reg, retval);
+ return -1;
+ } else {
+ *value = (u8) retval;
+ return 0;
+ }
+}
+
+int cs42888_write_reg(unsigned int reg, u8 value)
+{
+ if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value) < 0) {
+ pr_err("%s:write reg errorr:reg=%x,val=%x\n",
+ __func__, reg, value);
+ return -1;
+ }
+ return 0;
+}
+/**
+ * cs42888_fill_cache - pre-fill the CS42888 register cache.
+ * @codec: the codec for this CS42888
+ *
+ * This function fills in the CS42888 register cache by reading the register
+ * values from the hardware.
+ *
+ * This CS42888 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS42888 never updates
+ * the register values, so we won't have a cache coherency problem.
+ *
+ * We use the auto-increment feature of the CS42888 to read all registers in
+ * one shot.
+ */
+static int cs42888_fill_cache(struct snd_soc_codec *codec)
+{
+ u8 *cache = codec->reg_cache;
+ struct i2c_client *i2c_client = codec->control_data;
+ s32 length;
+
+ length = i2c_smbus_read_i2c_block_data(i2c_client,
+ CS42888_FIRSTREG | CS42888_I2C_INCR, CS42888_NUMREGS, cache);
+
+ if (length != CS42888_NUMREGS) {
+ dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
+ i2c_client->addr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * cs42888_read_reg_cache - read from the CS42888 register cache.
+ * @codec: the codec for this CS42888
+ * @reg: the register to read
+ *
+ * This function returns the value for a given register. It reads only from
+ * the register cache, not the hardware itself.
+ *
+ * This CS42888 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS42888 never updates
+ * the register values, so we won't have a cache coherency problem.
+ */
+static u8 cs42888_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG))
+ return -EIO;
+
+ return cache[reg - CS42888_FIRSTREG];
+}
+
+/**
+ * cs42888_i2c_write - write to a CS42888 register via the I2C bus.
+ * @codec: the codec for this CS42888
+ * @reg: the register to write
+ * @value: the value to write to the register
+ *
+ * This function writes the given value to the given CS42888 register, and
+ * also updates the register cache.
+ *
+ * Note that we don't use the hw_write function pointer of snd_soc_codec.
+ * That's because it's too clunky: the hw_write_t prototype does not match
+ * i2c_smbus_write_byte_data(), and it's just another layer of overhead.
+ */
+static int cs42888_i2c_write(struct snd_soc_codec *codec, unsigned int reg,
+ u8 value)
+{
+ u8 *cache = codec->reg_cache;
+
+ if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG))
+ return -EIO;
+
+ /* Only perform an I2C operation if the new value is different */
+ if (cache[reg - CS42888_FIRSTREG] != value) {
+ if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value)
+ < 0) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return -EIO;
+ }
+
+ /* We've written to the hardware, so update the cache */
+ cache[reg - CS42888_FIRSTREG] = value;
+ }
+
+ return 0;
+}
+
+#ifdef CS42888_DEBUG
+static void dump_reg(struct snd_soc_codec *codec)
+{
+ int i, reg;
+ int ret;
+ printk(KERN_DEBUG "dump begin\n");
+ printk(KERN_DEBUG "reg value in cache\n");
+ for (i = 0; i < CS42888_NUMREGS; i++)
+ printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]);
+
+ printk(KERN_DEBUG "real reg value\n");
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ pr_err("failed to fill register cache\n");
+ return ret;
+ }
+ for (i = 0; i < CS42888_NUMREGS; i++)
+ printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]);
+
+ printk(KERN_DEBUG "dump end\n");
+}
+#else
+static void dump_reg(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+/* -127.5dB to 0dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+/* -64dB to 24dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 1);
+
+static int cs42888_out_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* Now write again with the volume update bit set */
+ val = cs42888_read_reg_cache(codec, reg);
+ ret = cs42888_i2c_write(codec, reg, val);
+
+ val = cs42888_read_reg_cache(codec, reg2);
+ ret = cs42888_i2c_write(codec, reg2, val);
+ return 0;
+}
+
+int cs42888_info_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int max = mc->max;
+ int min = mc->min;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = max-min;
+ return 0;
+}
+
+int cs42888_get_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int min = mc->min;
+ int val = cs42888_read_reg_cache(codec, reg);
+
+ ucontrol->value.integer.value[0] =
+ ((signed char)(val))-min;
+
+ val = cs42888_read_reg_cache(codec, reg2);
+ ucontrol->value.integer.value[1] =
+ ((signed char)(val))-min;
+ return 0;
+}
+
+int cs42888_put_volsw_s8(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned int reg = mc->reg;
+ unsigned int reg2 = mc->rreg;
+ int min = mc->min;
+ unsigned short val;
+ int ret;
+
+ val = (ucontrol->value.integer.value[0]+min);
+ ret = cs42888_i2c_write(codec, reg, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ val = ((ucontrol->value.integer.value[1]+min));
+ ret = cs42888_i2c_write(codec, reg2, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#define SOC_CS42888_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+ xinvert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = snd_soc_get_volsw_2r, \
+ .put = cs42888_out_vu, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, \
+ .rreg = reg_right, \
+ .shift = xshift, \
+ .max = xmax, \
+ .invert = xinvert} \
+}
+
+#define SOC_CS42888_DOUBLE_R_S8_TLV(xname, reg_left, reg_right, xmin, xmax, \
+ tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = cs42888_info_volsw_s8, .get = cs42888_get_volsw_s8, \
+ .put = cs42888_put_volsw_s8, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = reg_left, \
+ .rreg = reg_right, \
+ .min = xmin, \
+ .max = xmax} \
+}
+
+static const char *cs42888_adcfilter[] = { "None", "High Pass" };
+static const char *cs42888_dacinvert[] = { "Disabled", "Enabled" };
+static const char *cs42888_adcinvert[] = { "Disabled", "Enabled" };
+static const char *cs42888_dacamute[] = { "Disabled", "AutoMute" };
+static const char *cs42888_dac_sngvol[] = { "Disabled", "Enabled" };
+static const char *cs42888_dac_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+static const char *cs42888_mute_adc[] = { "UnMute", "Mute" };
+static const char *cs42888_adc_sngvol[] = { "Disabled", "Enabled" };
+static const char *cs42888_adc_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+static const char *cs42888_dac_dem[] = { "No-De-Emphasis", "De-Emphasis" };
+static const char *cs42888_adc_single[] = { "Differential", "Single-Ended" };
+
+static const struct soc_enum cs42888_enum[] = {
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 7, 2, cs42888_adcfilter),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 0, 1, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 2, 3, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 4, 5, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_DACINV, 6, 7, 2, cs42888_dacinvert),
+ SOC_ENUM_DOUBLE(CS42888_ADCINV, 0, 1, 2, cs42888_adcinvert),
+ SOC_ENUM_DOUBLE(CS42888_ADCINV, 2, 3, 2, cs42888_adcinvert),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 4, 2, cs42888_dacamute),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 7, 2, cs42888_dac_sngvol),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 5, 4, cs42888_dac_szc),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 3, 2, cs42888_mute_adc),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 2, 2, cs42888_adc_sngvol),
+ SOC_ENUM_SINGLE(CS42888_TRANS, 0, 4, cs42888_adc_szc),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 5, 2, cs42888_dac_dem),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 4, 2, cs42888_adc_single),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 3, 2, cs42888_adc_single),
+};
+
+static const struct snd_kcontrol_new cs42888_snd_controls[] = {
+SOC_CS42888_DOUBLE_R_TLV("DAC1 Playback Volume",
+ CS42888_VOLAOUT1,
+ CS42888_VOLAOUT2,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC2 Playback Volume",
+ CS42888_VOLAOUT3,
+ CS42888_VOLAOUT4,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC3 Playback Volume",
+ CS42888_VOLAOUT5,
+ CS42888_VOLAOUT6,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_TLV("DAC4 Playback Volume",
+ CS42888_VOLAOUT7,
+ CS42888_VOLAOUT8,
+ 0, 0xff, 1, dac_tlv),
+SOC_CS42888_DOUBLE_R_S8_TLV("ADC1 Capture Volume",
+ CS42888_VOLAIN1,
+ CS42888_VOLAIN2,
+ -128, 48, adc_tlv),
+SOC_CS42888_DOUBLE_R_S8_TLV("ADC2 Capture Volume",
+ CS42888_VOLAIN3,
+ CS42888_VOLAIN4,
+ -128, 48, adc_tlv),
+SOC_ENUM("ADC High-Pass Filter Switch", cs42888_enum[0]),
+SOC_ENUM("DAC1 Invert Switch", cs42888_enum[1]),
+SOC_ENUM("DAC2 Invert Switch", cs42888_enum[2]),
+SOC_ENUM("DAC3 Invert Switch", cs42888_enum[3]),
+SOC_ENUM("DAC4 Invert Switch", cs42888_enum[4]),
+SOC_ENUM("ADC1 Invert Switch", cs42888_enum[5]),
+SOC_ENUM("ADC2 Invert Switch", cs42888_enum[6]),
+SOC_ENUM("DAC Auto Mute Switch", cs42888_enum[7]),
+SOC_ENUM("DAC Single Volume Control Switch", cs42888_enum[8]),
+SOC_ENUM("DAC Soft Ramp and Zero Cross Control Switch", cs42888_enum[9]),
+SOC_ENUM("Mute ADC Serial Port Switch", cs42888_enum[10]),
+SOC_ENUM("ADC Single Volume Control Switch", cs42888_enum[11]),
+SOC_ENUM("ADC Soft Ramp and Zero Cross Control Switch", cs42888_enum[12]),
+SOC_ENUM("DAC Deemphasis Switch", cs42888_enum[13]),
+SOC_ENUM("ADC1 Single Ended Mode Switch", cs42888_enum[14]),
+SOC_ENUM("ADC2 Single Ended Mode Switch", cs42888_enum[15]),
+};
+
+
+static const struct snd_soc_dapm_widget cs42888_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", CS42888_PWRCTL, 1, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", CS42888_PWRCTL, 2, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", CS42888_PWRCTL, 3, 1),
+SND_SOC_DAPM_DAC("DAC4", "Playback", CS42888_PWRCTL, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("AOUT1L"),
+SND_SOC_DAPM_OUTPUT("AOUT1R"),
+SND_SOC_DAPM_OUTPUT("AOUT2L"),
+SND_SOC_DAPM_OUTPUT("AOUT2R"),
+SND_SOC_DAPM_OUTPUT("AOUT3L"),
+SND_SOC_DAPM_OUTPUT("AOUT3R"),
+SND_SOC_DAPM_OUTPUT("AOUT4L"),
+SND_SOC_DAPM_OUTPUT("AOUT4R"),
+
+SND_SOC_DAPM_ADC("ADC1", "Capture", CS42888_PWRCTL, 5, 1),
+SND_SOC_DAPM_ADC("ADC2", "Capture", CS42888_PWRCTL, 6, 1),
+
+SND_SOC_DAPM_INPUT("AIN1L"),
+SND_SOC_DAPM_INPUT("AIN1R"),
+SND_SOC_DAPM_INPUT("AIN2L"),
+SND_SOC_DAPM_INPUT("AIN2R"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Playback */
+ { "AOUT1L", NULL, "DAC1" },
+ { "AOUT1R", NULL, "DAC1" },
+
+ { "AOUT2L", NULL, "DAC2" },
+ { "AOUT2R", NULL, "DAC2" },
+
+ { "AOUT3L", NULL, "DAC3" },
+ { "AOUT3R", NULL, "DAC3" },
+
+ { "AOUT4L", NULL, "DAC4" },
+ { "AOUT4R", NULL, "DAC4" },
+
+ /* Capture */
+ { "ADC1", NULL, "AIN1L" },
+ { "ADC1", NULL, "AIN1R" },
+
+ { "ADC2", NULL, "AIN2L" },
+ { "ADC2", NULL, "AIN2R" },
+};
+
+
+static int ca42888_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, cs42888_dapm_widgets,
+ ARRAY_SIZE(cs42888_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/**
+ * struct cs42888_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ * this ratio
+ * @mclk: the Ratio Select bits to set in the Mode Control register for this
+ * ratio
+ *
+ * The data for this chart is taken from Table 10 of the CS42888 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Functional Mode register.
+ * It is also used by cs42888_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS42888 currently supports.
+ *
+ * @speed_mode is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ */
+struct cs42888_mode_ratios {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk;
+};
+
+static struct cs42888_mode_ratios cs42888_mode_ratios[] = {
+ {64, CS42888_MODE_4X, CS42888_MODE_DIV1},
+ {96, CS42888_MODE_4X, CS42888_MODE_DIV2},
+ {128, CS42888_MODE_2X, CS42888_MODE_DIV1},
+ {192, CS42888_MODE_2X, CS42888_MODE_DIV2},
+ {256, CS42888_MODE_1X, CS42888_MODE_DIV1},
+ {384, CS42888_MODE_2X, CS42888_MODE_DIV4},
+ {512, CS42888_MODE_1X, CS42888_MODE_DIV3},
+ {768, CS42888_MODE_1X, CS42888_MODE_DIV4},
+ {1024, CS42888_MODE_1X, CS42888_MODE_DIV5}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS42888 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs42888_mode_ratios)
+
+/**
+ * cs42888_set_dai_sysclk - determine the CS42888 samples rates.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
+ *
+ */
+static int cs42888_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_private *cs42888 = codec->private_data;
+
+ cs42888->mclk = freq;
+
+ return 0;
+}
+
+/**
+ * cs42888_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J. The CS42888 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs42888_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_private *cs42888 = codec->private_data;
+ int ret = 0;
+ u8 val;
+ val = cs42888_read_reg_cache(codec, CS42888_FORMAT);
+ val &= ~CS42888_FORMAT_DAC_DIF_MASK;
+ val &= ~CS42888_FORMAT_ADC_DIF_MASK;
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= DIF_LEFT_J << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_LEFT_J << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= DIF_I2S << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_I2S << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val |= DIF_RIGHT_J << CS42888_FORMAT_DAC_DIF_OFFSET;
+ val |= DIF_RIGHT_J << CS42888_FORMAT_ADC_DIF_OFFSET;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = cs42888_i2c_write(codec, CS42888_FORMAT, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ val = cs42888_read_reg_cache(codec, CS42888_MODE);
+ /* set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42888->slave_mode = 1;
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= CS42888_MODE_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42888->slave_mode = 0;
+ break;
+ default:
+ /* all other modes are unsupported by the hardware */
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = cs42888_i2c_write(codec, CS42888_MODE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * cs42888_hw_params - program the CS42888 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+
+ * @dai: the SOC DAI (ignored)
+ *
+ * This function programs the hardware with the values provided.
+ * Specifically, the sample rate and the data format.
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs42888_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct cs42888_private *cs42888 = codec->private_data;
+ int ret;
+ unsigned int i;
+ unsigned int rate;
+ unsigned int ratio;
+ u8 val;
+
+ rate = params_rate(params); /* Sampling rate, in Hz */
+ ratio = cs42888->mclk / rate; /* MCLK/LRCK ratio */
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ if (cs42888_mode_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == NUM_MCLK_RATIOS) {
+ /* We did not find a matching ratio */
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ if (!cs42888->slave_mode) {
+ val = cs42888_read_reg_cache(codec, CS42888_MODE);
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= cs42888_mode_ratios[i].speed_mode;
+ val &= ~CS42888_MODE_DIV_MASK;
+ val |= cs42888_mode_ratios[i].mclk;
+ } else {
+ val = cs42888_read_reg_cache(codec, CS42888_MODE);
+ val &= ~CS42888_MODE_SPEED_MASK;
+ val |= CS42888_MODE_SLAVE;
+ }
+ ret = cs42888_i2c_write(codec, CS42888_MODE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ /* Out of low power state */
+ val = cs42888_read_reg_cache(codec, CS42888_PWRCTL);
+ val &= ~CS42888_PWRCTL_PDN_MASK;
+ ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ /* Unmute all the channels */
+ val = cs42888_read_reg_cache(codec, CS42888_MUTE);
+ val &= ~CS42888_MUTE_ALL;
+ ret = cs42888_i2c_write(codec, CS42888_MUTE, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ pr_err("failed to fill register cache\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * cs42888_shutdown - cs42888 enters into low power mode again.
+ * @substream: the audio stream
+ * @dai: the SOC DAI (ignored)
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static void cs42888_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int ret;
+ u8 val;
+
+ /* Mute all the channels */
+ val = cs42888_read_reg_cache(codec, CS42888_MUTE);
+ val |= CS42888_MUTE_ALL;
+ ret = cs42888_i2c_write(codec, CS42888_MUTE, val);
+ if (ret < 0)
+ pr_err("i2c write failed\n");
+
+ /* Enter low power state */
+ val = cs42888_read_reg_cache(codec, CS42888_PWRCTL);
+ val |= CS42888_PWRCTL_PDN_MASK;
+ ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val);
+ if (ret < 0)
+ pr_err("i2c write failed\n");
+}
+
+/*
+ * cs42888_codec - global variable to store codec for the ASoC probe function
+ *
+ * If struct i2c_driver had a private_data field, we wouldn't need to use
+ * cs42888_codec. This is the only way to pass the codec structure from
+ * cs42888_i2c_probe() to cs42888_probe(). Unfortunately, there is no good
+ * way to synchronize these two functions. cs42888_i2c_probe() can be called
+ * multiple times before cs42888_probe() is called even once. So for now, we
+ * also only allow cs42888_i2c_probe() to be run once. That means that we do
+ * not support more than one cs42888 device in the system, at least for now.
+ */
+static struct snd_soc_codec *cs42888_codec;
+
+static struct snd_soc_dai_ops cs42888_dai_ops = {
+ .set_fmt = cs42888_set_dai_fmt,
+ .set_sysclk = cs42888_set_dai_sysclk,
+ .hw_params = cs42888_hw_params,
+ .shutdown = cs42888_shutdown,
+};
+
+struct snd_soc_dai cs42888_dai = {
+ .name = "CS42888",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_176400),
+ .formats = CS42888_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_176400),
+ .formats = CS42888_FORMATS,
+ },
+ .ops = &cs42888_dai_ops,
+};
+EXPORT_SYMBOL_GPL(cs42888_dai);
+
+/**
+ * cs42888_probe - ASoC probe function
+ * @pdev: platform device
+ *
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
+ */
+static int cs42888_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = cs42888_codec;
+ int ret;
+
+ /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
+ socdev->card->codec = codec;
+
+ /* Register PCMs */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ /* Add the non-DAPM controls */
+ ret = snd_soc_add_controls(codec, cs42888_snd_controls,
+ ARRAY_SIZE(cs42888_snd_controls));
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to add controls\n");
+ goto error_free_pcms;
+ }
+
+ /* Add DAPM controls */
+ ca42888_add_widgets(codec);
+
+ /* And finally, register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ goto error_free_pcms;
+ }
+
+ return 0;
+
+error_free_pcms:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return ret;
+}
+
+/**
+ * cs42888_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs42888_probe().
+ */
+static int cs42888_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+};
+
+
+/**
+ * cs42888_i2c_probe - initialize the I2C interface of the CS42888
+ * @i2c_client: the I2C client object
+ * @id: the I2C device ID (ignored)
+ *
+ * This function is called whenever the I2C subsystem finds a device that
+ * matches the device ID given via a prior call to i2c_add_driver().
+ */
+static int cs42888_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_codec *codec;
+ struct cs42888_private *cs42888;
+ int ret;
+ struct regulator *regulator_vsd;
+ u8 val;
+
+ if (cs42888_codec) {
+ dev_err(&i2c_client->dev,
+ "Multiple CS42888 devices not supported\n");
+ return -ENOMEM;
+ }
+
+ cs42888_i2c_client = i2c_client;
+
+ /* Allocate enough space for the snd_soc_codec structure
+ and our private data together. */
+ cs42888 = kzalloc(sizeof(struct cs42888_private), GFP_KERNEL);
+ if (!cs42888) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ /* hold on reset */
+ gpio_cs42888_pdwn(1);
+
+ regulator_vsd = regulator_get(&i2c_client->dev, "VSD");
+ if (!IS_ERR(regulator_vsd))
+ cs42888->regulator_vsd = regulator_vsd;
+
+ if (cs42888->regulator_vsd) {
+ regulator_set_voltage(cs42888->regulator_vsd,
+ 2800000, 2800000);
+ if (regulator_enable(cs42888->regulator_vsd) != 0) {
+ pr_err("%s:VSD set voltage error\n", __func__);
+ } else {
+ dev_dbg(&i2c_client->dev,
+ "%s:io set voltage ok\n", __func__);
+ }
+ }
+
+ msleep(1);
+ /* out of reset state */
+ gpio_cs42888_pdwn(0);
+
+ /* Verify that we have a CS42888 */
+ ret = cs42888_read_reg(CS42888_CHIPID, &val);
+ if (ret < 0) {
+ pr_err("Device with ID register %x is not a CS42888", val);
+ return -ENODEV;
+ }
+ /* The top four bits of the chip ID should be 0000. */
+ if ((val & CS42888_CHIPID_ID_MASK) != 0x00) {
+ dev_err(&i2c_client->dev, "device is not a CS42888\n");
+ return -ENODEV;
+ }
+
+ dev_info(&i2c_client->dev, "found device at i2c address %X\n",
+ i2c_client->addr);
+ dev_info(&i2c_client->dev, "hardware revision %X\n", val & 0xF);
+
+ codec = &cs42888->codec;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ i2c_set_clientdata(i2c_client, cs42888);
+ codec->control_data = i2c_client;
+
+ codec->dev = &i2c_client->dev;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = cs42888;
+ codec->name = "CS42888";
+ codec->owner = THIS_MODULE;
+ codec->read = cs42888_read_reg_cache;
+ codec->write = cs42888_i2c_write;
+ codec->dai = &cs42888_dai;
+ codec->num_dai = 1;
+ codec->reg_cache = cs42888->reg_cache;
+ codec->reg_cache_size = ARRAY_SIZE(cs42888->reg_cache);
+
+ /* The I2C interface is set up, so pre-fill our register cache */
+ ret = cs42888_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to fill register cache\n");
+ goto error_free_codec;
+ }
+
+ /* Enter low power state */
+ val = cs42888_read_reg_cache(codec, CS42888_PWRCTL);
+ val |= CS42888_PWRCTL_PDN_MASK;
+ ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "i2c write failed\n");
+ return ret;
+ }
+
+ /* Disable auto-mute */
+ val = cs42888_read_reg_cache(codec, CS42888_TRANS);
+ val &= ~CS42888_TRANS_AMUTE_MASK;
+ ret = cs42888_i2c_write(codec, CS42888_TRANS, val);
+ if (ret < 0) {
+ pr_err("i2c write failed\n");
+ return ret;
+ }
+
+ cs42888_dai.dev = &i2c_client->dev;
+
+ cs42888_codec = codec;
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(&i2c_client->dev,
+ "Failed to register codec: %d\n", ret);
+ goto error_free_codec;
+ }
+
+ ret = snd_soc_register_dai(&cs42888_dai);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to register DAIe\n");
+ goto error_codec;
+ }
+
+ return 0;
+
+error_codec:
+ snd_soc_unregister_codec(codec);
+error_free_codec:
+ kfree(cs42888);
+ cs42888_codec = NULL;
+ cs42888_dai.dev = NULL;
+
+ return ret;
+}
+
+/**
+ * cs42888_i2c_remove - remove an I2C device
+ * @i2c_client: the I2C client object
+ *
+ * This function is the counterpart to cs42888_i2c_probe().
+ */
+static int cs42888_i2c_remove(struct i2c_client *i2c_client)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(i2c_client);
+
+ snd_soc_unregister_dai(&cs42888_dai);
+ snd_soc_unregister_codec(&cs42888->codec);
+ kfree(cs42888);
+ cs42888_codec = NULL;
+ cs42888_dai.dev = NULL;
+
+ return 0;
+}
+
+/*
+ * cs42888_i2c_id - I2C device IDs supported by this driver
+ */
+static struct i2c_device_id cs42888_i2c_id[] = {
+ {"cs42888", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42888_i2c_id);
+
+#ifdef CONFIG_PM
+
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs42888_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = &cs42888->codec;
+ int reg = snd_soc_read(codec, CS42888_PWRCTL) | CS42888_PWRCTL_PDN_MASK;
+
+ return snd_soc_write(codec, CS42888_PWRCTL, reg);
+}
+
+static int cs42888_i2c_resume(struct i2c_client *client)
+{
+ struct cs42888_private *cs42888 = i2c_get_clientdata(client);
+ struct snd_soc_codec *codec = &cs42888->codec;
+ int reg;
+
+ /* In case the device was put to hard reset during sleep, we need to
+ * wait 500ns here before any I2C communication. */
+ ndelay(500);
+
+ /* first restore the entire register cache ... */
+ for (reg = CS42888_FIRSTREG; reg <= CS42888_LASTREG; reg++) {
+ u8 val = snd_soc_read(codec, reg);
+
+ if (i2c_smbus_write_byte_data(client, reg, val)) {
+ dev_err(codec->dev, "i2c write failed\n");
+ return -EIO;
+ }
+ }
+
+ /* ... then disable the power-down bits */
+ reg = snd_soc_read(codec, CS42888_PWRCTL);
+ reg &= ~CS42888_PWRCTL_PDN_MASK;
+
+ return snd_soc_write(codec, CS42888_PWRCTL, reg);
+}
+#else
+#define cs42888_i2c_suspend NULL
+#define cs42888_i2c_resume NULL
+#endif /* CONFIG_PM */
+
+/*
+ * cs42888_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs42888_i2c_driver = {
+ .driver = {
+ .name = "cs42888",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs42888_i2c_id,
+ .probe = cs42888_i2c_probe,
+ .remove = cs42888_i2c_remove,
+ .suspend = cs42888_i2c_suspend,
+ .resume = cs42888_i2c_resume,
+};
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+struct snd_soc_codec_device soc_codec_device_cs42888 = {
+ .probe = cs42888_probe,
+ .remove = cs42888_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs42888);
+
+static int __init cs42888_init(void)
+{
+ pr_info("Cirrus Logic CS42888 ALSA SoC Codec Driver\n");
+
+ return i2c_add_driver(&cs42888_i2c_driver);
+}
+module_init(cs42888_init);
+
+static void __exit cs42888_exit(void)
+{
+ i2c_del_driver(&cs42888_i2c_driver);
+}
+module_exit(cs42888_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Cirrus Logic CS42888 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42888.h b/sound/soc/codecs/cs42888.h
new file mode 100644
index 000000000000..a2c8562c17c5
--- /dev/null
+++ b/sound/soc/codecs/cs42888.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _CS42888_H
+#define _CS42888_H
+
+/*
+ * The ASoC codec DAI structure for the CS42888. Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_dai cs42888_dai;
+
+/*
+ * The ASoC codec device structure for the CS42888. Assign this structure
+ * to the .codec_dev field of your machine driver's snd_soc_device
+ * structure.
+ */
+extern struct snd_soc_codec_device soc_codec_device_cs42888;
+
+extern void gpio_cs42888_pdwn(int pdwn);
+#endif
diff --git a/sound/soc/codecs/mxs-adc-codec.c b/sound/soc/codecs/mxs-adc-codec.c
new file mode 100644
index 000000000000..f8f10619731a
--- /dev/null
+++ b/sound/soc/codecs/mxs-adc-codec.c
@@ -0,0 +1,1220 @@
+/*
+ * ALSA codec for Freescale MXS ADC/DAC Audio
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <asm/dma.h>
+
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+#include <mach/regs-rtc.h>
+
+#include "mxs-adc-codec.h"
+
+#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */
+
+#ifndef BF
+#define BF(value, field) (((value) << BP_##field) & BM_##field)
+#endif
+
+#define BM_RTC_PERSISTENT0_RELEASE_GND BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG)
+
+#define REGS_RTC_BASE (IO_ADDRESS(RTC_PHYS_ADDR))
+
+struct mxs_codec_priv {
+ struct clk *clk;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * ALSA API
+ */
+static void __iomem *adc_regmap[] = {
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_STAT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACDEBUG,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT0,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT1,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_VERSION,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_STAT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCDEBUG,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_DATA,
+};
+
+static u16 mxs_audio_regs[ADC_REGNUM];
+
+static u8 dac_volumn_control_word[] = {
+ 0x37, 0x5e, 0x7e, 0x8e,
+ 0x9e, 0xae, 0xb6, 0xbe,
+ 0xc6, 0xce, 0xd6, 0xde,
+ 0xe6, 0xee, 0xf6, 0xfe,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit ADC/DAC registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static void mxs_codec_write_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < ADC_REGNUM)
+ cache[reg] = value;
+}
+
+static int mxs_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= ADC_REGNUM)
+ return -EIO;
+
+ mxs_codec_write_cache(codec, reg, value);
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, adc_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int mxs_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= ADC_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+static unsigned int mxs_codec_read_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ADC_REGNUM)
+ return -EINVAL;
+ return cache[reg];
+}
+
+static void mxs_codec_sync_reg_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ for (reg = 0; reg < ADC_REGNUM; reg += 1)
+ mxs_codec_write_cache(codec, reg,
+ mxs_codec_read(codec, reg));
+}
+
+static int mxs_codec_restore_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int cached_val, hw_val;
+
+ cached_val = mxs_codec_read_cache(codec, reg);
+ hw_val = mxs_codec_read(codec, reg);
+
+ if (hw_val != cached_val)
+ return mxs_codec_write(codec, reg, cached_val);
+
+ return 0;
+}
+
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xf;
+ return 0;
+}
+
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg, l, r;
+ int i;
+
+ reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME);
+
+ l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT;
+ r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT;
+ /*Left channel */
+ i = 0;
+ while (i < 16) {
+ if (l == dac_volumn_control_word[i]) {
+ ucontrol->value.integer.value[0] = i;
+ break;
+ }
+ i++;
+ }
+ if (i == 16)
+ ucontrol->value.integer.value[0] = i;
+ /*Right channel */
+ i = 0;
+ while (i < 16) {
+ if (r == dac_volumn_control_word[i]) {
+ ucontrol->value.integer.value[1] = i;
+ break;
+ }
+ i++;
+ }
+ if (i == 16)
+ ucontrol->value.integer.value[1] = i;
+
+ return 0;
+}
+
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg, l, r;
+ int i;
+
+ i = ucontrol->value.integer.value[0];
+ l = dac_volumn_control_word[i];
+ /*Get dac volume for left channel */
+ reg = BF(l, AUDIOOUT_DACVOLUME_VOLUME_LEFT);
+
+ i = ucontrol->value.integer.value[1];
+ r = dac_volumn_control_word[i];
+ /*Get dac volume for right channel */
+ reg = reg | BF(r, AUDIOOUT_DACVOLUME_VOLUME_RIGHT);
+
+ /*Clear left/right dac volume */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT |
+ BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR);
+ __raw_writel(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+
+ return 0;
+}
+
+static int pga_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Prepare powering up HP and SPEAKER output */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+ __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND,
+ REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET);
+ msleep(100);
+ break;
+ case SND_SOC_DAPM_POST_PMU:
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND,
+ REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR);
+ break;
+ }
+ return 0;
+}
+
+static int adc_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND,
+ REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET);
+ msleep(100);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND,
+ REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR);
+ break;
+ }
+ return 0;
+}
+
+static const char *mxs_codec_adc_input_sel[] =
+ { "Mic", "Line In 1", "Head Phone", "Line In 2" };
+
+static const char *mxs_codec_hp_output_sel[] = { "DAC Out", "Line In 1" };
+
+static const char *mxs_codec_adc_3d_sel[] =
+ { "Off", "Low", "Medium", "High" };
+
+static const struct soc_enum mxs_codec_enum[] = {
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, mxs_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, mxs_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, mxs_codec_hp_output_sel),
+ SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, mxs_codec_adc_3d_sel),
+};
+
+/* Codec controls */
+static const struct snd_kcontrol_new mxs_snd_controls[] = {
+ /* Playback Volume */
+ {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+
+ SOC_DOUBLE_R("DAC Playback Switch",
+ DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1),
+ SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1),
+
+ /* Capture Volume */
+ SOC_DOUBLE_R("ADC Capture Volume",
+ ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0),
+ SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0),
+ SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1),
+ SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0),
+
+ /* Virtual 3D effect */
+ SOC_ENUM("3D effect", mxs_codec_enum[3]),
+};
+
+/* Left ADC Mux */
+static const struct snd_kcontrol_new mxs_left_adc_controls =
+SOC_DAPM_ENUM("Route", mxs_codec_enum[0]);
+
+/* Right ADC Mux */
+static const struct snd_kcontrol_new mxs_right_adc_controls =
+SOC_DAPM_ENUM("Route", mxs_codec_enum[1]);
+
+/* Head Phone Mux */
+static const struct snd_kcontrol_new mxs_hp_controls =
+SOC_DAPM_ENUM("Route", mxs_codec_enum[2]);
+
+static const struct snd_soc_dapm_widget mxs_codec_widgets[] = {
+
+ SND_SOC_DAPM_ADC_E("ADC", "Capture", DAC_PWRDN_L, 8, 1, adc_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &mxs_left_adc_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &mxs_right_adc_controls),
+ SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0,
+ &mxs_hp_controls),
+ SND_SOC_DAPM_PGA_E("HP AMP", DAC_PWRDN_L, 0, 1, NULL, 0, pga_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_PGA("SPEAKER AMP", DAC_PWRDN_H, 8, 1, NULL, 0),
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+ SND_SOC_DAPM_INPUT("MIC"),
+
+ SND_SOC_DAPM_OUTPUT("SPEAKER"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Mic", "MIC"},
+ {"Left ADC Mux", "Line In 1", "LINE1L"},
+ {"Left ADC Mux", "Line In 2", "LINE2L"},
+ {"Left ADC Mux", "Head Phone", "HPL"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Mic", "MIC"},
+ {"Right ADC Mux", "Line In 1", "LINE1R"},
+ {"Right ADC Mux", "Line In 2", "LINE2R"},
+ {"Right ADC Mux", "Head Phone", "HPR"},
+
+ /* ADC */
+ {"ADC", NULL, "Left ADC Mux"},
+ {"ADC", NULL, "Right ADC Mux"},
+
+ /* HP Mux */
+ {"HP Mux", "DAC Out", "DAC"},
+ {"HP Mux", "Line In 1", "LINE1L"},
+ {"HP Mux", "Line In 1", "LINE1R"},
+
+ /* HP amp */
+ {"HP AMP", NULL, "HP Mux"},
+ /* HP output */
+ {"HPR", NULL, "HP AMP"},
+ {"HPL", NULL, "HP AMP"},
+
+ /* Speaker amp */
+ {"SPEAKER AMP", NULL, "DAC"},
+ {"SPEAKER", NULL, "SPEAKER AMP"},
+};
+
+static int mxs_codec_add_widgets(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ snd_soc_dapm_new_controls(codec, mxs_codec_widgets,
+ ARRAY_SIZE(mxs_codec_widgets));
+
+ if (ret) {
+ dev_err(codec->dev, "dapm control register failed\n");
+ return ret;
+ }
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ if (ret) {
+ dev_err(codec->dev, "DAPM route register failed\n");
+ return ret;
+ }
+
+ return snd_soc_dapm_new_widgets(codec);
+}
+
+struct dac_srr {
+ u32 rate;
+ u32 basemult;
+ u32 src_hold;
+ u32 src_int;
+ u32 src_frac;
+};
+
+static struct dac_srr srr_values[] = {
+ {192000, 0x4, 0x0, 0x0F, 0x13FF},
+ {176400, 0x4, 0x0, 0x11, 0x0037},
+ {128000, 0x4, 0x0, 0x17, 0x0E00},
+ {96000, 0x2, 0x0, 0x0F, 0x13FF},
+ {88200, 0x2, 0x0, 0x11, 0x0037},
+ {64000, 0x2, 0x0, 0x17, 0x0E00},
+ {48000, 0x1, 0x0, 0x0F, 0x13FF},
+ {44100, 0x1, 0x0, 0x11, 0x0037},
+ {32000, 0x1, 0x0, 0x17, 0x0E00},
+ {24000, 0x1, 0x1, 0x0F, 0x13FF},
+ {22050, 0x1, 0x1, 0x11, 0x0037},
+ {16000, 0x1, 0x1, 0x17, 0x0E00},
+ {12000, 0x1, 0x3, 0x0F, 0x13FF},
+ {11025, 0x1, 0x3, 0x11, 0x0037},
+ {8000, 0x1, 0x3, 0x17, 0x0E00}
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int mxs_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 src_hold = 0;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ dev_warn(socdev->dev, "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ src_hold = srr_values[i].src_hold;
+
+ srr_value =
+ BF(srr_values[i].basemult, AUDIOOUT_DACSRR_BASEMULT) |
+ BF(srr_values[i].src_int, AUDIOOUT_DACSRR_SRC_INT) |
+ BF(srr_values[i].src_frac, AUDIOOUT_DACSRR_SRC_FRAC) |
+ BF(src_hold, AUDIOOUT_DACSRR_SRC_HOLD);
+
+ if (playback)
+ __raw_writel(srr_value,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR);
+ else
+ __raw_writel(srr_value,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ break;
+
+ default:
+ dev_warn(socdev->dev, "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+
+ }
+
+ return 0;
+}
+
+static int mxs_codec_dig_mute(struct snd_soc_dai *dai, int mute)
+{
+ int l, r;
+ int ll, rr;
+ u32 reg, reg1, reg2;
+ u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT;
+
+ if (mute) {
+ reg = __raw_readl(REGS_AUDIOOUT_BASE + \
+ HW_AUDIOOUT_DACVOLUME);
+
+ reg1 = reg & ~BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT;
+ reg1 = reg1 & ~BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT;
+
+ l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT;
+ r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT;
+
+ /* fade out dac vol */
+ while ((l > DAC_VOLUME_MIN) || (r > DAC_VOLUME_MIN)) {
+ l -= 0x8;
+ r -= 0x8;
+ ll = l > DAC_VOLUME_MIN ? l : DAC_VOLUME_MIN;
+ rr = r > DAC_VOLUME_MIN ? r : DAC_VOLUME_MIN;
+ reg2 = reg1 | BF_AUDIOOUT_DACVOLUME_VOLUME_LEFT(ll)
+ | BF_AUDIOOUT_DACVOLUME_VOLUME_RIGHT(rr);
+ __raw_writel(reg2,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME);
+ msleep(1);
+ }
+
+ __raw_writel(dac_mask,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ reg = reg | dac_mask;
+ __raw_writel(reg,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME);
+ } else
+ __raw_writel(dac_mask,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR);
+
+ return 0;
+}
+
+static int mxs_codec_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ pr_debug("dapm level %d\n", level);
+ switch (level) {
+ case SND_SOC_BIAS_ON: /* full On */
+ if (codec->bias_level == SND_SOC_BIAS_ON)
+ break;
+ break;
+
+ case SND_SOC_BIAS_PREPARE: /* partial On */
+ if (codec->bias_level == SND_SOC_BIAS_PREPARE)
+ break;
+ /* Set Capless mode */
+ __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
+ break;
+
+ case SND_SOC_BIAS_STANDBY: /* Off, with power */
+ if (codec->bias_level == SND_SOC_BIAS_STANDBY)
+ break;
+ /* Unset Capless mode */
+ __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ break;
+
+ case SND_SOC_BIAS_OFF: /* Off, without power */
+ if (codec->bias_level == SND_SOC_BIAS_OFF)
+ break;
+ /* Unset Capless mode */
+ __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+/*
+ * Codec initialization
+ */
+#define VAG_BASE_VALUE ((1400/2 - 625)/25)
+static void mxs_codec_dac_set_vag(void)
+{
+ u32 refctrl_val = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL);
+
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL);
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ);
+ refctrl_val |= BF(VAG_BASE_VALUE, AUDIOOUT_REFCTRL_VAG_VAL) |
+ BM_AUDIOOUT_REFCTRL_ADJ_VAG |
+ BF(0xF, AUDIOOUT_REFCTRL_ADC_REFVAL) |
+ BM_AUDIOOUT_REFCTRL_ADJ_ADC |
+ BF(0x3, AUDIOOUT_REFCTRL_VBG_ADJ) | BM_AUDIOOUT_REFCTRL_RAISE_REF;
+
+ __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL);
+}
+
+static bool mxs_codec_dac_is_capless()
+{
+ if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN)
+ & BM_AUDIOOUT_PWRDN_CAPLESS) == 0)
+ return false;
+ else
+ return true;
+}
+static void mxs_codec_dac_arm_short_cm(bool bShort)
+{
+ __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_CM),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_CM_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ if (bShort)
+ __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_CM),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+}
+static void mxs_codec_dac_arm_short_lr(bool bShort)
+{
+ __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_LR),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ if (bShort)
+ __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_LR),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+}
+static void mxs_codec_dac_set_short_trip_level(u8 u8level)
+{
+ __raw_writel(__raw_readl(REGS_AUDIOOUT_BASE +
+ HW_AUDIOOUT_ANACTRL)
+ & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJL)
+ & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJR)
+ | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJL)
+ | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJR),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL);
+}
+static void mxs_codec_dac_arm_short(bool bLatchCM, bool bLatchLR)
+{
+ if (bLatchCM) {
+ if (mxs_codec_dac_is_capless())
+ mxs_codec_dac_arm_short_cm(true);
+ } else
+ mxs_codec_dac_arm_short_cm(false);
+
+ if (bLatchLR)
+ mxs_codec_dac_arm_short_lr(true);
+ else
+ mxs_codec_dac_arm_short_lr(false);
+
+}
+static void
+mxs_codec_dac_power_on(struct mxs_codec_priv *mxs_adc)
+{
+ /* Ungate DAC clocks */
+ __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+
+ /* Arm headphone LR short protect */
+ mxs_codec_dac_set_short_trip_level(0);
+ mxs_codec_dac_arm_short(false, true);
+
+ /* Update DAC volume over zero crossings */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_EN_ZCD,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ /* Mute DAC */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+
+ /* Update HP volume over zero crossings */
+ __raw_writel(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ /* Mute HP output */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ /* Mute speaker amp */
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ /* Enable the audioout */
+ __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+}
+
+static void
+mxs_codec_dac_power_down(struct mxs_codec_priv *mxs_adc)
+{
+ /* Disable the audioout */
+ __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ /* Disable class AB */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+
+ /* Set hold to ground */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ /* Mute HP output */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ /* Power down HP output */
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Mute speaker amp */
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ /* Power down speaker amp */
+ __raw_writel(BM_AUDIOOUT_PWRDN_SPEAKER,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Mute DAC */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ /* Power down DAC */
+ __raw_writel(BM_AUDIOOUT_PWRDN_DAC,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Gate DAC clocks */
+ __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+}
+
+static void
+mxs_codec_adc_power_on(struct mxs_codec_priv *mxs_adc)
+{
+ u32 reg;
+
+ /* Ungate ADC clocks */
+ __raw_writel(BM_AUDIOIN_CTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+
+ /* Unmute ADC channels */
+ __raw_writel(BM_AUDIOIN_ADCVOL_MUTE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+
+ /*
+ * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared.
+ * They aren't presented in the datasheet, so this is hardcode.
+ */
+ __raw_writel(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME_CLR);
+
+ /* Set the Input channel gain 3dB */
+ __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_LEFT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_RIGHT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+ __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Select default input - Microphone */
+ __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_LEFT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_RIGHT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BF
+ (BV_AUDIOIN_ADCVOL_SELECT__MIC,
+ AUDIOIN_ADCVOL_SELECT_LEFT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+ __raw_writel(BF
+ (BV_AUDIOIN_ADCVOL_SELECT__MIC,
+ AUDIOIN_ADCVOL_SELECT_RIGHT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Supply bias voltage to microphone */
+ __raw_writel(BF(1, AUDIOIN_MICLINE_MIC_RESISTOR),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+ __raw_writel(BM_AUDIOIN_MICLINE_MIC_SELECT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+ __raw_writel(BF(1, AUDIOIN_MICLINE_MIC_GAIN),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+ __raw_writel(BF(7, AUDIOIN_MICLINE_MIC_BIAS),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+
+ /* Set max ADC volume */
+ reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+ reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_LEFT);
+ reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_RIGHT);
+ __raw_writel(reg, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+}
+
+static void
+mxs_codec_adc_power_down(struct mxs_codec_priv *mxs_adc)
+{
+ /* Mute ADC channels */
+ __raw_writel(BM_AUDIOIN_ADCVOL_MUTE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Power Down ADC */
+ __raw_writel(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Gate ADC clocks */
+ __raw_writel(BM_AUDIOIN_CTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_SET);
+
+ /* Disable bias voltage to microphone */
+ __raw_writel(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+}
+
+static void mxs_codec_dac_enable(struct mxs_codec_priv *mxs_adc)
+{
+ /* Move DAC codec out of reset */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+
+ /* Reduce analog power */
+ __raw_writel(BM_AUDIOOUT_TEST_HP_I1_ADJ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_CLR);
+ __raw_writel(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_LOW_PWR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_BIAS_CTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR);
+ __raw_writel(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR);
+
+ /* Set Vag value */
+ mxs_codec_dac_set_vag();
+
+ /* Power on DAC codec */
+ mxs_codec_dac_power_on(mxs_adc);
+}
+
+static void mxs_codec_dac_disable(struct mxs_codec_priv *mxs_adc)
+{
+ mxs_codec_dac_power_down(mxs_adc);
+}
+
+static void mxs_codec_adc_enable(struct mxs_codec_priv *mxs_adc)
+{
+ /* Move ADC codec out of reset */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ /* Power on ADC codec */
+ mxs_codec_adc_power_on(mxs_adc);
+}
+
+static void mxs_codec_adc_disable(struct mxs_codec_priv *mxs_adc)
+{
+ mxs_codec_adc_power_down(mxs_adc);
+}
+
+static void mxs_codec_startup(struct snd_soc_codec *codec)
+{
+ struct mxs_codec_priv *mxs_adc = codec->private_data;
+
+ /* Soft reset DAC block */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL)
+ & BM_AUDIOOUT_CTRL_CLKGATE)){
+ }
+
+ /* Soft reset ADC block */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ while (!(__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL)
+ & BM_AUDIOIN_CTRL_CLKGATE)){
+ }
+
+ mxs_codec_dac_enable(mxs_adc);
+ mxs_codec_adc_enable(mxs_adc);
+
+ /*Sync regs and cache */
+ mxs_codec_sync_reg_cache(codec);
+
+ snd_soc_add_controls(codec, mxs_snd_controls,
+ ARRAY_SIZE(mxs_snd_controls));
+
+ mxs_codec_add_widgets(codec);
+}
+
+static void mxs_codec_stop(struct snd_soc_codec *codec)
+{
+ struct mxs_codec_priv *mxs_adc = codec->private_data;
+ mxs_codec_dac_disable(mxs_adc);
+ mxs_codec_adc_disable(mxs_adc);
+}
+
+#define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops mxs_dai_ops = {
+ .hw_params = mxs_codec_hw_params,
+ .digital_mute = mxs_codec_dig_mute,
+};
+
+struct snd_soc_dai mxs_codec_dai = {
+ .name = "mxs adc/dac",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_ADC_RATES,
+ .formats = MXS_ADC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_ADC_RATES,
+ .formats = MXS_ADC_FORMATS,
+ },
+ .ops = &mxs_dai_ops,
+};
+EXPORT_SYMBOL_GPL(mxs_codec_dai);
+
+static struct snd_soc_codec *mxs_codec;
+
+static int mxs_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ socdev->card->codec = mxs_codec;
+ codec = mxs_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ mxs_codec_startup(codec);
+
+ /* Register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ return ret;
+ }
+ /* Set default bias level*/
+ mxs_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int mxs_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ mxs_codec_stop(codec);
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxs_codec_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct mxs_codec_priv *mxs_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ mxs_adc = codec->private_data;
+
+ mxs_codec_dac_disable(mxs_adc);
+ mxs_codec_adc_disable(mxs_adc);
+ clk_disable(mxs_adc->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int mxs_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct mxs_codec_priv *mxs_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ mxs_adc = codec->private_data;
+ clk_enable(mxs_adc->clk);
+
+ /* Soft reset DAC block */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ while (!
+ (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL)
+ & BM_AUDIOOUT_CTRL_CLKGATE)){
+ }
+
+ /* Soft reset ADC block */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ while (!
+ (__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL)
+ & BM_AUDIOIN_CTRL_CLKGATE)){
+ }
+
+ mxs_codec_dac_enable(mxs_adc);
+ mxs_codec_adc_enable(mxs_adc);
+
+ /*restore registers relevant to amixer controls */
+ mxs_codec_restore_reg(codec, DAC_CTRL_L);
+ mxs_codec_restore_reg(codec, DAC_VOLUME_L);
+ mxs_codec_restore_reg(codec, DAC_VOLUME_H);
+ mxs_codec_restore_reg(codec, DAC_HPVOL_L);
+ mxs_codec_restore_reg(codec, DAC_HPVOL_H);
+ mxs_codec_restore_reg(codec, DAC_SPEAKERCTRL_H);
+ mxs_codec_restore_reg(codec, ADC_VOLUME_L);
+ mxs_codec_restore_reg(codec, ADC_VOLUME_H);
+ mxs_codec_restore_reg(codec, ADC_ADCVOL_L);
+ mxs_codec_restore_reg(codec, ADC_ADCVOL_H);
+ mxs_codec_restore_reg(codec, ADC_MICLINE_L);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define mxs_codec_suspend NULL
+#define mxs_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_codec_dev_mxs = {
+ .probe = mxs_codec_probe,
+ .remove = mxs_codec_remove,
+ .suspend = mxs_codec_suspend,
+ .resume = mxs_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_mxs);
+
+/* codec register, unregister function */
+static int __init mxs_codec_audio_probe(struct platform_device *pdev)
+{
+ struct mxs_codec_priv *mxs_adc;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ dev_info(&pdev->dev,
+ "MXS ADC/DAC Audio Codec \n");
+
+ mxs_adc = kzalloc(sizeof(struct mxs_codec_priv), GFP_KERNEL);
+ if (mxs_adc == NULL)
+ return -ENOMEM;
+
+ codec = &mxs_adc->codec;
+ codec->dev = &pdev->dev;
+ codec->name = "mxs adc/dac";
+ codec->owner = THIS_MODULE;
+ codec->private_data = mxs_adc;
+ codec->read = mxs_codec_read;
+ codec->write = mxs_codec_write;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = mxs_codec_set_bias_level;
+ codec->dai = &mxs_codec_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(mxs_audio_regs) >> 1;
+ codec->reg_cache_step = 1;
+ codec->reg_cache = (void *)&mxs_audio_regs;
+
+ platform_set_drvdata(pdev, mxs_adc);
+
+ mxs_codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* Turn on audio clock */
+ mxs_adc->clk = clk_get(&pdev->dev, "audio");
+ if (IS_ERR(mxs_adc->clk)) {
+ ret = PTR_ERR(mxs_adc->clk);
+ dev_err(&pdev->dev, "%s: Clocks initialization failed\n",
+ __func__);
+ goto clk_err;
+ }
+ clk_enable(mxs_adc->clk);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto card_err;
+ }
+
+ ret = snd_soc_register_dai(&mxs_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec dai\n");
+ goto dai_err;
+ }
+
+ return 0;
+
+dai_err:
+ snd_soc_unregister_codec(codec);
+card_err:
+ clk_disable(mxs_adc->clk);
+ clk_put(mxs_adc->clk);
+clk_err:
+ kfree(mxs_adc);
+ return ret;
+}
+
+static int __devexit mxs_codec_audio_remove(struct platform_device *pdev)
+{
+ struct mxs_codec_priv *mxs_adc = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = &mxs_adc->codec;
+
+ snd_soc_unregister_codec(codec);
+
+ clk_disable(mxs_adc->clk);
+ clk_put(mxs_adc->clk);
+
+ kfree(mxs_adc);
+
+ return 0;
+}
+
+struct platform_driver mxs_codec_audio_driver = {
+ .driver = {
+ .name = "mxs-adc-audio",
+ },
+ .probe = mxs_codec_audio_probe,
+ .remove = __devexit_p(mxs_codec_audio_remove),
+};
+
+static int __init mxs_codec_init(void)
+{
+ return platform_driver_register(&mxs_codec_audio_driver);
+}
+
+static void __exit mxs_codec_exit(void)
+{
+ return platform_driver_unregister(&mxs_codec_audio_driver);
+}
+
+module_init(mxs_codec_init);
+module_exit(mxs_codec_exit);
+
+MODULE_DESCRIPTION("MXS ADC/DAC codec");
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/mxs-adc-codec.h b/sound/soc/codecs/mxs-adc-codec.h
new file mode 100644
index 000000000000..129c1c7bdf66
--- /dev/null
+++ b/sound/soc/codecs/mxs-adc-codec.h
@@ -0,0 +1,87 @@
+/*
+ * ALSA codec for Freescale MXS ADC/DAC Audio
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __MXS_ADC_CODEC_H
+#define __MXS_ADC_CODEC_H
+
+#define DAC_CTRL_L 0
+#define DAC_CTRL_H 1
+#define DAC_STAT_L 2
+#define DAC_STAT_H 3
+#define DAC_SRR_L 4
+#define DAC_VOLUME_L 6
+#define DAC_VOLUME_H 7
+#define DAC_DEBUG_L 8
+#define DAC_DEBUG_H 9
+#define DAC_HPVOL_L 10
+#define DAC_HPVOL_H 11
+#define DAC_PWRDN_L 12
+#define DAC_PWRDN_H 13
+#define DAC_REFCTRL_L 14
+#define DAC_REFCTRL_H 15
+#define DAC_ANACTRL_L 16
+#define DAC_ANACTRL_H 17
+#define DAC_TEST_L 18
+#define DAC_TEST_H 19
+#define DAC_BISTCTRL_L 20
+#define DAC_BISTCTRL_H 21
+#define DAC_BISTSTAT0_L 22
+#define DAC_BISTSTAT0_H 23
+#define DAC_BISTSTAT1_L 24
+#define DAC_BISTSTAT1_H 25
+#define DAC_ANACLKCTRL_L 26
+#define DAC_ANACLKCTRL_H 27
+#define DAC_DATA_L 28
+#define DAC_DATA_H 29
+#define DAC_SPEAKERCTRL_L 30
+#define DAC_SPEAKERCTRL_H 31
+#define DAC_VERSION_L 32
+#define DAC_VERSION_H 33
+#define ADC_CTRL_L 34
+#define ADC_CTRL_H 35
+#define ADC_STAT_L 36
+#define ADC_STAT_H 37
+#define ADC_SRR_L 38
+#define ADC_SRR_H 39
+#define ADC_VOLUME_L 40
+#define ADC_VOLUME_H 41
+#define ADC_DEBUG_L 42
+#define ADC_DEBUG_H 43
+#define ADC_ADCVOL_L 44
+#define ADC_ADCVOL_H 45
+#define ADC_MICLINE_L 46
+#define ADC_MICLINE_H 47
+#define ADC_ANACLKCTRL_L 48
+#define ADC_ANACLKCTRL_H 49
+#define ADC_DATA_L 50
+#define ADC_DATA_H 51
+
+#define ADC_REGNUM 52
+
+#define DAC_VOLUME_MIN 0x37
+#define DAC_VOLUME_MAX 0xFE
+#define ADC_VOLUME_MIN 0x37
+#define ADC_VOLUME_MAX 0xFE
+#define HP_VOLUME_MAX 0x0
+#define HP_VOLUME_MIN 0x7F
+#define LO_VOLUME_MAX 0x0
+#define LO_VOLUME_MIN 0x1F
+
+extern struct snd_soc_dai mxs_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_mxs;
+
+#endif /* __MXS_ADC_CODEC_H */
diff --git a/sound/soc/codecs/mxs_spdif.c b/sound/soc/codecs/mxs_spdif.c
new file mode 100644
index 000000000000..af47dcb547cf
--- /dev/null
+++ b/sound/soc/codecs/mxs_spdif.c
@@ -0,0 +1,454 @@
+/*
+ * ALSA SoC MXS SPDIF codec driver
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif.h by:
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include "mxs_spdif.h"
+
+#define REGS_SPDIF_BASE IO_ADDRESS(SPDIF_PHYS_ADDR)
+
+#define BF(value, field) (((value) << BP_##field) & BM_##field)
+
+struct mxs_codec_priv {
+ struct clk *clk;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * ALSA API
+ */
+static void __iomem *spdif_regmap[] = {
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL,
+ REGS_SPDIF_BASE + HW_SPDIF_STAT,
+ REGS_SPDIF_BASE + HW_SPDIF_FRAMECTRL,
+ REGS_SPDIF_BASE + HW_SPDIF_SRR,
+ REGS_SPDIF_BASE + HW_SPDIF_DEBUG,
+ REGS_SPDIF_BASE + HW_SPDIF_DATA,
+ REGS_SPDIF_BASE + HW_SPDIF_VERSION,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit SPDIF registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static int mxs_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= SPDIF_REGNUM)
+ return -EIO;
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, spdif_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int mxs_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= SPDIF_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+/* Codec controls */
+static const struct snd_kcontrol_new mxs_snd_controls[] = {
+ SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0),
+ SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0),
+ SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0),
+ SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0),
+ SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0),
+ SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0),
+ SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0),
+ SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0),
+ SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0),
+ SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0),
+};
+
+struct spdif_srr {
+ u32 rate;
+ u32 basemult;
+ u32 rate_factor;
+};
+
+static struct spdif_srr srr_values[] = {
+ {96000, 0x2, 0x0BB80},
+ {88200, 0x2, 0x0AC44},
+ {64000, 0x2, 0x07D00},
+ {48000, 0x1, 0x0BB80},
+ {44100, 0x1, 0x0AC44},
+ {32000, 0x1, 0x07D00},
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int mxs_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 basemult;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ printk(KERN_WARNING "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ basemult = srr_values[i].basemult;
+
+ srr_value = BF(basemult, SPDIF_SRR_BASEMULT) |
+ BF(srr_values[i].rate_factor, SPDIF_SRR_RATE);
+
+ if (playback)
+ __raw_writel(srr_value, REGS_SPDIF_BASE + HW_SPDIF_SRR);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ break;
+ default:
+ printk(KERN_WARNING "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+ }
+
+ return 0;
+}
+
+static void mxs_codec_spdif_enable(struct mxs_codec_priv *mxs_spdif)
+{
+ /* Move SPDIF codec out of reset */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+
+ /* Ungate SPDIF clocks */
+ __raw_writel(BM_SPDIF_CTRL_CLKGATE,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+}
+
+static void mxs_codec_spdif_disable(struct mxs_codec_priv *mxs_spdif)
+{
+ /* Gate SPDIF clocks */
+ __raw_writel(BM_SPDIF_CTRL_CLKGATE,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+}
+
+static void mxs_codec_init(struct snd_soc_codec *codec)
+{
+ struct mxs_codec_priv *mxs_spdif = codec->private_data;
+
+ /* Soft reset SPDIF block */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+
+ while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL)
+ & BM_SPDIF_CTRL_CLKGATE))
+ ;
+
+ mxs_codec_spdif_enable(mxs_spdif);
+
+ snd_soc_add_controls(codec, mxs_snd_controls,
+ ARRAY_SIZE(mxs_snd_controls));
+}
+
+static void mxs_codec_exit(struct snd_soc_codec *codec)
+{
+ struct mxs_codec_priv *mxs_spdif = codec->private_data;
+
+ mxs_codec_spdif_disable(mxs_spdif);
+}
+
+struct snd_soc_dai_ops mxs_spdif_codec_dai_ops = {
+ .hw_params = mxs_codec_hw_params,
+};
+
+struct snd_soc_dai mxs_spdif_codec_dai = {
+ .name = "mxs spdif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SPDIF_RATES,
+ .formats = MXS_SPDIF_FORMATS,
+ },
+ .ops = &mxs_spdif_codec_dai_ops,
+};
+EXPORT_SYMBOL_GPL(mxs_spdif_codec_dai);
+
+static struct snd_soc_codec *mxs_spdif_codec;
+
+static int mxs_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ socdev->card->codec = mxs_spdif_codec;
+ codec = mxs_spdif_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ mxs_codec_init(codec);
+
+ /* Register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mxs_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ mxs_codec_exit(codec);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxs_codec_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct mxs_codec_priv *mxs_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ mxs_spdif = codec->private_data;
+
+ mxs_codec_spdif_disable(mxs_spdif);
+ clk_disable(mxs_spdif->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int mxs_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct mxs_codec_priv *mxs_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ mxs_spdif = codec->private_data;
+ clk_enable(mxs_spdif->clk);
+
+ /* Soft reset SPDIF block */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL)
+ & BM_SPDIF_CTRL_CLKGATE))
+ ;
+
+ mxs_codec_spdif_enable(mxs_spdif);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define mxs_codec_suspend NULL
+#define mxs_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_spdif_codec_dev_mxs = {
+ .probe = mxs_codec_probe,
+ .remove = mxs_codec_remove,
+ .suspend = mxs_codec_suspend,
+ .resume = mxs_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_mxs);
+
+static int __init mxs_spdif_probe(struct platform_device *pdev)
+{
+ struct snd_soc_codec *codec;
+ struct mxs_codec_priv *mxs_spdif;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "MXS SPDIF Audio Transmitter\n");
+
+ mxs_spdif = kzalloc(sizeof(struct mxs_codec_priv), GFP_KERNEL);
+ if (mxs_spdif == NULL)
+ return -ENOMEM;
+
+ codec = &mxs_spdif->codec;
+ codec->name = "mxs spdif";
+ codec->owner = THIS_MODULE;
+ codec->private_data = mxs_spdif;
+ codec->read = mxs_codec_read;
+ codec->write = mxs_codec_write;
+ codec->dai = &mxs_spdif_codec_dai;
+ codec->num_dai = 1;
+
+ platform_set_drvdata(pdev, mxs_spdif);
+ mxs_spdif_codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* Turn on audio clock */
+ mxs_spdif->clk = clk_get(&pdev->dev, "spdif");
+ if (IS_ERR(mxs_spdif->clk)) {
+ ret = PTR_ERR(mxs_spdif->clk);
+ dev_err(&pdev->dev, "Clocks initialization failed\n");
+ goto clk_err;
+ }
+ clk_enable(mxs_spdif->clk);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto card_err;
+ }
+
+ ret = snd_soc_register_dai(&mxs_spdif_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto dai_err;
+ }
+
+ return 0;
+
+dai_err:
+ snd_soc_unregister_codec(codec);
+card_err:
+ clk_disable(mxs_spdif->clk);
+ clk_put(mxs_spdif->clk);
+clk_err:
+ kfree(mxs_spdif);
+ return ret;
+}
+
+static int __devexit mxs_spdif_remove(struct platform_device *pdev)
+{
+ struct mxs_codec_priv *mxs_spdif = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = &mxs_spdif->codec;
+
+ snd_soc_unregister_codec(codec);
+
+ clk_disable(mxs_spdif->clk);
+ clk_put(mxs_spdif->clk);
+
+ kfree(mxs_spdif);
+
+ return 0;
+}
+
+struct platform_driver mxs_spdif_driver = {
+ .driver = {
+ .name = "mxs-spdif",
+ },
+ .probe = mxs_spdif_probe,
+ .remove = __devexit_p(mxs_spdif_remove),
+};
+
+static int __init mxs_spdif_init(void)
+{
+ return platform_driver_register(&mxs_spdif_driver);
+}
+
+static void __exit mxs_spdif_exit(void)
+{
+ return platform_driver_unregister(&mxs_spdif_driver);
+}
+
+module_init(mxs_spdif_init);
+module_exit(mxs_spdif_exit);
+
+MODULE_DESCRIPTION("MXS SPDIF transmitter");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/mxs_spdif.h b/sound/soc/codecs/mxs_spdif.h
new file mode 100644
index 000000000000..6bce044d683e
--- /dev/null
+++ b/sound/soc/codecs/mxs_spdif.h
@@ -0,0 +1,172 @@
+/*
+ * ALSA SoC MXS SPDIF codec driver
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif.h by:
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#ifndef __MXS_SPDIF_CODEC_H
+#define __MXS_SPDIF_CODEC_H
+
+#define SPDIF_CTRL_L 0
+#define SPDIF_CTRL_H 1
+#define SPDIF_STAT_L 2
+#define SPDIF_STAT_H 3
+#define SPDIF_FRAMECTRL_L 4
+#define SPDIF_FRAMECTRL_H 5
+#define SPDIF_SRR_L 6
+#define SPDIF_SRR_H 7
+#define SPDIF_DEBUG_L 8
+#define SPDIF_DEBUG_H 9
+#define SPDIF_DATA_L 10
+#define SPDIF_DATA_H 11
+#define SPDIF_VERSION_L 12
+#define SPDIF_VERSION_H 13
+
+#define SPDIF_REGNUM 14
+
+#define HW_SPDIF_CTRL (0x00000000)
+#define HW_SPDIF_CTRL_SET (0x00000004)
+#define HW_SPDIF_CTRL_CLR (0x00000008)
+#define HW_SPDIF_CTRL_TOG (0x0000000c)
+
+#define BM_SPDIF_CTRL_SFTRST 0x80000000
+#define BM_SPDIF_CTRL_CLKGATE 0x40000000
+#define BP_SPDIF_CTRL_RSRVD1 21
+#define BM_SPDIF_CTRL_RSRVD1 0x3FE00000
+#define BF_SPDIF_CTRL_RSRVD1(v) \
+ (((v) << 21) & BM_SPDIF_CTRL_RSRVD1)
+#define BP_SPDIF_CTRL_DMAWAIT_COUNT 16
+#define BM_SPDIF_CTRL_DMAWAIT_COUNT 0x001F0000
+#define BF_SPDIF_CTRL_DMAWAIT_COUNT(v) \
+ (((v) << 16) & BM_SPDIF_CTRL_DMAWAIT_COUNT)
+#define BP_SPDIF_CTRL_RSRVD0 6
+#define BM_SPDIF_CTRL_RSRVD0 0x0000FFC0
+#define BF_SPDIF_CTRL_RSRVD0(v) \
+ (((v) << 6) & BM_SPDIF_CTRL_RSRVD0)
+#define BM_SPDIF_CTRL_WAIT_END_XFER 0x00000020
+#define BM_SPDIF_CTRL_WORD_LENGTH 0x00000010
+#define BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ 0x00000008
+#define BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ 0x00000004
+#define BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN 0x00000002
+#define BM_SPDIF_CTRL_RUN 0x00000001
+
+#define HW_SPDIF_STAT (0x00000010)
+#define HW_SPDIF_STAT_SET (0x00000014)
+#define HW_SPDIF_STAT_CLR (0x00000018)
+#define HW_SPDIF_STAT_TOG (0x0000001c)
+
+#define BM_SPDIF_STAT_PRESENT 0x80000000
+#define BP_SPDIF_STAT_RSRVD1 1
+#define BM_SPDIF_STAT_RSRVD1 0x7FFFFFFE
+#define BF_SPDIF_STAT_RSRVD1(v) \
+ (((v) << 1) & BM_SPDIF_STAT_RSRVD1)
+#define BM_SPDIF_STAT_END_XFER 0x00000001
+
+#define HW_SPDIF_FRAMECTRL (0x00000020)
+#define HW_SPDIF_FRAMECTRL_SET (0x00000024)
+#define HW_SPDIF_FRAMECTRL_CLR (0x00000028)
+#define HW_SPDIF_FRAMECTRL_TOG (0x0000002c)
+
+#define BP_SPDIF_FRAMECTRL_RSRVD2 18
+#define BM_SPDIF_FRAMECTRL_RSRVD2 0xFFFC0000
+#define BF_SPDIF_FRAMECTRL_RSRVD2(v) \
+ (((v) << 18) & BM_SPDIF_FRAMECTRL_RSRVD2)
+#define BM_SPDIF_FRAMECTRL_V_CONFIG 0x00020000
+#define BM_SPDIF_FRAMECTRL_AUTO_MUTE 0x00010000
+#define BM_SPDIF_FRAMECTRL_RSRVD1 0x00008000
+#define BM_SPDIF_FRAMECTRL_USER_DATA 0x00004000
+#define BM_SPDIF_FRAMECTRL_V 0x00002000
+#define BM_SPDIF_FRAMECTRL_L 0x00001000
+#define BM_SPDIF_FRAMECTRL_RSRVD0 0x00000800
+#define BP_SPDIF_FRAMECTRL_CC 4
+#define BM_SPDIF_FRAMECTRL_CC 0x000007F0
+#define BF_SPDIF_FRAMECTRL_CC(v) \
+ (((v) << 4) & BM_SPDIF_FRAMECTRL_CC)
+#define BM_SPDIF_FRAMECTRL_PRE 0x00000008
+#define BM_SPDIF_FRAMECTRL_COPY 0x00000004
+#define BM_SPDIF_FRAMECTRL_AUDIO 0x00000002
+#define BM_SPDIF_FRAMECTRL_PRO 0x00000001
+
+#define HW_SPDIF_SRR (0x00000030)
+#define HW_SPDIF_SRR_SET (0x00000034)
+#define HW_SPDIF_SRR_CLR (0x00000038)
+#define HW_SPDIF_SRR_TOG (0x0000003c)
+
+#define BM_SPDIF_SRR_RSRVD1 0x80000000
+#define BP_SPDIF_SRR_BASEMULT 28
+#define BM_SPDIF_SRR_BASEMULT 0x70000000
+#define BF_SPDIF_SRR_BASEMULT(v) \
+ (((v) << 28) & BM_SPDIF_SRR_BASEMULT)
+#define BP_SPDIF_SRR_RSRVD0 20
+#define BM_SPDIF_SRR_RSRVD0 0x0FF00000
+#define BF_SPDIF_SRR_RSRVD0(v) \
+ (((v) << 20) & BM_SPDIF_SRR_RSRVD0)
+#define BP_SPDIF_SRR_RATE 0
+#define BM_SPDIF_SRR_RATE 0x000FFFFF
+#define BF_SPDIF_SRR_RATE(v) \
+ (((v) << 0) & BM_SPDIF_SRR_RATE)
+
+#define HW_SPDIF_DEBUG (0x00000040)
+#define HW_SPDIF_DEBUG_SET (0x00000044)
+#define HW_SPDIF_DEBUG_CLR (0x00000048)
+#define HW_SPDIF_DEBUG_TOG (0x0000004c)
+
+#define BP_SPDIF_DEBUG_RSRVD1 2
+#define BM_SPDIF_DEBUG_RSRVD1 0xFFFFFFFC
+#define BF_SPDIF_DEBUG_RSRVD1(v) \
+ (((v) << 2) & BM_SPDIF_DEBUG_RSRVD1)
+#define BM_SPDIF_DEBUG_DMA_PREQ 0x00000002
+#define BM_SPDIF_DEBUG_FIFO_STATUS 0x00000001
+
+#define HW_SPDIF_DATA (0x00000050)
+#define HW_SPDIF_DATA_SET (0x00000054)
+#define HW_SPDIF_DATA_CLR (0x00000058)
+#define HW_SPDIF_DATA_TOG (0x0000005c)
+
+#define BP_SPDIF_DATA_HIGH 16
+#define BM_SPDIF_DATA_HIGH 0xFFFF0000
+#define BF_SPDIF_DATA_HIGH(v) \
+ (((v) << 16) & BM_SPDIF_DATA_HIGH)
+#define BP_SPDIF_DATA_LOW 0
+#define BM_SPDIF_DATA_LOW 0x0000FFFF
+#define BF_SPDIF_DATA_LOW(v) \
+ (((v) << 0) & BM_SPDIF_DATA_LOW)
+
+#define HW_SPDIF_VERSION (0x00000060)
+
+#define BP_SPDIF_VERSION_MAJOR 24
+#define BM_SPDIF_VERSION_MAJOR 0xFF000000
+#define BF_SPDIF_VERSION_MAJOR(v) \
+ (((v) << 24) & BM_SPDIF_VERSION_MAJOR)
+#define BP_SPDIF_VERSION_MINOR 16
+#define BM_SPDIF_VERSION_MINOR 0x00FF0000
+#define BF_SPDIF_VERSION_MINOR(v) \
+ (((v) << 16) & BM_SPDIF_VERSION_MINOR)
+#define BP_SPDIF_VERSION_STEP 0
+#define BM_SPDIF_VERSION_STEP 0x0000FFFF
+#define BF_SPDIF_VERSION_STEP(v) \
+ (((v) << 0) & BM_SPDIF_VERSION_STEP)
+
+#define MXS_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define MXS_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+extern struct snd_soc_dai mxs_spdif_codec_dai;
+extern struct snd_soc_codec_device soc_spdif_codec_dev_mxs;
+
+#endif /* __MXS_SPDIF_CODEC_H */
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
new file mode 100644
index 000000000000..e1d5ef73f4d1
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.c
@@ -0,0 +1,1295 @@
+/*
+ * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/hardware.h>
+
+#include "sgtl5000.h"
+
+struct sgtl5000_priv {
+ int sysclk;
+ int master;
+ int fmt;
+ int rev;
+ int lrclk;
+ int capture_channels;
+ int playback_active;
+ int capture_active;
+ struct regulator *reg_vddio;
+ struct regulator *reg_vdda;
+ struct regulator *reg_vddd;
+ int vddio; /* voltage of VDDIO (mv) */
+ int vdda; /* voltage of vdda (mv) */
+ int vddd; /* voltage of vddd (mv), 0 if not connected */
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level);
+
+#define SGTL5000_MAX_CACHED_REG SGTL5000_CHIP_SHORT_CTRL
+static u16 sgtl5000_regs[(SGTL5000_MAX_CACHED_REG >> 1) + 1];
+
+static unsigned int sgtl5000_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ unsigned int offset = reg >> 1;
+ if (offset >= ARRAY_SIZE(sgtl5000_regs))
+ return -EINVAL;
+ pr_debug("r r:%02x,v:%04x\n", reg, cache[offset]);
+ return cache[offset];
+}
+
+static unsigned int sgtl5000_hw_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct i2c_client *client = codec->control_data;
+ int i2c_ret;
+ u16 value;
+ u8 buf0[2], buf1[2];
+ u16 addr = client->addr;
+ u16 flags = client->flags;
+ struct i2c_msg msg[2] = {
+ {addr, flags, 2, buf0},
+ {addr, flags | I2C_M_RD, 2, buf1},
+ };
+
+ buf0[0] = (reg & 0xff00) >> 8;
+ buf0[1] = reg & 0xff;
+ i2c_ret = i2c_transfer(client->adapter, msg, 2);
+ if (i2c_ret < 0) {
+ pr_err("%s: read reg error : Reg 0x%02x\n", __func__, reg);
+ return 0;
+ }
+
+ value = buf1[0] << 8 | buf1[1];
+
+ pr_debug("r r:%02x,v:%04x\n", reg, value);
+ return value;
+}
+
+static unsigned int sgtl5000_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if ((reg == SGTL5000_CHIP_ID) ||
+ (reg == SGTL5000_CHIP_ADCDAC_CTRL) ||
+ (reg == SGTL5000_CHIP_ANA_STATUS) ||
+ (reg > SGTL5000_MAX_CACHED_REG))
+ return sgtl5000_hw_read(codec, reg);
+ else
+ return sgtl5000_read_reg_cache(codec, reg);
+}
+
+static inline void sgtl5000_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ unsigned int offset = reg >> 1;
+ if (offset < ARRAY_SIZE(sgtl5000_regs))
+ cache[offset] = value;
+}
+
+static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct i2c_client *client = codec->control_data;
+ u16 addr = client->addr;
+ u16 flags = client->flags;
+ u8 buf[4];
+ int i2c_ret;
+ struct i2c_msg msg = { addr, flags, 4, buf };
+
+ sgtl5000_write_reg_cache(codec, reg, value);
+ pr_debug("w r:%02x,v:%04x\n", reg, value);
+ buf[0] = (reg & 0xff00) >> 8;
+ buf[1] = reg & 0xff;
+ buf[2] = (value & 0xff00) >> 8;
+ buf[3] = value & 0xff;
+
+ i2c_ret = i2c_transfer(client->adapter, &msg, 1);
+ if (i2c_ret < 0) {
+ pr_err("%s: write reg error : Reg 0x%02x = 0x%04x\n",
+ __func__, reg, value);
+ return -EIO;
+ }
+
+ return i2c_ret;
+}
+
+static void sgtl5000_sync_reg_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ for (reg = 0; reg <= SGTL5000_MAX_CACHED_REG; reg += 2)
+ sgtl5000_write_reg_cache(codec, reg,
+ sgtl5000_hw_read(codec, reg));
+}
+
+static int sgtl5000_restore_reg(struct snd_soc_codec *codec, unsigned int reg)
+{
+ unsigned int cached_val, hw_val;
+
+ cached_val = sgtl5000_read_reg_cache(codec, reg);
+ hw_val = sgtl5000_hw_read(codec, reg);
+
+ if (hw_val != cached_val)
+ return sgtl5000_write(codec, reg, cached_val);
+
+ return 0;
+}
+
+static int all_reg[] = {
+ SGTL5000_CHIP_ID,
+ SGTL5000_CHIP_DIG_POWER,
+ SGTL5000_CHIP_CLK_CTRL,
+ SGTL5000_CHIP_I2S_CTRL,
+ SGTL5000_CHIP_SSS_CTRL,
+ SGTL5000_CHIP_ADCDAC_CTRL,
+ SGTL5000_CHIP_DAC_VOL,
+ SGTL5000_CHIP_PAD_STRENGTH,
+ SGTL5000_CHIP_ANA_ADC_CTRL,
+ SGTL5000_CHIP_ANA_HP_CTRL,
+ SGTL5000_CHIP_ANA_CTRL,
+ SGTL5000_CHIP_LINREG_CTRL,
+ SGTL5000_CHIP_REF_CTRL,
+ SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_CHIP_LINE_OUT_CTRL,
+ SGTL5000_CHIP_LINE_OUT_VOL,
+ SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_CHIP_PLL_CTRL,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_CHIP_ANA_STATUS,
+ SGTL5000_CHIP_SHORT_CTRL,
+};
+
+#ifdef DEBUG
+static void dump_reg(struct snd_soc_codec *codec)
+{
+ int i, reg;
+ printk(KERN_DEBUG "dump begin\n");
+ for (i = 0; i < 21; i++) {
+ reg = sgtl5000_read(codec, all_reg[i]);
+ printk(KERN_DEBUG "d r %04x, v %04x\n", all_reg[i], reg);
+ }
+ printk(KERN_DEBUG "dump end\n");
+}
+#else
+static void dump_reg(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static const char *adc_mux_text[] = {
+ "MIC_IN", "LINE_IN"
+};
+
+static const char *dac_mux_text[] = {
+ "DAC", "LINE_IN"
+};
+
+static const struct soc_enum adc_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text);
+
+static const struct soc_enum dac_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text);
+
+static const struct snd_kcontrol_new adc_mux =
+SOC_DAPM_ENUM("ADC Mux", adc_enum);
+
+static const struct snd_kcontrol_new dac_mux =
+SOC_DAPM_ENUM("DAC Mux", dac_enum);
+
+static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("LINE_IN"),
+ SND_SOC_DAPM_INPUT("MIC_IN"),
+
+ SND_SOC_DAPM_OUTPUT("HP_OUT"),
+ SND_SOC_DAPM_OUTPUT("LINE_OUT"),
+
+ SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_CTRL, 4, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_CTRL, 8, 1, NULL, 0),
+
+ SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &adc_mux),
+ SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &dac_mux),
+
+ SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_DIG_POWER, 6, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"ADC Mux", "LINE_IN", "LINE_IN"},
+ {"ADC Mux", "MIC_IN", "MIC_IN"},
+ {"ADC", NULL, "ADC Mux"},
+ {"DAC Mux", "DAC", "DAC"},
+ {"DAC Mux", "LINE_IN", "LINE_IN"},
+ {"LO", NULL, "DAC"},
+ {"HP", NULL, "DAC Mux"},
+ {"LINE_OUT", NULL, "LO"},
+ {"HP_OUT", NULL, "HP"},
+};
+
+static int sgtl5000_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, sgtl5000_dapm_widgets,
+ ARRAY_SIZE(sgtl5000_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xfc - 0x3c;
+ return 0;
+}
+
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg, l, r;
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DAC_VOL);
+ l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT;
+ r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT;
+ l = l < 0x3c ? 0x3c : l;
+ l = l > 0xfc ? 0xfc : l;
+ r = r < 0x3c ? 0x3c : r;
+ r = r > 0xfc ? 0xfc : r;
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ ucontrol->value.integer.value[0] = l;
+ ucontrol->value.integer.value[1] = r;
+
+ return 0;
+}
+
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg, l, r;
+
+ l = ucontrol->value.integer.value[0];
+ r = ucontrol->value.integer.value[1];
+
+ l = l < 0 ? 0 : l;
+ l = l > 0xfc - 0x3c ? 0xfc - 0x3c : l;
+ r = r < 0 ? 0 : r;
+ r = r > 0xfc - 0x3c ? 0xfc - 0x3c : r;
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT |
+ r << SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ sgtl5000_write(codec, SGTL5000_CHIP_DAC_VOL, reg);
+
+ return 0;
+}
+
+static const char *mic_gain_text[] = {
+ "0dB", "20dB", "30dB", "40dB"
+};
+
+static const char *adc_m6db_text[] = {
+ "No Change", "Reduced by 6dB"
+};
+
+static const struct soc_enum mic_gain =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_MIC_CTRL, 0, 4, mic_gain_text);
+
+static const struct soc_enum adc_m6db =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_ADC_CTRL, 8, 2, adc_m6db_text);
+
+static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
+ SOC_ENUM("MIC GAIN", mic_gain),
+ SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0),
+ SOC_ENUM("Capture Vol Reduction", adc_m6db),
+ {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+ SOC_DOUBLE("Headphone Volume", SGTL5000_CHIP_ANA_HP_CTRL, 0, 8, 0x7f,
+ 1),
+};
+
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 adcdac_ctrl;
+
+ adcdac_ctrl = sgtl5000_read(codec, SGTL5000_CHIP_ADCDAC_CTRL);
+
+ if (mute) {
+ adcdac_ctrl |= SGTL5000_DAC_MUTE_LEFT;
+ adcdac_ctrl |= SGTL5000_DAC_MUTE_RIGHT;
+ } else {
+ adcdac_ctrl &= ~SGTL5000_DAC_MUTE_LEFT;
+ adcdac_ctrl &= ~SGTL5000_DAC_MUTE_RIGHT;
+ }
+
+ sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, adcdac_ctrl);
+ if (!mute)
+ dump_reg(codec);
+ return 0;
+}
+
+static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ u16 i2sctl = 0;
+ pr_debug("%s:fmt=%08x\n", __func__, fmt);
+ sgtl5000->master = 0;
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2sctl |= SGTL5000_I2S_MASTER;
+ sgtl5000->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2sctl |= SGTL5000_I2S_MODE_RJ;
+ i2sctl |= SGTL5000_I2S_LRPOL;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ case SND_SOC_DAIFMT_IB_NF:
+ i2sctl |= SGTL5000_I2S_SCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl);
+
+ return 0;
+}
+
+static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+
+ switch (clk_id) {
+ case SGTL5000_SYSCLK:
+ sgtl5000->sysclk = freq;
+ break;
+ case SGTL5000_LRCLK:
+ sgtl5000->lrclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sgtl5000_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ int reg;
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg |= SGTL5000_I2S_IN_POWERUP;
+ else
+ reg |= SGTL5000_I2S_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_ADC_POWERUP;
+ if (sgtl5000->capture_channels == 1)
+ reg &= ~SGTL5000_ADC_STEREO;
+ else
+ reg |= SGTL5000_ADC_STEREO;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ }
+
+ return 0;
+}
+
+static int sgtl5000_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ struct snd_pcm_runtime *master_runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sgtl5000->playback_active++;
+ else
+ sgtl5000->capture_active++;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (sgtl5000->master_substream) {
+ master_runtime = sgtl5000->master_substream->runtime;
+
+ pr_debug("Constraining to %d bits\n",
+ master_runtime->sample_bits);
+
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+
+ sgtl5000->slave_substream = substream;
+ } else
+ sgtl5000->master_substream = substream;
+
+ return 0;
+}
+
+static void sgtl5000_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ int reg, dig_pwr, ana_pwr;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sgtl5000->playback_active--;
+ else
+ sgtl5000->capture_active--;
+
+ if (sgtl5000->master_substream == substream)
+ sgtl5000->master_substream = sgtl5000->slave_substream;
+
+ sgtl5000->slave_substream = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ana_pwr = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ }
+
+ dig_pwr = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dig_pwr &= ~SGTL5000_I2S_IN_POWERUP;
+ else
+ dig_pwr &= ~SGTL5000_I2S_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr);
+
+ if (!sgtl5000->playback_active && !sgtl5000->capture_active) {
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL);
+ reg &= ~SGTL5000_I2S_MASTER;
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, reg);
+ }
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ * input: params_rate, params_fmt
+ */
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ int channels = params_channels(params);
+ int clk_ctl = 0;
+ int pll_ctl = 0;
+ int i2s_ctl;
+ int div2 = 0;
+ int reg;
+ int sys_fs;
+
+ pr_debug("%s channels=%d\n", __func__, channels);
+
+ if (!sgtl5000->sysclk) {
+ pr_err("%s: set sysclk first!\n", __func__);
+ return -EFAULT;
+ }
+
+ if (substream == sgtl5000->slave_substream) {
+ pr_debug("Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ sgtl5000->capture_channels = channels;
+
+ switch (sgtl5000->lrclk) {
+ case 8000:
+ case 16000:
+ sys_fs = 32000;
+ break;
+ case 11025:
+ case 22050:
+ sys_fs = 44100;
+ break;
+ default:
+ sys_fs = sgtl5000->lrclk;
+ break;
+ }
+
+ switch (sys_fs / sgtl5000->lrclk) {
+ case 4:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 2:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ default:
+ break;
+ }
+
+ switch (sys_fs) {
+ case 32000:
+ clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 44100:
+ clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 48000:
+ clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 96000:
+ clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ default:
+ pr_err("%s: sample rate %d not supported\n", __func__,
+ sgtl5000->lrclk);
+ return -EFAULT;
+ }
+ /* SGTL5000 rev1 has a IC bug to prevent switching to MCLK from PLL. */
+ if (!sgtl5000->master) {
+ sys_fs = sgtl5000->lrclk;
+ clk_ctl = SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT;
+ if (sys_fs * 256 == sgtl5000->sysclk)
+ clk_ctl |= SGTL5000_MCLK_FREQ_256FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else if (sys_fs * 384 == sgtl5000->sysclk && sys_fs != 96000)
+ clk_ctl |= SGTL5000_MCLK_FREQ_384FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else if (sys_fs * 512 == sgtl5000->sysclk && sys_fs != 96000)
+ clk_ctl |= SGTL5000_MCLK_FREQ_512FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else {
+ pr_err("%s: PLL not supported in slave mode\n",
+ __func__);
+ return -EINVAL;
+ }
+ } else
+ clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT;
+
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ u64 out, t;
+ unsigned int in, int_div, frac_div;
+ if (sgtl5000->sysclk > 17000000) {
+ div2 = 1;
+ in = sgtl5000->sysclk / 2;
+ } else {
+ div2 = 0;
+ in = sgtl5000->sysclk;
+ }
+ if (sys_fs == 44100)
+ out = 180633600;
+ else
+ out = 196608000;
+ t = do_div(out, in);
+ int_div = out;
+ t *= 2048;
+ do_div(t, in);
+ frac_div = t;
+ pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT |
+ frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT;
+ }
+
+ i2s_ctl = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pr_debug("\nfs=%d,clk_ctl=%04x,pll_ctl=%04x,i2s_ctl=%04x,div2=%d\n",
+ sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2);
+
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ sgtl5000_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL);
+ if (div2)
+ reg |= SGTL5000_INPUT_FREQ_DIV2;
+ else
+ reg &= ~SGTL5000_INPUT_FREQ_DIV2;
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg);
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ }
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl);
+
+ return 0;
+}
+
+static void sgtl5000_mic_bias(struct snd_soc_codec *codec, int enable)
+{
+ int reg, bias_r = 0;
+ if (enable)
+ bias_r = SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT;
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL);
+ if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) {
+ reg &= ~SGTL5000_BIAS_R_MASK;
+ reg |= bias_r;
+ sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg);
+ }
+}
+
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 reg, ana_pwr;
+ int delay = 0;
+ pr_debug("dapm level %d\n", level);
+ switch (level) {
+ case SND_SOC_BIAS_ON: /* full On */
+ if (codec->bias_level == SND_SOC_BIAS_ON)
+ break;
+
+ sgtl5000_mic_bias(codec, 1);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_VAG_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(400);
+
+ break;
+
+ case SND_SOC_BIAS_PREPARE: /* partial On */
+ if (codec->bias_level == SND_SOC_BIAS_PREPARE)
+ break;
+
+ sgtl5000_mic_bias(codec, 0);
+
+ /* must power up hp/line out before vag & dac to
+ avoid pops. */
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ if (reg & SGTL5000_VAG_POWERUP)
+ delay = 600;
+ reg &= ~SGTL5000_VAG_POWERUP;
+ reg |= SGTL5000_DAC_POWERUP;
+ reg |= SGTL5000_HP_POWERUP;
+ reg |= SGTL5000_LINE_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ if (delay)
+ msleep(delay);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ reg |= SGTL5000_DAC_EN;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+
+ break;
+
+ case SND_SOC_BIAS_STANDBY: /* Off, with power */
+ /* soc doesn't do PREPARE state after record so make sure
+ that anything that needs to be turned OFF gets turned off. */
+ if (codec->bias_level == SND_SOC_BIAS_STANDBY)
+ break;
+
+ /* soc calls digital_mute to unmute before record but doesn't
+ call digital_mute to mute after record. */
+ sgtl5000_digital_mute(&sgtl5000_dai, 1);
+
+ sgtl5000_mic_bias(codec, 0);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ if (reg & SGTL5000_VAG_POWERUP) {
+ reg &= ~SGTL5000_VAG_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(400);
+ }
+ reg &= ~SGTL5000_DAC_POWERUP;
+ reg &= ~SGTL5000_HP_POWERUP;
+ reg &= ~SGTL5000_LINE_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ reg &= ~SGTL5000_DAC_EN;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+
+ break;
+
+ case SND_SOC_BIAS_OFF: /* Off, without power */
+ /* must power down hp/line out after vag & dac to
+ avoid pops. */
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr = reg;
+ reg &= ~SGTL5000_VAG_POWERUP;
+
+ /* Workaround for sgtl5000 rev 0x11 chip audio suspend failure
+ issue on mx25 */
+ /* reg &= ~SGTL5000_REFTOP_POWERUP; */
+
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(600);
+
+ reg &= ~SGTL5000_HP_POWERUP;
+ reg &= ~SGTL5000_LINE_OUT_POWERUP;
+ reg &= ~SGTL5000_DAC_POWERUP;
+ reg &= ~SGTL5000_ADC_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ /* save ANA POWER register value for resume */
+ sgtl5000_write_reg_cache(codec, SGTL5000_CHIP_ANA_POWER,
+ ana_pwr);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define SGTL5000_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_96000)
+
+#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai_ops sgtl5000_ops = {
+ .prepare = sgtl5000_pcm_prepare,
+ .startup = sgtl5000_pcm_startup,
+ .shutdown = sgtl5000_pcm_shutdown,
+ .hw_params = sgtl5000_pcm_hw_params,
+ .digital_mute = sgtl5000_digital_mute,
+ .set_fmt = sgtl5000_set_dai_fmt,
+ .set_sysclk = sgtl5000_set_dai_sysclk
+};
+
+struct snd_soc_dai sgtl5000_dai = {
+ .name = "SGTL5000",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SGTL5000_RATES,
+ .formats = SGTL5000_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SGTL5000_RATES,
+ .formats = SGTL5000_FORMATS,
+ },
+ .ops = &sgtl5000_ops,
+ .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(sgtl5000_dai);
+
+static int sgtl5000_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int sgtl5000_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ unsigned int i;
+
+ /* Restore refs first in same order as in sgtl5000_init */
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINREG_CTRL);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_ANA_POWER);
+ msleep(10);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_REF_CTRL);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINE_OUT_CTRL);
+
+ /* Restore everythine else */
+ for (i = 1; i < sizeof(all_reg) / sizeof(int); i++)
+ sgtl5000_restore_reg(codec, all_reg[i]);
+
+ sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0);
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ sgtl5000_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+static struct snd_soc_codec *sgtl5000_codec;
+
+/*
+ * initialise the SGTL5000 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int sgtl5000_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = sgtl5000_codec;
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+ u16 reg, ana_pwr, lreg_ctrl, ref_ctrl, lo_ctrl, short_ctrl, sss;
+ int vag;
+ int ret = 0;
+
+ socdev->card->codec = sgtl5000_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ /* reset value */
+ ana_pwr = SGTL5000_DAC_STEREO |
+ SGTL5000_LINREG_SIMPLE_POWERUP |
+ SGTL5000_STARTUP_POWERUP |
+ SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP;
+ lreg_ctrl = 0;
+ ref_ctrl = 0;
+ lo_ctrl = 0;
+ short_ctrl = 0;
+ sss = SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT;
+
+ /* workaround for rev 0x11: use vddd linear regulator */
+ if (!sgtl5000->vddd || (sgtl5000->rev >= 0x11)) {
+ /* set VDDD to 1.2v */
+ lreg_ctrl |= 0x8 << SGTL5000_LINREG_VDDD_SHIFT;
+ /* power internal linear regulator */
+ ana_pwr |= SGTL5000_LINEREG_D_POWERUP;
+ } else {
+ /* turn of startup power */
+ ana_pwr &= ~SGTL5000_STARTUP_POWERUP;
+ ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
+ }
+ if (sgtl5000->vddio < 3100 && sgtl5000->vdda < 3100) {
+ /* Enable VDDC charge pump */
+ ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
+ }
+ if (sgtl5000->vddio >= 3100 && sgtl5000->vdda >= 3100) {
+ /* VDDC use VDDIO rail */
+ lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
+ if (sgtl5000->vddio >= 3100)
+ lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO <<
+ SGTL5000_VDDC_MAN_ASSN_SHIFT;
+ }
+ /* If PLL is powered up (such as on power cycle) leave it on. */
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr |= reg & (SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
+
+ /* set ADC/DAC ref voltage to vdda/2 */
+ vag = sgtl5000->vdda / 2;
+ if (vag <= SGTL5000_ANA_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP *
+ (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT))
+ vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT;
+ else
+ vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP;
+ ref_ctrl |= vag << SGTL5000_ANA_GND_SHIFT;
+
+ /* set line out ref voltage to vddio/2 */
+ vag = sgtl5000->vddio / 2;
+ if (vag <= SGTL5000_LINE_OUT_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_LINE_OUT_GND_BASE + SGTL5000_LINE_OUT_GND_STP *
+ SGTL5000_LINE_OUT_GND_MAX)
+ vag = SGTL5000_LINE_OUT_GND_MAX;
+ else
+ vag = (vag - SGTL5000_LINE_OUT_GND_BASE) /
+ SGTL5000_LINE_OUT_GND_STP;
+ lo_ctrl |= vag << SGTL5000_LINE_OUT_GND_SHIFT;
+
+ /* enable small pop */
+ ref_ctrl |= SGTL5000_SMALL_POP;
+
+ /* Controls the output bias current for the lineout */
+ lo_ctrl |=
+ (SGTL5000_LINE_OUT_CURRENT_360u << SGTL5000_LINE_OUT_CURRENT_SHIFT);
+
+ /* set short detect */
+ /* keep default */
+
+ /* set routing */
+ /* keep default, bypass DAP */
+
+ sgtl5000_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ msleep(10);
+
+ /* For rev 0x11, if vddd linear reg has been enabled, we have
+ to disable simple reg to get proper VDDD voltage. */
+ if ((ana_pwr & SGTL5000_LINEREG_D_POWERUP) && (sgtl5000->rev >= 0x11)) {
+ ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ msleep(10);
+ }
+
+ sgtl5000_write(codec, SGTL5000_CHIP_REF_CTRL, ref_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, lo_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_SHORT_CTRL, short_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_SSS_CTRL, sss);
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, 0);
+
+ reg = SGTL5000_DAC_VOL_RAMP_EN |
+ SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT;
+ sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, reg);
+
+#ifdef CONFIG_ARCH_MXC
+ if (cpu_is_mx25())
+ sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x01df);
+ else
+#endif
+ sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_ADC_CTRL);
+ reg &= ~SGTL5000_ADC_VOL_M6DB;
+ reg &= ~(SGTL5000_ADC_VOL_LEFT_MASK | SGTL5000_ADC_VOL_RIGHT_MASK);
+ reg |= (0xf << SGTL5000_ADC_VOL_LEFT_SHIFT)
+ | (0xf << SGTL5000_ADC_VOL_RIGHT_SHIFT);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg);
+
+ reg = SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE |
+ SGTL5000_HP_ZCD_EN | SGTL5000_ADC_ZCD_EN;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg);
+
+ sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, 0);
+ /* disable DAP */
+ sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0);
+ /* TODO: initialize DAP */
+
+ snd_soc_add_controls(codec, sgtl5000_snd_controls,
+ ARRAY_SIZE(sgtl5000_snd_controls));
+ sgtl5000_add_widgets(codec);
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "sgtl5000: failed to register card\n");
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* power down chip */
+static int sgtl5000_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ if (codec->control_data)
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_sgtl5000 = {
+ .probe = sgtl5000_probe,
+ .remove = sgtl5000_remove,
+ .suspend = sgtl5000_suspend,
+ .resume = sgtl5000_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_sgtl5000);
+
+static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sgtl5000_priv *sgtl5000;
+ struct snd_soc_codec *codec;
+ struct regulator *reg;
+ int ret = 0;
+ u32 val;
+
+ if (sgtl5000_codec) {
+ dev_err(&client->dev,
+ "Multiple SGTL5000 devices not supported\n");
+ return -ENOMEM;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL);
+ if (sgtl5000 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = sgtl5000;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ i2c_set_clientdata(client, codec);
+ codec->control_data = client;
+
+ reg = regulator_get(&client->dev, "VDDIO");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vddio = reg;
+
+ reg = regulator_get(&client->dev, "VDDA");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vdda = reg;
+
+ reg = regulator_get(&client->dev, "VDDD");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vddd = reg;
+
+ if (sgtl5000->reg_vdda) {
+ sgtl5000->vdda =
+ regulator_get_voltage(sgtl5000->reg_vdda) / 1000;
+ regulator_enable(sgtl5000->reg_vdda);
+ }
+ if (sgtl5000->reg_vddio) {
+ sgtl5000->vddio =
+ regulator_get_voltage(sgtl5000->reg_vddio) / 1000;
+ regulator_enable(sgtl5000->reg_vddio);
+ }
+ if (sgtl5000->reg_vddd) {
+ sgtl5000->vddd =
+ regulator_get_voltage(sgtl5000->reg_vddd) / 1000;
+ regulator_enable(sgtl5000->reg_vddd);
+ } else {
+ sgtl5000->vddd = 0; /* use internal regulator */
+ }
+
+ msleep(1);
+
+ val = sgtl5000_read(codec, SGTL5000_CHIP_ID);
+ if (((val & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
+ SGTL5000_PARTID_PART_ID) {
+ pr_err("Device with ID register %x is not a SGTL5000\n", val);
+ ret = -ENODEV;
+ goto err_codec_reg;
+ }
+
+ sgtl5000->rev = (val & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
+ dev_info(&client->dev, "SGTL5000 revision %d\n", sgtl5000->rev);
+
+ codec->dev = &client->dev;
+ codec->name = "SGTL5000";
+ codec->owner = THIS_MODULE;
+ codec->read = sgtl5000_read_reg_cache;
+ codec->write = sgtl5000_write;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = sgtl5000_set_bias_level;
+ codec->dai = &sgtl5000_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(sgtl5000_regs);
+ codec->reg_cache_step = 2;
+ codec->reg_cache = (void *)&sgtl5000_regs;
+
+ sgtl5000_sync_reg_cache(codec);
+
+ sgtl5000_codec = codec;
+ sgtl5000_dai.dev = &client->dev;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto err_codec_reg;
+ }
+
+ ret = snd_soc_register_dai(&sgtl5000_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+ goto err_codec_reg;
+ }
+
+ return 0;
+
+err_codec_reg:
+ if (sgtl5000->reg_vddd)
+ regulator_disable(sgtl5000->reg_vddd);
+ if (sgtl5000->reg_vdda)
+ regulator_disable(sgtl5000->reg_vdda);
+ if (sgtl5000->reg_vddio)
+ regulator_disable(sgtl5000->reg_vddio);
+ if (sgtl5000->reg_vddd)
+ regulator_put(sgtl5000->reg_vddd);
+ if (sgtl5000->reg_vdda)
+ regulator_put(sgtl5000->reg_vdda);
+ if (sgtl5000->reg_vddio)
+ regulator_put(sgtl5000->reg_vddio);
+ kfree(sgtl5000);
+ kfree(codec);
+ return ret;
+}
+
+static __devexit int sgtl5000_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ struct sgtl5000_priv *sgtl5000 = codec->private_data;
+
+ if (client->dev.platform_data)
+ clk_disable((struct clk *)client->dev.platform_data);
+
+ snd_soc_unregister_dai(&sgtl5000_dai);
+ snd_soc_unregister_codec(codec);
+
+ if (sgtl5000->reg_vddio) {
+ regulator_disable(sgtl5000->reg_vddio);
+ regulator_put(sgtl5000->reg_vddio);
+ }
+ if (sgtl5000->reg_vddd) {
+ regulator_disable(sgtl5000->reg_vddd);
+ regulator_put(sgtl5000->reg_vddd);
+ }
+ if (sgtl5000->reg_vdda) {
+ regulator_disable(sgtl5000->reg_vdda);
+ regulator_put(sgtl5000->reg_vdda);
+ }
+
+ kfree(codec);
+ kfree(sgtl5000);
+ sgtl5000_codec = NULL;
+ return 0;
+}
+
+static const struct i2c_device_id sgtl5000_id[] = {
+ {"sgtl5000-i2c", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, sgtl5000_id);
+
+static struct i2c_driver sgtl5000_i2c_driver = {
+ .driver = {
+ .name = "sgtl5000-i2c",
+ .owner = THIS_MODULE,
+ },
+ .probe = sgtl5000_i2c_probe,
+ .remove = __devexit_p(sgtl5000_i2c_remove),
+ .id_table = sgtl5000_id,
+};
+
+static int __init sgtl5000_modinit(void)
+{
+ return i2c_add_driver(&sgtl5000_i2c_driver);
+}
+module_init(sgtl5000_modinit);
+
+static void __exit sgtl5000_exit(void)
+{
+ i2c_del_driver(&sgtl5000_i2c_driver);
+}
+module_exit(sgtl5000_exit);
+
+MODULE_DESCRIPTION("ASoC SGTL5000 driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h
new file mode 100644
index 000000000000..b1a755e29d57
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.h
@@ -0,0 +1,406 @@
+/*
+ * sgtl5000.h - SGTL5000 audio codec interface
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _SGTL5000_H
+#define _SGTL5000_H
+
+#include <linux/i2c.h>
+
+extern struct snd_soc_dai sgtl5000_dai;
+extern struct snd_soc_codec_device soc_codec_dev_sgtl5000;
+
+/*
+ * Register values.
+ */
+#define SGTL5000_CHIP_ID 0x0000
+#define SGTL5000_CHIP_DIG_POWER 0x0002
+#define SGTL5000_CHIP_CLK_CTRL 0x0004
+#define SGTL5000_CHIP_I2S_CTRL 0x0006
+#define SGTL5000_CHIP_SSS_CTRL 0x000a
+#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e
+#define SGTL5000_CHIP_DAC_VOL 0x0010
+#define SGTL5000_CHIP_PAD_STRENGTH 0x0014
+#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020
+#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022
+#define SGTL5000_CHIP_ANA_CTRL 0x0024
+#define SGTL5000_CHIP_LINREG_CTRL 0x0026
+#define SGTL5000_CHIP_REF_CTRL 0x0028
+#define SGTL5000_CHIP_MIC_CTRL 0x002a
+#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c
+#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e
+#define SGTL5000_CHIP_ANA_POWER 0x0030
+#define SGTL5000_CHIP_PLL_CTRL 0x0032
+#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034
+#define SGTL5000_CHIP_ANA_STATUS 0x0036
+#define SGTL5000_CHIP_SHORT_CTRL 0x003c
+#define SGTL5000_CHIP_ANA_TEST2 0x003a
+#define SGTL5000_DAP_CTRL 0x0100
+#define SGTL5000_DAP_PEQ 0x0102
+#define SGTL5000_DAP_BASS_ENHANCE 0x0104
+#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106
+#define SGTL5000_DAP_AUDIO_EQ 0x0108
+#define SGTL5000_DAP_SURROUND 0x010a
+#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c
+#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e
+#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110
+#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116
+#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118
+#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a
+#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c
+#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e
+#define SGTL5000_DAP_MAIN_CHAN 0x0120
+#define SGTL5000_DAP_MIX_CHAN 0x0122
+#define SGTL5000_DAP_AVC_CTRL 0x0124
+#define SGTL5000_DAP_AVC_THRESHOLD 0x0126
+#define SGTL5000_DAP_AVC_ATTACK 0x0128
+#define SGTL5000_DAP_AVC_DECAY 0x012a
+#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c
+#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e
+#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130
+#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132
+#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134
+#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136
+#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138
+#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * SGTL5000_CHIP_ID
+ */
+#define SGTL5000_PARTID_MASK 0xff00
+#define SGTL5000_PARTID_SHIFT 8
+#define SGTL5000_PARTID_WIDTH 8
+#define SGTL5000_PARTID_PART_ID 0xa0
+#define SGTL5000_REVID_MASK 0x00ff
+#define SGTL5000_REVID_SHIFT 0
+#define SGTL5000_REVID_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_DIG_POWER
+ */
+#define SGTL5000_ADC_EN 0x0040
+#define SGTL5000_DAC_EN 0x0020
+#define SGTL5000_DAP_POWERUP 0x0010
+#define SGTL5000_I2S_OUT_POWERUP 0x0002
+#define SGTL5000_I2S_IN_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_CLK_CTRL
+ */
+#define SGTL5000_RATE_MODE_MASK 0x0030
+#define SGTL5000_RATE_MODE_SHIFT 4
+#define SGTL5000_RATE_MODE_WIDTH 2
+#define SGTL5000_RATE_MODE_DIV_1 0
+#define SGTL5000_RATE_MODE_DIV_2 1
+#define SGTL5000_RATE_MODE_DIV_4 2
+#define SGTL5000_RATE_MODE_DIV_6 3
+#define SGTL5000_SYS_FS_MASK 0x000c
+#define SGTL5000_SYS_FS_SHIFT 2
+#define SGTL5000_SYS_FS_WIDTH 2
+#define SGTL5000_SYS_FS_32k 0x0
+#define SGTL5000_SYS_FS_44_1k 0x1
+#define SGTL5000_SYS_FS_48k 0x2
+#define SGTL5000_SYS_FS_96k 0x3
+#define SGTL5000_MCLK_FREQ_MASK 0x0003
+#define SGTL5000_MCLK_FREQ_SHIFT 0
+#define SGTL5000_MCLK_FREQ_WIDTH 2
+#define SGTL5000_MCLK_FREQ_256FS 0x0
+#define SGTL5000_MCLK_FREQ_384FS 0x1
+#define SGTL5000_MCLK_FREQ_512FS 0x2
+#define SGTL5000_MCLK_FREQ_PLL 0x3
+
+/*
+ * SGTL5000_CHIP_I2S_CTRL
+ */
+#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100
+#define SGTL5000_I2S_SCLKFREQ_SHIFT 8
+#define SGTL5000_I2S_SCLKFREQ_WIDTH 1
+#define SGTL5000_I2S_SCLKFREQ_64FS 0x0
+#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */
+#define SGTL5000_I2S_MASTER 0x0080
+#define SGTL5000_I2S_SCLK_INV 0x0040
+#define SGTL5000_I2S_DLEN_MASK 0x0030
+#define SGTL5000_I2S_DLEN_SHIFT 4
+#define SGTL5000_I2S_DLEN_WIDTH 2
+#define SGTL5000_I2S_DLEN_32 0x0
+#define SGTL5000_I2S_DLEN_24 0x1
+#define SGTL5000_I2S_DLEN_20 0x2
+#define SGTL5000_I2S_DLEN_16 0x3
+#define SGTL5000_I2S_MODE_MASK 0x000c
+#define SGTL5000_I2S_MODE_SHIFT 2
+#define SGTL5000_I2S_MODE_WIDTH 2
+#define SGTL5000_I2S_MODE_I2S_LJ 0x0
+#define SGTL5000_I2S_MODE_RJ 0x1
+#define SGTL5000_I2S_MODE_PCM 0x2
+#define SGTL5000_I2S_LRALIGN 0x0002
+#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */
+
+/*
+ * SGTL5000_CHIP_SSS_CTRL
+ */
+#define SGTL5000_DAP_MIX_LRSWAP 0x4000
+#define SGTL5000_DAP_LRSWAP 0x2000
+#define SGTL5000_DAC_LRSWAP 0x1000
+#define SGTL5000_I2S_OUT_LRSWAP 0x0400
+#define SGTL5000_DAP_MIX_SEL_MASK 0x0300
+#define SGTL5000_DAP_MIX_SEL_SHIFT 8
+#define SGTL5000_DAP_MIX_SEL_WIDTH 2
+#define SGTL5000_DAP_MIX_SEL_ADC 0x0
+#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1
+#define SGTL5000_DAP_SEL_MASK 0x00c0
+#define SGTL5000_DAP_SEL_SHIFT 6
+#define SGTL5000_DAP_SEL_WIDTH 2
+#define SGTL5000_DAP_SEL_ADC 0x0
+#define SGTL5000_DAP_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_MASK 0x0030
+#define SGTL5000_DAC_SEL_SHIFT 4
+#define SGTL5000_DAC_SEL_WIDTH 2
+#define SGTL5000_DAC_SEL_ADC 0x0
+#define SGTL5000_DAC_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_DAP 0x3
+#define SGTL5000_I2S_OUT_SEL_MASK 0x0003
+#define SGTL5000_I2S_OUT_SEL_SHIFT 0
+#define SGTL5000_I2S_OUT_SEL_WIDTH 2
+#define SGTL5000_I2S_OUT_SEL_ADC 0x0
+#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1
+#define SGTL5000_I2S_OUT_SEL_DAP 0x3
+
+/*
+ * SGTL5000_CHIP_ADCDAC_CTRL
+ */
+#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000
+#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000
+#define SGTL5000_DAC_VOL_RAMP_EN 0x0200
+#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100
+#define SGTL5000_DAC_MUTE_RIGHT 0x0008
+#define SGTL5000_DAC_MUTE_LEFT 0x0004
+#define SGTL5000_ADC_HPF_FREEZE 0x0002
+#define SGTL5000_ADC_HPF_BYPASS 0x0001
+
+/*
+ * SGTL5000_CHIP_DAC_VOL
+ */
+#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00
+#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8
+#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8
+#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff
+#define SGTL5000_DAC_VOL_LEFT_SHIFT 0
+#define SGTL5000_DAC_VOL_LEFT_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_PAD_STRENGTH
+ */
+#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300
+#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8
+#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0
+#define SGTL5000_PAD_I2S_SCLK_SHIFT 6
+#define SGTL5000_PAD_I2S_SCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030
+#define SGTL5000_PAD_I2S_DOUT_SHIFT 4
+#define SGTL5000_PAD_I2S_DOUT_WIDTH 2
+#define SGTL5000_PAD_I2C_SDA_MASK 0x000c
+#define SGTL5000_PAD_I2C_SDA_SHIFT 2
+#define SGTL5000_PAD_I2C_SDA_WIDTH 2
+#define SGTL5000_PAD_I2C_SCL_MASK 0x0003
+#define SGTL5000_PAD_I2C_SCL_SHIFT 0
+#define SGTL5000_PAD_I2C_SCL_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_ANA_ADC_CTRL
+ */
+#define SGTL5000_ADC_VOL_M6DB 0x0100
+#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0
+#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4
+#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4
+#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f
+#define SGTL5000_ADC_VOL_LEFT_SHIFT 0
+#define SGTL5000_ADC_VOL_LEFT_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_ANA_HP_CTRL
+ */
+#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00
+#define SGTL5000_HP_VOL_RIGHT_SHIFT 8
+#define SGTL5000_HP_VOL_RIGHT_WIDTH 7
+#define SGTL5000_HP_VOL_LEFT_MASK 0x007f
+#define SGTL5000_HP_VOL_LEFT_SHIFT 0
+#define SGTL5000_HP_VOL_LEFT_WIDTH 7
+
+/*
+ * SGTL5000_CHIP_ANA_CTRL
+ */
+#define SGTL5000_LINE_OUT_MUTE 0x0100
+#define SGTL5000_HP_SEL_MASK 0x0040
+#define SGTL5000_HP_SEL_SHIFT 6
+#define SGTL5000_HP_SEL_WIDTH 1
+#define SGTL5000_HP_SEL_DAC 0x0
+#define SGTL5000_HP_SEL_LINE_IN 0x1
+#define SGTL5000_HP_ZCD_EN 0x0020
+#define SGTL5000_HP_MUTE 0x0010
+#define SGTL5000_ADC_SEL_MASK 0x0004
+#define SGTL5000_ADC_SEL_SHIFT 2
+#define SGTL5000_ADC_SEL_WIDTH 1
+#define SGTL5000_ADC_SEL_MIC 0x0
+#define SGTL5000_ADC_SEL_LINE_IN 0x1
+#define SGTL5000_ADC_ZCD_EN 0x0002
+#define SGTL5000_ADC_MUTE 0x0001
+
+/*
+ * SGTL5000_CHIP_LINREG_CTRL
+ */
+#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040
+#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6
+#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1
+#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0
+#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1
+#define SGTL5000_VDDC_ASSN_OVRD 0x0020
+#define SGTL5000_LINREG_VDDD_MASK 0x000f
+#define SGTL5000_LINREG_VDDD_SHIFT 0
+#define SGTL5000_LINREG_VDDD_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_REF_CTRL
+ */
+#define SGTL5000_ANA_GND_MASK 0x01f0
+#define SGTL5000_ANA_GND_SHIFT 4
+#define SGTL5000_ANA_GND_WIDTH 5
+#define SGTL5000_ANA_GND_BASE 800 /* mv */
+#define SGTL5000_ANA_GND_STP 25 /*mv */
+#define SGTL5000_BIAS_CTRL_MASK 0x000e
+#define SGTL5000_BIAS_CTRL_SHIFT 1
+#define SGTL5000_BIAS_CTRL_WIDTH 3
+#define SGTL5000_SMALL_POP 0x0001
+
+/*
+ * SGTL5000_CHIP_MIC_CTRL
+ */
+#define SGTL5000_BIAS_R_MASK 0x0200
+#define SGTL5000_BIAS_R_SHIFT 8
+#define SGTL5000_BIAS_R_WIDTH 2
+#define SGTL5000_BIAS_R_off 0x0
+#define SGTL5000_BIAS_R_2K 0x1
+#define SGTL5000_BIAS_R_4k 0x2
+#define SGTL5000_BIAS_R_8k 0x3
+#define SGTL5000_BIAS_VOLT_MASK 0x0070
+#define SGTL5000_BIAS_VOLT_SHIFT 4
+#define SGTL5000_BIAS_VOLT_WIDTH 3
+#define SGTL5000_MIC_GAIN_MASK 0x0003
+#define SGTL5000_MIC_GAIN_SHIFT 0
+#define SGTL5000_MIC_GAIN_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_CTRL
+ */
+#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00
+#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8
+#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4
+#define SGTL5000_LINE_OUT_CURRENT_180u 0x0
+#define SGTL5000_LINE_OUT_CURRENT_270u 0x1
+#define SGTL5000_LINE_OUT_CURRENT_360u 0x3
+#define SGTL5000_LINE_OUT_CURRENT_450u 0x7
+#define SGTL5000_LINE_OUT_CURRENT_540u 0xf
+#define SGTL5000_LINE_OUT_GND_MASK 0x003f
+#define SGTL5000_LINE_OUT_GND_SHIFT 0
+#define SGTL5000_LINE_OUT_GND_WIDTH 6
+#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */
+#define SGTL5000_LINE_OUT_GND_STP 25
+#define SGTL5000_LINE_OUT_GND_MAX 0x23
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_VOL
+ */
+#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00
+#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8
+#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5
+#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f
+#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0
+#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5
+
+/*
+ * SGTL5000_CHIP_ANA_POWER
+ */
+#define SGTL5000_DAC_STEREO 0x4000
+#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000
+#define SGTL5000_STARTUP_POWERUP 0x1000
+#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800
+#define SGTL5000_PLL_POWERUP 0x0400
+#define SGTL5000_LINEREG_D_POWERUP 0x0200
+#define SGTL5000_VCOAMP_POWERUP 0x0100
+#define SGTL5000_VAG_POWERUP 0x0080
+#define SGTL5000_ADC_STEREO 0x0040
+#define SGTL5000_REFTOP_POWERUP 0x0020
+#define SGTL5000_HP_POWERUP 0x0010
+#define SGTL5000_DAC_POWERUP 0x0008
+#define SGTL5000_CAPLESS_HP_POWERUP 0x0004
+#define SGTL5000_ADC_POWERUP 0x0002
+#define SGTL5000_LINE_OUT_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_PLL_CTRL
+ */
+#define SGTL5000_PLL_INT_DIV_MASK 0xf800
+#define SGTL5000_PLL_INT_DIV_SHIFT 11
+#define SGTL5000_PLL_INT_DIV_WIDTH 5
+#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700
+#define SGTL5000_PLL_FRAC_DIV_SHIFT 0
+#define SGTL5000_PLL_FRAC_DIV_WIDTH 11
+
+/*
+ * SGTL5000_CHIP_CLK_TOP_CTRL
+ */
+#define SGTL5000_INT_OSC_EN 0x0800
+#define SGTL5000_INPUT_FREQ_DIV2 0x0008
+
+/*
+ * SGTL5000_CHIP_ANA_STATUS
+ */
+#define SGTL5000_HP_LRSHORT 0x0200
+#define SGTL5000_CAPLESS_SHORT 0x0100
+#define SGTL5000_PLL_LOCKED 0x0010
+
+/*
+ * SGTL5000_CHIP_SHORT_CTRL
+ */
+#define SGTL5000_LVLADJR_MASK 0x7000
+#define SGTL5000_LVLADJR_SHIFT 12
+#define SGTL5000_LVLADJR_WIDTH 3
+#define SGTL5000_LVLADJL_MASK 0x0700
+#define SGTL5000_LVLADJL_SHIFT 8
+#define SGTL5000_LVLADJL_WIDTH 3
+#define SGTL5000_LVLADJC_MASK 0x0070
+#define SGTL5000_LVLADJC_SHIFT 4
+#define SGTL5000_LVLADJC_WIDTH 3
+#define SGTL5000_LR_SHORT_MOD_MASK 0x000c
+#define SGTL5000_LR_SHORT_MOD_SHIFT 2
+#define SGTL5000_LR_SHORT_MOD_WIDTH 2
+#define SGTL5000_CM_SHORT_MOD_MASK 0x0003
+#define SGTL5000_CM_SHORT_MOD_SHIFT 0
+#define SGTL5000_CM_SHORT_MOD_WIDTH 2
+
+/*
+ *SGTL5000_CHIP_ANA_TEST2
+ */
+#define SGTL5000_MONO_DAC 0x1000
+
+/*
+ * SGTL5000_DAP_CTRL
+ */
+#define SGTL5000_DAP_MIX_EN 0x0010
+#define SGTL5000_DAP_EN 0x0001
+
+#define SGTL5000_SYSCLK 0x00
+#define SGTL5000_LRCLK 0x01
+
+#endif
diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c
new file mode 100644
index 000000000000..b8463f58d18d
--- /dev/null
+++ b/sound/soc/codecs/stmp378x_codec.c
@@ -0,0 +1,1051 @@
+/*
+ * ALSA codec for Freescale STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <asm/dma.h>
+
+#include <mach/regs-apbx.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+#include <mach/regs-rtc.h>
+#include <mach/platform.h>
+
+#include "stmp378x_codec.h"
+
+#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */
+
+#define STMP378X_VERSION "0.1"
+struct stmp378x_codec_priv {
+ struct clk *clk;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * ALSA API
+ */
+static void __iomem *adc_regmap[] = {
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_STAT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACDEBUG,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT0,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT1,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_VERSION,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_STAT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCDEBUG,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_DATA,
+};
+
+static u16 stmp378x_audio_regs[ADC_REGNUM];
+
+static u8 dac_volumn_control_word[] = {
+ 0x37, 0x5e, 0x7e, 0x8e,
+ 0x9e, 0xae, 0xb6, 0xbe,
+ 0xc6, 0xce, 0xd6, 0xde,
+ 0xe6, 0xee, 0xf6, 0xfe,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit ADC/DAC registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static void stmp378x_codec_write_cache(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg < ADC_REGNUM)
+ cache[reg] = value;
+}
+
+static int stmp378x_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= ADC_REGNUM)
+ return -EIO;
+
+ stmp378x_codec_write_cache(codec, reg, value);
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, adc_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int stmp378x_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= ADC_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(adc_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+static unsigned int stmp378x_codec_read_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u16 *cache = codec->reg_cache;
+ if (reg >= ADC_REGNUM)
+ return -EINVAL;
+ return cache[reg];
+}
+
+static void stmp378x_codec_sync_reg_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ for (reg = 0; reg < ADC_REGNUM; reg += 1)
+ stmp378x_codec_write_cache(codec, reg,
+ stmp378x_codec_read(codec, reg));
+}
+
+static int stmp378x_codec_restore_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int cached_val, hw_val;
+
+ cached_val = stmp378x_codec_read_cache(codec, reg);
+ hw_val = stmp378x_codec_read(codec, reg);
+
+ if (hw_val != cached_val)
+ return stmp378x_codec_write(codec, reg, cached_val);
+
+ return 0;
+}
+
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xf;
+ return 0;
+}
+
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg, l, r;
+ int i;
+
+ reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME);
+
+ l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT;
+ r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >>
+ BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT;
+ /*Left channel */
+ i = 0;
+ while (i < 16) {
+ if (l == dac_volumn_control_word[i]) {
+ ucontrol->value.integer.value[0] = i;
+ break;
+ }
+ i++;
+ }
+ if (i == 16)
+ ucontrol->value.integer.value[0] = i;
+ /*Right channel */
+ i = 0;
+ while (i < 16) {
+ if (r == dac_volumn_control_word[i]) {
+ ucontrol->value.integer.value[1] = i;
+ break;
+ }
+ i++;
+ }
+ if (i == 16)
+ ucontrol->value.integer.value[1] = i;
+
+ return 0;
+}
+
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int reg, l, r;
+ int i;
+
+ i = ucontrol->value.integer.value[0];
+ l = dac_volumn_control_word[i];
+ /*Get dac volume for left channel */
+ reg = BF(l, AUDIOOUT_DACVOLUME_VOLUME_LEFT);
+
+ i = ucontrol->value.integer.value[1];
+ r = dac_volumn_control_word[i];
+ /*Get dac volume for right channel */
+ reg = reg | BF(r, AUDIOOUT_DACVOLUME_VOLUME_RIGHT);
+
+ /*Clear left/right dac volume */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT |
+ BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR);
+ __raw_writel(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+
+ return 0;
+}
+
+static const char *stmp378x_codec_adc_input_sel[] =
+ { "Mic", "Line In 1", "Head Phone", "Line In 2" };
+
+static const char *stmp378x_codec_hp_output_sel[] = { "DAC Out", "Line In 1" };
+
+static const char *stmp378x_codec_adc_3d_sel[] =
+ { "Off", "Low", "Medium", "High" };
+
+static const struct soc_enum stmp378x_codec_enum[] = {
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, stmp378x_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, stmp378x_codec_adc_input_sel),
+ SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, stmp378x_codec_hp_output_sel),
+ SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, stmp378x_codec_adc_3d_sel),
+};
+
+/* Codec controls */
+static const struct snd_kcontrol_new stmp378x_snd_controls[] = {
+ /* Playback Volume */
+ {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+
+ SOC_DOUBLE_R("DAC Playback Switch",
+ DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1),
+ SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1),
+ SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1),
+ SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1),
+
+ /* Capture Volume */
+ SOC_DOUBLE_R("ADC Capture Volume",
+ ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0),
+ SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0),
+ SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1),
+ SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0),
+
+ /* Virtual 3D effect */
+ SOC_ENUM("3D effect", stmp378x_codec_enum[3]),
+};
+
+/* Left ADC Mux */
+static const struct snd_kcontrol_new stmp378x_left_adc_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[0]);
+
+/* Right ADC Mux */
+static const struct snd_kcontrol_new stmp378x_right_adc_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[1]);
+
+/* Head Phone Mux */
+static const struct snd_kcontrol_new stmp378x_hp_controls =
+SOC_DAPM_ENUM("Route", stmp378x_codec_enum[2]);
+
+static const struct snd_soc_dapm_widget stmp378x_codec_widgets[] = {
+
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", DAC_PWRDN_L, 8, 1),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", DAC_PWRDN_H, 0, 1),
+
+ SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1),
+
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_left_adc_controls),
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_right_adc_controls),
+ SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0,
+ &stmp378x_hp_controls),
+
+ SND_SOC_DAPM_PGA("HP_AMP", DAC_PWRDN_L, 0, 1, NULL, 0),
+
+ SND_SOC_DAPM_PGA("HP_CAPLESS", DAC_PWRDN_L, 4, 1, NULL, 0),
+
+ SND_SOC_DAPM_PGA("SPK_AMP", DAC_PWRDN_H, 8, 1, NULL, 0),
+
+ SND_SOC_DAPM_INPUT("LINE1L"),
+ SND_SOC_DAPM_INPUT("LINE1R"),
+ SND_SOC_DAPM_INPUT("LINE2L"),
+ SND_SOC_DAPM_INPUT("LINE2R"),
+ SND_SOC_DAPM_INPUT("MIC"),
+
+ SND_SOC_DAPM_OUTPUT("SPEAKER"),
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+ /* Left ADC Mux */
+ {"Left ADC Mux", "Mic", "MIC"},
+ {"Left ADC Mux", "Line In 1", "LINE1L"},
+ {"Left ADC Mux", "Line In 2", "LINE2L"},
+ {"Left ADC Mux", "Head Phone", "HPL"},
+
+ /* Right ADC Mux */
+ {"Right ADC Mux", "Mic", "MIC"},
+ {"Right ADC Mux", "Line In 1", "LINE1R"},
+ {"Right ADC Mux", "Line In 2", "LINE2R"},
+ {"Right ADC Mux", "Head Phone", "HPR"},
+
+ /* ADC */
+ {"Left ADC", NULL, "Left ADC Mux"},
+ {"Right ADC", NULL, "Right ADC Mux"},
+
+ /* HP Mux */
+ {"HP Mux", "DAC Out", "DAC"},
+ {"HP Mux", "Line In 1", "LINE1L"},
+ {"HP Mux", "Line In 1", "LINE1R"},
+
+ /* HP output */
+ {"HP_CAPLESS", NULL, "HP Mux"},
+ {"HP_AMP", NULL, "HP_CAPLESS"},
+ {"HPR", NULL, "HP_AMP"},
+ {"HPL", NULL, "HP_AMP"},
+
+ /* Speaker amp */
+ {"SPK_AMP", NULL, "DAC"},
+ {"SPEAKER", NULL, "SPK_AMP"},
+};
+
+static int stmp378x_codec_add_widgets(struct snd_soc_codec *codec)
+{
+ int ret = 0;
+
+ snd_soc_dapm_new_controls(codec, stmp378x_codec_widgets,
+ ARRAY_SIZE(stmp378x_codec_widgets));
+
+ if (ret) {
+ dev_err(codec->dev, "dapm control register failed\n");
+ return ret;
+ }
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ if (ret) {
+ dev_err(codec->dev, "DAPM route register failed\n");
+ return ret;
+ }
+
+ return snd_soc_dapm_new_widgets(codec);
+}
+
+struct dac_srr {
+ u32 rate;
+ u32 basemult;
+ u32 src_hold;
+ u32 src_int;
+ u32 src_frac;
+};
+
+static struct dac_srr srr_values[] = {
+ {192000, 0x4, 0x0, 0x0F, 0x13FF},
+ {176400, 0x4, 0x0, 0x11, 0x0037},
+ {128000, 0x4, 0x0, 0x17, 0x0E00},
+ {96000, 0x2, 0x0, 0x0F, 0x13FF},
+ {88200, 0x2, 0x0, 0x11, 0x0037},
+ {64000, 0x2, 0x0, 0x17, 0x0E00},
+ {48000, 0x1, 0x0, 0x0F, 0x13FF},
+ {44100, 0x1, 0x0, 0x11, 0x0037},
+ {32000, 0x1, 0x0, 0x17, 0x0E00},
+ {24000, 0x1, 0x1, 0x0F, 0x13FF},
+ {22050, 0x1, 0x1, 0x11, 0x0037},
+ {16000, 0x1, 0x1, 0x17, 0x0E00},
+ {12000, 0x1, 0x3, 0x0F, 0x13FF},
+ {11025, 0x1, 0x3, 0x11, 0x0037},
+ {8000, 0x1, 0x3, 0x17, 0x0E00}
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int stmp378x_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 src_hold = 0;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ dev_warn(socdev->dev, "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ src_hold = srr_values[i].src_hold;
+
+ srr_value =
+ BF(srr_values[i].basemult, AUDIOOUT_DACSRR_BASEMULT) |
+ BF(srr_values[i].src_int, AUDIOOUT_DACSRR_SRC_INT) |
+ BF(srr_values[i].src_frac, AUDIOOUT_DACSRR_SRC_FRAC) |
+ BF(src_hold, AUDIOOUT_DACSRR_SRC_HOLD);
+
+ if (playback)
+ __raw_writel(srr_value,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR);
+ else
+ __raw_writel(srr_value,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+
+ break;
+
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ break;
+
+ default:
+ dev_warn(socdev->dev, "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+
+ }
+
+ return 0;
+}
+
+static int stmp378x_codec_dig_mute(struct snd_soc_dai *dai, int mute)
+{
+ u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT;
+
+ if (mute) {
+ __raw_writel(dac_mask,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ } else {
+ __raw_writel(dac_mask,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR);
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR);
+ }
+ return 0;
+}
+
+/*
+ * Codec initialization
+ */
+#define VAG_BASE_VALUE ((1400/2 - 625)/25)
+static void stmp378x_codec_dac_set_vag(void)
+{
+ u32 refctrl_val = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL);
+
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL);
+ refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ);
+ refctrl_val |= BF(VAG_BASE_VALUE, AUDIOOUT_REFCTRL_VAG_VAL) |
+ BM_AUDIOOUT_REFCTRL_ADJ_VAG |
+ BF(0xF, AUDIOOUT_REFCTRL_ADC_REFVAL) |
+ BM_AUDIOOUT_REFCTRL_ADJ_ADC |
+ BF(0x3, AUDIOOUT_REFCTRL_VBG_ADJ) | BM_AUDIOOUT_REFCTRL_RAISE_REF;
+
+ __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL);
+}
+
+static void
+stmp378x_codec_dac_power_on(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Ungate DAC clocks */
+ __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+
+ /* Update DAC volume over zero crossings */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_EN_ZCD,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ /* Mute DAC */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+
+ /* Update HP volume over zero crossings */
+ __raw_writel(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+
+ /* Prepare powering up HP output */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+ __raw_writel(BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG),
+ REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ /* Mute HP output */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+
+ /* Mute speaker amp */
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+}
+
+static void
+stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Disable class AB */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+
+ /* Set hold to ground */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ /* Mute HP output */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ /* Power down HP output */
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Mute speaker amp */
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ /* Power down speaker amp */
+ __raw_writel(BM_AUDIOOUT_PWRDN_SPEAKER,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Mute DAC */
+ __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT |
+ BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET);
+ /* Power down DAC */
+ __raw_writel(BM_AUDIOOUT_PWRDN_DAC,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Gate DAC clocks */
+ __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+}
+
+static void
+stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ u32 reg;
+
+ /* Ungate ADC clocks */
+ __raw_writel(BM_AUDIOIN_CTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+
+ /* Unmute ADC channels */
+ __raw_writel(BM_AUDIOIN_ADCVOL_MUTE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+
+ /*
+ * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared.
+ * They aren't presented in the datasheet, so this is hardcode.
+ */
+ __raw_writel(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME_CLR);
+
+ /* Set the Input channel gain 3dB */
+ __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_LEFT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_RIGHT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+ __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Select default input - Microphone */
+ __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_LEFT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_RIGHT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR);
+ __raw_writel(BF
+ (BV_AUDIOIN_ADCVOL_SELECT__MIC,
+ AUDIOIN_ADCVOL_SELECT_LEFT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+ __raw_writel(BF
+ (BV_AUDIOIN_ADCVOL_SELECT__MIC,
+ AUDIOIN_ADCVOL_SELECT_RIGHT),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Supply bias voltage to microphone */
+ __raw_writel(BF(2, AUDIOIN_MICLINE_MIC_RESISTOR),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+ __raw_writel(BM_AUDIOIN_MICLINE_MIC_SELECT,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+
+ /* Set max ADC volume */
+ reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+ reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_LEFT);
+ reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_RIGHT);
+ __raw_writel(reg, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+}
+
+static void
+stmp378x_codec_adc_power_down(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Mute ADC channels */
+ __raw_writel(BM_AUDIOIN_ADCVOL_MUTE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET);
+
+ /* Power Down ADC */
+ __raw_writel(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+
+ /* Gate ADC clocks */
+ __raw_writel(BM_AUDIOIN_CTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_SET);
+
+ /* Disable bias voltage to microphone */
+ __raw_writel(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR),
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET);
+}
+
+static void stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Move DAC codec out of reset */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+
+ /* Reduce analog power */
+ __raw_writel(BM_AUDIOOUT_TEST_HP_I1_ADJ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_CLR);
+ __raw_writel(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_LOW_PWR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET);
+ __raw_writel(BM_AUDIOOUT_REFCTRL_BIAS_CTRL,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR);
+ __raw_writel(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR);
+
+ /* Set Vag value */
+ stmp378x_codec_dac_set_vag();
+
+ /* Power on DAC codec */
+ stmp378x_codec_dac_power_on(stmp378x_adc);
+}
+
+static void stmp378x_codec_dac_disable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ stmp378x_codec_dac_power_down(stmp378x_adc);
+}
+
+static void stmp378x_codec_adc_enable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ /* Move ADC codec out of reset */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ /* Power on ADC codec */
+ stmp378x_codec_adc_power_on(stmp378x_adc);
+}
+
+static void stmp378x_codec_adc_disable(struct stmp378x_codec_priv *stmp378x_adc)
+{
+ stmp378x_codec_adc_power_down(stmp378x_adc);
+}
+
+static void stmp378x_codec_startup(struct snd_soc_codec *codec)
+{
+ struct stmp378x_codec_priv *stmp378x_adc = codec->private_data;
+
+ /* Soft reset DAC block */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) &
+ BM_AUDIOOUT_CTRL_CLKGATE));
+
+ /* Soft reset ADC block */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ while (!(__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) &
+ BM_AUDIOIN_CTRL_CLKGATE)) ;
+
+ stmp378x_codec_dac_enable(stmp378x_adc);
+ stmp378x_codec_adc_enable(stmp378x_adc);
+
+ /*Sync regs and cache */
+ stmp378x_codec_sync_reg_cache(codec);
+
+ snd_soc_add_controls(codec, stmp378x_snd_controls,
+ ARRAY_SIZE(stmp378x_snd_controls));
+
+ stmp378x_codec_add_widgets(codec);
+}
+
+static void stmp378x_codec_stop(struct snd_soc_codec *codec)
+{
+ struct stmp378x_codec_priv *stmp378x_adc = codec->private_data;
+ stmp378x_codec_dac_disable(stmp378x_adc);
+ stmp378x_codec_adc_disable(stmp378x_adc);
+}
+
+#define STMP378X_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define STMP378X_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops stmp378x_dai_ops = {
+ .hw_params = stmp378x_codec_hw_params,
+ .digital_mute = stmp378x_codec_dig_mute,
+};
+
+struct snd_soc_dai stmp378x_codec_dai = {
+ .name = "stmp378x adc/dac",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP378X_ADC_RATES,
+ .formats = STMP378X_ADC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP378X_ADC_RATES,
+ .formats = STMP378X_ADC_FORMATS,
+ },
+ .ops = &stmp378x_dai_ops,
+};
+
+EXPORT_SYMBOL_GPL(stmp378x_codec_dai);
+
+static struct snd_soc_codec *stmp378x_codec;
+
+static int stmp378x_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ socdev->card->codec = stmp378x_codec;
+ codec = stmp378x_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ stmp378x_codec_startup(codec);
+
+ /* Register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmp378x_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ stmp378x_codec_stop(codec);
+
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int stmp378x_codec_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct stmp378x_codec_priv *stmp378x_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp378x_adc = codec->private_data;
+
+ stmp378x_codec_dac_disable(stmp378x_adc);
+ stmp378x_codec_adc_disable(stmp378x_adc);
+ clk_disable(stmp378x_adc->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int stmp378x_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct stmp378x_codec_priv *stmp378x_adc;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp378x_adc = codec->private_data;
+ clk_enable(stmp378x_adc->clk);
+
+ /* Soft reset DAC block */
+ __raw_writel(BM_AUDIOOUT_CTRL_SFTRST,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ while (!
+ (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) &
+ BM_AUDIOOUT_CTRL_CLKGATE)) ;
+
+ /* Soft reset ADC block */
+ __raw_writel(BM_AUDIOIN_CTRL_SFTRST,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ while (!
+ (__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) &
+ BM_AUDIOIN_CTRL_CLKGATE)) ;
+
+ stmp378x_codec_dac_enable(stmp378x_adc);
+ stmp378x_codec_adc_enable(stmp378x_adc);
+
+ /*restore registers relevant to amixer controls */
+ stmp378x_codec_restore_reg(codec, DAC_CTRL_L);
+ stmp378x_codec_restore_reg(codec, DAC_VOLUME_L);
+ stmp378x_codec_restore_reg(codec, DAC_VOLUME_H);
+ stmp378x_codec_restore_reg(codec, DAC_HPVOL_L);
+ stmp378x_codec_restore_reg(codec, DAC_HPVOL_H);
+ stmp378x_codec_restore_reg(codec, DAC_SPEAKERCTRL_H);
+ stmp378x_codec_restore_reg(codec, ADC_VOLUME_L);
+ stmp378x_codec_restore_reg(codec, ADC_VOLUME_H);
+ stmp378x_codec_restore_reg(codec, ADC_ADCVOL_L);
+ stmp378x_codec_restore_reg(codec, ADC_ADCVOL_H);
+ stmp378x_codec_restore_reg(codec, ADC_MICLINE_L);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define stmp378x_codec_suspend NULL
+#define stmp378x_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_codec_dev_stmp378x = {
+ .probe = stmp378x_codec_probe,
+ .remove = stmp378x_codec_remove,
+ .suspend = stmp378x_codec_suspend,
+ .resume = stmp378x_codec_resume,
+};
+
+EXPORT_SYMBOL_GPL(soc_codec_dev_stmp378x);
+
+/* codec register, unregister function */
+static int __init stmp378x_audio_probe(struct platform_device *pdev)
+{
+ struct stmp378x_codec_priv *stmp378x_adc;
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ dev_info(&pdev->dev,
+ "STMP378X ADC/DAC Audio Codec %s\n", STMP378X_VERSION);
+
+ stmp378x_adc = kzalloc(sizeof(struct stmp378x_codec_priv), GFP_KERNEL);
+ if (stmp378x_adc == NULL)
+ return -ENOMEM;
+
+ codec = &stmp378x_adc->codec;
+ codec->dev = &pdev->dev;
+ codec->name = "stmp378x adc/dac";
+ codec->owner = THIS_MODULE;
+ codec->private_data = stmp378x_adc;
+ codec->read = stmp378x_codec_read;
+ codec->write = stmp378x_codec_write;
+ codec->dai = &stmp378x_codec_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(stmp378x_audio_regs) >> 1;
+ codec->reg_cache_step = 1;
+ codec->reg_cache = (void *)&stmp378x_audio_regs;
+
+ platform_set_drvdata(pdev, stmp378x_adc);
+
+ stmp378x_codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* Turn on audio clock */
+ stmp378x_adc->clk = clk_get(&pdev->dev, "audio");
+ if (IS_ERR(stmp378x_adc->clk)) {
+ ret = PTR_ERR(stmp378x_adc->clk);
+ dev_err(&pdev->dev, "%s: Clocks initialization failed\n", __func__);
+ goto clk_err;
+ }
+ clk_enable(stmp378x_adc->clk);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto card_err;
+ }
+
+ ret = snd_soc_register_dai(&stmp378x_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register codec dai\n");
+ goto dai_err;
+ }
+
+ return 0;
+
+dai_err:
+ snd_soc_unregister_codec(codec);
+card_err:
+ clk_disable(stmp378x_adc->clk);
+ clk_put(stmp378x_adc->clk);
+clk_err:
+ kfree(stmp378x_adc);
+ return ret;
+}
+
+static int __devexit stmp378x_audio_remove(struct platform_device *pdev)
+{
+ struct stmp378x_codec_priv *stmp378x_adc = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = &stmp378x_adc->codec;
+
+ snd_soc_unregister_codec(codec);
+
+ clk_disable(stmp378x_adc->clk);
+ clk_put(stmp378x_adc->clk);
+
+ kfree(stmp378x_adc);
+
+ return 0;
+}
+
+struct platform_driver stmp378x_audio_driver = {
+ .driver = {
+ .name = "stmp378x-audio",
+ },
+ .probe = stmp378x_audio_probe,
+ .remove = __devexit_p(stmp378x_audio_remove),
+};
+
+static int __init stmp378x_codec_init(void)
+{
+ return platform_driver_register(&stmp378x_audio_driver);
+}
+
+static void __exit stmp378x_codec_exit(void)
+{
+ return platform_driver_unregister(&stmp378x_audio_driver);
+}
+
+module_init(stmp378x_codec_init);
+module_exit(stmp378x_codec_exit);
+
+MODULE_DESCRIPTION("STMP378X ADC/DAC codec");
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stmp378x_codec.h b/sound/soc/codecs/stmp378x_codec.h
new file mode 100644
index 000000000000..80fce7273126
--- /dev/null
+++ b/sound/soc/codecs/stmp378x_codec.h
@@ -0,0 +1,87 @@
+/*
+ * ALSA codec for Freescale STMP378X
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef __STMP378X_CODEC_H
+#define __STMP378X_CODEC_H
+
+#define DAC_CTRL_L 0
+#define DAC_CTRL_H 1
+#define DAC_STAT_L 2
+#define DAC_STAT_H 3
+#define DAC_SRR_L 4
+#define DAC_VOLUME_L 6
+#define DAC_VOLUME_H 7
+#define DAC_DEBUG_L 8
+#define DAC_DEBUG_H 9
+#define DAC_HPVOL_L 10
+#define DAC_HPVOL_H 11
+#define DAC_PWRDN_L 12
+#define DAC_PWRDN_H 13
+#define DAC_REFCTRL_L 14
+#define DAC_REFCTRL_H 15
+#define DAC_ANACTRL_L 16
+#define DAC_ANACTRL_H 17
+#define DAC_TEST_L 18
+#define DAC_TEST_H 19
+#define DAC_BISTCTRL_L 20
+#define DAC_BISTCTRL_H 21
+#define DAC_BISTSTAT0_L 22
+#define DAC_BISTSTAT0_H 23
+#define DAC_BISTSTAT1_L 24
+#define DAC_BISTSTAT1_H 25
+#define DAC_ANACLKCTRL_L 26
+#define DAC_ANACLKCTRL_H 27
+#define DAC_DATA_L 28
+#define DAC_DATA_H 29
+#define DAC_SPEAKERCTRL_L 30
+#define DAC_SPEAKERCTRL_H 31
+#define DAC_VERSION_L 32
+#define DAC_VERSION_H 33
+#define ADC_CTRL_L 34
+#define ADC_CTRL_H 35
+#define ADC_STAT_L 36
+#define ADC_STAT_H 37
+#define ADC_SRR_L 38
+#define ADC_SRR_H 39
+#define ADC_VOLUME_L 40
+#define ADC_VOLUME_H 41
+#define ADC_DEBUG_L 42
+#define ADC_DEBUG_H 43
+#define ADC_ADCVOL_L 44
+#define ADC_ADCVOL_H 45
+#define ADC_MICLINE_L 46
+#define ADC_MICLINE_H 47
+#define ADC_ANACLKCTRL_L 48
+#define ADC_ANACLKCTRL_H 49
+#define ADC_DATA_L 50
+#define ADC_DATA_H 51
+
+#define ADC_REGNUM 52
+
+#define DAC_VOLUME_MIN 0x37
+#define DAC_VOLUME_MAX 0xFE
+#define ADC_VOLUME_MIN 0x37
+#define ADC_VOLUME_MAX 0xFE
+#define HP_VOLUME_MAX 0x0
+#define HP_VOLUME_MIN 0x7F
+#define LO_VOLUME_MAX 0x0
+#define LO_VOLUME_MIN 0x1F
+
+extern struct snd_soc_dai stmp378x_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_stmp378x;
+
+#endif /* __STMP378X_CODEC_H */
diff --git a/sound/soc/codecs/stmp3xxx_spdif.c b/sound/soc/codecs/stmp3xxx_spdif.c
new file mode 100644
index 000000000000..afbcfa6c9f8c
--- /dev/null
+++ b/sound/soc/codecs/stmp3xxx_spdif.c
@@ -0,0 +1,455 @@
+/*
+ * ALSA SoC STMP3xxx SPDIF transmitter driver
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <asm/dma.h>
+
+#include <mach/regs-spdif.h>
+#include <mach/platform.h>
+
+#include "stmp3xxx_spdif.h"
+
+#define STMP3XXX_VERSION "0.1"
+struct stmp3xxx_codec_priv {
+ struct clk *clk;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * ALSA API
+ */
+static void __iomem *spdif_regmap[] = {
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL,
+ REGS_SPDIF_BASE + HW_SPDIF_STAT,
+ REGS_SPDIF_BASE + HW_SPDIF_FRAMECTRL,
+ REGS_SPDIF_BASE + HW_SPDIF_SRR,
+ REGS_SPDIF_BASE + HW_SPDIF_DEBUG,
+ REGS_SPDIF_BASE + HW_SPDIF_DATA,
+ REGS_SPDIF_BASE + HW_SPDIF_VERSION,
+};
+
+/*
+ * ALSA core supports only 16 bit registers. It means we have to simulate it
+ * by virtually splitting a 32bit SPDIF registers into two halves
+ * high (bits 31:16) and low (bits 15:0). The routins abow detects which part
+ * of 32bit register is accessed.
+ */
+static int stmp3xxx_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ unsigned int reg_val;
+ unsigned int mask = 0xffff;
+
+ if (reg >= SPDIF_REGNUM)
+ return -EIO;
+
+ if (reg & 0x1) {
+ mask <<= 16;
+ value <<= 16;
+ }
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ reg_val = (reg_val & ~mask) | value;
+ __raw_writel(reg_val, spdif_regmap[reg >> 1]);
+
+ return 0;
+}
+
+static unsigned int stmp3xxx_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int reg_val;
+
+ if (reg >= SPDIF_REGNUM)
+ return -1;
+
+ reg_val = __raw_readl(spdif_regmap[reg >> 1]);
+ if (reg & 1)
+ reg_val >>= 16;
+
+ return reg_val & 0xffff;
+}
+
+/* Codec controls */
+static const struct snd_kcontrol_new stmp3xxx_snd_controls[] = {
+ SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0),
+ SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0),
+ SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0),
+ SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0),
+ SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0),
+ SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0),
+ SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0),
+ SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0),
+ SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0),
+ SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0),
+};
+
+struct spdif_srr {
+ u32 rate;
+ u32 basemult;
+ u32 rate_factor;
+};
+
+static struct spdif_srr srr_values[] = {
+ {96000, 0x2, 0x0BB80},
+ {88200, 0x2, 0x0AC44},
+ {64000, 0x2, 0x07D00},
+ {48000, 0x1, 0x0BB80},
+ {44100, 0x1, 0x0AC44},
+ {32000, 0x1, 0x07D00},
+};
+
+static inline int get_srr_values(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(srr_values); i++)
+ if (srr_values[i].rate == rate)
+ return i;
+
+ return -1;
+}
+
+static int stmp3xxx_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int i;
+ u32 srr_value = 0;
+ u32 basemult;
+
+ i = get_srr_values(params_rate(params));
+ if (i < 0)
+ printk(KERN_WARNING "%s doesn't support rate %d\n",
+ codec->name, params_rate(params));
+ else {
+ basemult = srr_values[i].basemult;
+
+ srr_value = BF(basemult, SPDIF_SRR_BASEMULT) |
+ BF(srr_values[i].rate_factor, SPDIF_SRR_RATE);
+
+ if (playback)
+ __raw_writel(srr_value, REGS_SPDIF_BASE + HW_SPDIF_SRR);
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ break;
+ default:
+ printk(KERN_WARNING "%s doesn't support format %d\n",
+ codec->name, params_format(params));
+ }
+
+ return 0;
+}
+
+static void
+stmp3xxx_codec_spdif_enable(struct stmp3xxx_codec_priv *stmp3xxx_spdif)
+{
+ /* Move SPDIF codec out of reset */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+
+ /* Ungate SPDIF clocks */
+ __raw_writel(BM_SPDIF_CTRL_CLKGATE,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+
+ /* 16 bit word length */
+ __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+}
+
+static void
+stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif)
+{
+ /* Gate SPDIF clocks */
+ __raw_writel(BM_SPDIF_CTRL_CLKGATE,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+}
+
+static void stmp3xxx_codec_init(struct snd_soc_codec *codec)
+{
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data;
+
+ /* Soft reset SPDIF block */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE));
+
+ stmp3xxx_codec_spdif_enable(stmp3xxx_spdif);
+
+ snd_soc_add_controls(codec, stmp3xxx_snd_controls,
+ ARRAY_SIZE(stmp3xxx_snd_controls));
+}
+
+static void stmp3xxx_codec_exit(struct snd_soc_codec *codec)
+{
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data;
+
+ stmp3xxx_codec_spdif_disable(stmp3xxx_spdif);
+}
+
+#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai_ops stmp3xxx_spdif_codec_dai_ops = {
+ .hw_params = stmp3xxx_codec_hw_params,
+};
+
+struct snd_soc_dai stmp3xxx_spdif_codec_dai = {
+ .name = "stmp3xxx spdif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .ops = &stmp3xxx_spdif_codec_dai_ops,
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_spdif_codec_dai);
+
+static struct snd_soc_codec *stmp3xxx_spdif_codec;
+
+static int stmp3xxx_codec_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ socdev->card->codec = stmp3xxx_spdif_codec;
+ codec = stmp3xxx_spdif_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ stmp3xxx_codec_init(codec);
+
+ /* Register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ snd_soc_dapm_free(socdev);
+ snd_soc_free_pcms(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmp3xxx_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ stmp3xxx_codec_exit(codec);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_codec_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp3xxx_spdif = codec->private_data;
+
+ stmp3xxx_codec_spdif_disable(stmp3xxx_spdif);
+ clk_disable(stmp3xxx_spdif->clk);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int stmp3xxx_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = -EINVAL;
+
+ if (codec == NULL)
+ goto out;
+
+ stmp3xxx_spdif = codec->private_data;
+ clk_enable(stmp3xxx_spdif->clk);
+
+ /* Soft reset SPDIF block */
+ __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE));
+
+ stmp3xxx_codec_spdif_enable(stmp3xxx_spdif);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+#else
+#define stmp3xxx_codec_suspend NULL
+#define stmp3xxx_codec_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx = {
+ .probe = stmp3xxx_codec_probe,
+ .remove = stmp3xxx_codec_remove,
+ .suspend = stmp3xxx_codec_suspend,
+ .resume = stmp3xxx_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_stmp3xxx);
+
+static int __init stmp3xxx_spdif_probe(struct platform_device *pdev)
+{
+ struct snd_soc_codec *codec;
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif;
+ int ret = 0;
+
+ dev_info(&pdev->dev,
+ "STMP3XXX SPDIF Audio Transmitter %s\n", STMP3XXX_VERSION);
+
+ stmp3xxx_spdif =
+ kzalloc(sizeof(struct stmp3xxx_codec_priv), GFP_KERNEL);
+ if (stmp3xxx_spdif == NULL)
+ return -ENOMEM;
+
+ codec = &stmp3xxx_spdif->codec;
+ codec->name = "stmp3xxx spdif";
+ codec->owner = THIS_MODULE;
+ codec->private_data = stmp3xxx_spdif;
+ codec->read = stmp3xxx_codec_read;
+ codec->write = stmp3xxx_codec_write;
+ codec->dai = &stmp3xxx_spdif_codec_dai;
+ codec->num_dai = 1;
+
+ platform_set_drvdata(pdev, stmp3xxx_spdif);
+ stmp3xxx_spdif_codec = codec;
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ /* Turn on audio clock */
+ stmp3xxx_spdif->clk = clk_get(&pdev->dev, "spdif");
+ if (IS_ERR(stmp3xxx_spdif->clk)) {
+ ret = PTR_ERR(stmp3xxx_spdif->clk);
+ dev_err(&pdev->dev, "Clocks initialization failed\n");
+ goto clk_err;
+ }
+ clk_enable(stmp3xxx_spdif->clk);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto card_err;
+ }
+
+ ret = snd_soc_register_dai(&stmp3xxx_spdif_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register card\n");
+ goto dai_err;
+ }
+
+ return 0;
+
+dai_err:
+ snd_soc_unregister_codec(codec);
+card_err:
+ clk_disable(stmp3xxx_spdif->clk);
+ clk_put(stmp3xxx_spdif->clk);
+clk_err:
+ kfree(stmp3xxx_spdif);
+ return ret;
+}
+static int __devexit stmp3xxx_spdif_remove(struct platform_device *pdev)
+{
+ struct stmp3xxx_codec_priv *stmp3xxx_spdif = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = &stmp3xxx_spdif->codec;
+
+ snd_soc_unregister_codec(codec);
+
+ clk_disable(stmp3xxx_spdif->clk);
+ clk_put(stmp3xxx_spdif->clk);
+
+ kfree(stmp3xxx_spdif);
+
+ return 0;
+}
+
+struct platform_driver stmp3xxx_spdif_driver = {
+ .driver = {
+ .name = "stmp3xxx-spdif",
+ },
+ .probe = stmp3xxx_spdif_probe,
+ .remove = __devexit_p(stmp3xxx_spdif_remove),
+};
+
+static int __init stmp3xxx_spdif_init(void)
+{
+ return platform_driver_register(&stmp3xxx_spdif_driver);
+}
+
+static void __exit stmp3xxx_spdif_exit(void)
+{
+ return platform_driver_unregister(&stmp3xxx_spdif_driver);
+}
+
+module_init(stmp3xxx_spdif_init);
+module_exit(stmp3xxx_spdif_exit);
+
+MODULE_DESCRIPTION("STMP3XXX SPDIF transmitter");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/stmp3xxx_spdif.h b/sound/soc/codecs/stmp3xxx_spdif.h
new file mode 100644
index 000000000000..0ae20a7e8cc6
--- /dev/null
+++ b/sound/soc/codecs/stmp3xxx_spdif.h
@@ -0,0 +1,37 @@
+/*
+ * ALSA SoC STMP378x SPDIF codec driver
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#ifndef __STMP3XXX_SPDIF_CODEC_H
+#define __STMP3XXX_SPDIF_CODEC_H
+
+#define SPDIF_CTRL_L 0
+#define SPDIF_CTRL_H 1
+#define SPDIF_STAT_L 2
+#define SPDIF_STAT_H 3
+#define SPDIF_FRAMECTRL_L 4
+#define SPDIF_FRAMECTRL_H 5
+#define SPDIF_SRR_L 6
+#define SPDIF_SRR_H 7
+#define SPDIF_DEBUG_L 8
+#define SPDIF_DEBUG_H 9
+#define SPDIF_DATA_L 10
+#define SPDIF_DATA_H 11
+#define SPDIF_VERSION_L 12
+#define SPDIF_VERSION_H 13
+
+#define SPDIF_REGNUM 14
+
+extern struct snd_soc_dai stmp3xxx_spdif_codec_dai;
+extern struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx;
+
+#endif /* __STMP3XXX_SPDIF_CODEC_H */
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
index e7348d341b76..fa91d844e646 100644
--- a/sound/soc/codecs/wm8350.c
+++ b/sound/soc/codecs/wm8350.c
@@ -406,7 +406,6 @@ static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" };
static const char *wm8350_dacmutem[] = { "Normal", "Soft" };
static const char *wm8350_dacmutes[] = { "Fast", "Slow" };
-static const char *wm8350_dacfilter[] = { "Normal", "Sloping" };
static const char *wm8350_adcfilter[] = { "None", "High Pass" };
static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" };
static const char *wm8350_lr[] = { "Left", "Right" };
@@ -416,7 +415,6 @@ static const struct soc_enum wm8350_enum[] = {
SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
- SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
@@ -444,10 +442,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = {
0, 255, 0, dac_pcm_tlv),
SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
- SOC_ENUM("Playback PCM Filter", wm8350_enum[4]),
- SOC_ENUM("Capture PCM Filter", wm8350_enum[5]),
- SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]),
- SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]),
+ SOC_ENUM("Capture PCM Filter", wm8350_enum[4]),
+ SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]),
+ SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]),
SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume",
WM8350_ADC_DIGITAL_VOLUME_L,
WM8350_ADC_DIGITAL_VOLUME_R,
@@ -580,7 +577,7 @@ static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = {
SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv),
SOC_DAPM_SINGLE("PGA Capture Switch",
- WM8350_LEFT_INPUT_VOLUME, 14, 1, 0),
+ WM8350_LEFT_INPUT_VOLUME, 14, 1, 1),
};
/* Right Input Mixer */
@@ -590,7 +587,7 @@ static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = {
SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv),
SOC_DAPM_SINGLE("PGA Capture Switch",
- WM8350_RIGHT_INPUT_VOLUME, 14, 1, 0),
+ WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1),
};
/* Left Mic Mixer */
@@ -613,7 +610,7 @@ SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1);
/* Out4 Capture Mux */
static const struct snd_kcontrol_new wm8350_out4_capture_controls =
-SOC_DAPM_ENUM("Route", wm8350_enum[8]);
+SOC_DAPM_ENUM("Route", wm8350_enum[7]);
static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
@@ -993,6 +990,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai)
{
struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
~WM8350_AIF_WL_MASK;
@@ -1012,6 +1010,19 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
}
wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+
+ /* The sloping stopband filter is recommended for use with
+ * lower sample rates to improve performance.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (params_rate(params) < 24000)
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ }
+
return 0;
}
@@ -1116,7 +1127,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) &
~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000);
wm8350_codec_write(codec, WM8350_FLL_CONTROL_1,
- fll_1 | (fll_div.div << 8) | 0x50);
+ fll_1 | (fll_div.div << 8));
wm8350_codec_write(codec, WM8350_FLL_CONTROL_2,
(fll_div.ratio << 11) | (fll_div.
n & WM8350_FLL_N_MASK));
@@ -1454,13 +1465,13 @@ static int wm8350_probe(struct platform_device *pdev)
/* Latch VU bits & mute */
wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME,
- WM8350_OUT1_VU | WM8350_OUT1L_MUTE);
+ WM8350_OUT1_VU);
wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME,
- WM8350_OUT2_VU | WM8350_OUT2L_MUTE);
+ WM8350_OUT2_VU);
wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME,
- WM8350_OUT1_VU | WM8350_OUT1R_MUTE);
+ WM8350_OUT1_VU);
wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
- WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+ WM8350_OUT2_VU);
wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c
index 86c4b24db817..f6627793d98b 100644
--- a/sound/soc/codecs/wm8580.c
+++ b/sound/soc/codecs/wm8580.c
@@ -23,7 +23,10 @@
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
+#include <linux/spi/spi.h>
#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -160,6 +163,8 @@
#define WM8580_DAC_CONTROL5_MUTEALL 0x10
+#define WM8580_REG_DATA_LENGTH 9
+
/*
* wm8580 register cache
* We can't read the WM8580 register space when we
@@ -187,15 +192,19 @@ struct pll_state {
unsigned int out;
};
+#define WM8580_NUM_SUPPLIES 0
+static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = {
+};
+
/* codec private data */
struct wm8580_priv {
struct snd_soc_codec codec;
+ struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES];
u16 reg_cache[WM8580_MAX_REGISTER + 1];
struct pll_state a;
struct pll_state b;
};
-
/*
* read wm8580 register cache
*/
@@ -319,7 +328,6 @@ SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume",
WM8580_DIGITAL_ATTENUATION_DACL3,
WM8580_DIGITAL_ATTENUATION_DACR3,
0, 0xff, 0, dac_tlv),
-
SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0),
SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0),
SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0),
@@ -524,7 +532,7 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai,
(pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
reg = wm8580_read(codec, WM8580_PLLA4 + offset);
- reg &= ~0x3f;
+ reg &= ~0x3b;
reg |= pll_div.prescale | pll_div.postscale << 1 |
pll_div.freqmode << 3;
@@ -740,6 +748,33 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
return 0;
}
+static int wm8580_set_paif_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ unsigned int reg;
+
+ switch (clk_id) {
+ case WM8580_BCLK_CLKDIV:
+ reg = wm8580_read(codec, WM8580_PAIF1) &
+ ~WM8580_AIF_BCLKSEL_MASK;
+ wm8580_write(codec, WM8580_PAIF1, reg | freq);
+ reg = wm8580_read(codec, WM8580_PAIF2) &
+ ~WM8580_AIF_BCLKSEL_MASK;
+ wm8580_write(codec, WM8580_PAIF2, reg | freq);
+ break;
+ case WM8580_LRCLK_CLKDIV:
+ reg = wm8580_read(codec, WM8580_PAIF1) & ~0x07;
+ wm8580_write(codec, WM8580_PAIF1, reg | freq);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
{
struct snd_soc_codec *codec = codec_dai->codec;
@@ -795,6 +830,7 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec,
static struct snd_soc_dai_ops wm8580_dai_ops_playback = {
.hw_params = wm8580_paif_hw_params,
.set_fmt = wm8580_set_paif_dai_fmt,
+ .set_sysclk = wm8580_set_paif_dai_sysclk,
.set_clkdiv = wm8580_set_dai_clkdiv,
.set_pll = wm8580_set_dai_pll,
.digital_mute = wm8580_digital_mute,
@@ -922,11 +958,28 @@ static int wm8580_register(struct wm8580_priv *wm8580)
memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg));
+ for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++)
+ wm8580->supplies[i].supply = wm8580_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies),
+ wm8580->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies),
+ wm8580->supplies);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_regulator_get;
+ }
+
/* Get the codec into a known state */
ret = wm8580_write(codec, WM8580_RESET, 0);
if (ret != 0) {
dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
- goto err;
+ goto err_regulator_enable;
}
for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++)
@@ -939,7 +992,7 @@ static int wm8580_register(struct wm8580_priv *wm8580)
ret = snd_soc_register_codec(codec);
if (ret != 0) {
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
- goto err;
+ goto err_regulator_enable;
}
ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai));
@@ -952,6 +1005,10 @@ static int wm8580_register(struct wm8580_priv *wm8580)
err_codec:
snd_soc_unregister_codec(codec);
+err_regulator_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+err_regulator_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
err:
kfree(wm8580);
return ret;
@@ -962,6 +1019,8 @@ static void wm8580_unregister(struct wm8580_priv *wm8580)
wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF);
snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai));
snd_soc_unregister_codec(&wm8580->codec);
+ regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+ regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
kfree(wm8580);
wm8580_codec = NULL;
}
@@ -1012,6 +1071,92 @@ static struct i2c_driver wm8580_i2c_driver = {
};
#endif
+#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE)
+ /*!
+ * This function is called to transfer data to WM8580 on SPI.
+ *
+ * @param spi the SPI slave device(WM8580)
+ * @param buf the pointer to the data buffer
+ * @param len the length of the data to be transferred
+ *
+ * @return Returns 0 on success -1 on failure.
+ */
+static inline int spi_rw(void *control_data, char *data, int length)
+{
+ int ret;
+ struct spi_transfer t = {
+ .tx_buf = (const void *)data,
+ .rx_buf = (void *)data,
+ .len = length / 2,
+ .cs_change = 0,
+ .delay_usecs = 0,
+ };
+ struct spi_message m;
+ u8 temp = data[0];
+ data[0] = data[1];
+ data[1] = temp;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync((struct spi_device *)control_data, &m);
+ if (unlikely(ret < 0))
+ return ret;
+ else
+ return length;
+
+}
+
+/*!
+ * This function is called whenever the SPI slave device is detected.
+ *
+ * @param spi the SPI slave device
+ *
+ * @return Returns 0 on SUCCESS and error on FAILURE.
+ */
+static int __devinit wm8580_spi_probe(struct spi_device *spi)
+{
+ struct wm8580_priv *wm8580;
+ struct snd_soc_codec *codec;
+
+ wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL);
+ if (wm8580 == NULL)
+ return -ENOMEM;
+
+ codec = &wm8580->codec;
+
+ spi->bits_per_word = 16;
+ spi->mode = SPI_MODE_2;
+
+ spi_setup(spi);
+
+ spi_set_drvdata(spi, wm8580);
+ codec->hw_write = (hw_write_t) spi_rw;
+ codec->hw_read = (hw_read_t) spi_rw;
+ codec->control_data = spi;
+
+ codec->dev = &spi->dev;
+
+ return wm8580_register(wm8580);
+}
+
+static int __devinit wm8580_spi_remove(struct spi_device *spi)
+{
+ struct wm8580_priv *wm8580 = spi_get_drvdata(spi);
+ wm8580_unregister(wm8580);
+ return 0;
+}
+
+static struct spi_driver wm8580_spi_driver = {
+ .driver = {
+ .name = "wm8580_spi",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8580_spi_probe,
+ .remove = __devexit_p(wm8580_spi_remove),
+};
+#endif
+
static int __init wm8580_modinit(void)
{
int ret;
@@ -1023,6 +1168,13 @@ static int __init wm8580_modinit(void)
}
#endif
+#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE)
+ spi_register_driver(&wm8580_spi_driver);
+ if (ret != 0) {
+ pr_err("Failed to register WM8580 SPI driver: %d\n", ret);
+ }
+#endif
+
return 0;
}
module_init(wm8580_modinit);
@@ -1032,6 +1184,10 @@ static void __exit wm8580_exit(void)
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
i2c_del_driver(&wm8580_i2c_driver);
#endif
+
+#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE)
+ spi_unregister_driver(&wm8580_spi_driver);
+#endif
}
module_exit(wm8580_exit);
diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h
index 0dfb5ddde6a2..0c51ede6b13b 100644
--- a/sound/soc/codecs/wm8580.h
+++ b/sound/soc/codecs/wm8580.h
@@ -28,6 +28,15 @@
#define WM8580_CLKSRC_OSC 4
#define WM8580_CLKSRC_NONE 5
+/*clock divider id's */
+#define WM8580_BCLK_CLKDIV 0
+#define WM8580_LRCLK_CLKDIV 1
+
+struct wm8580_setup_data {
+ unsigned short spi;
+ unsigned short i2c_address;
+};
+
#define WM8580_DAI_PAIFRX 0
#define WM8580_DAI_PAIFTX 1
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
new file mode 100644
index 000000000000..ab76a841845a
--- /dev/null
+++ b/sound/soc/imx/Kconfig
@@ -0,0 +1,84 @@
+config SND_MXC_SOC
+ tristate "SoC Audio for the Freescale i.MX CPU"
+ depends on ARCH_MXC && SND
+ select SND_PCM
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the MXC I2S or SSP interface. You will also need
+ to select the audio interfaces to support below.
+
+if SND_MXC_SOC
+
+config SND_MXC_SOC_SSI
+ tristate
+
+config SND_MXC_SOC_ESAI
+ tristate
+
+config SND_MXC_SOC_AC97
+ tristate
+
+config SND_MXC_SOC_IRAM
+ bool "Locate Audio DMA playback buffers in IRAM"
+ help
+ Say Y if you don't want Audio playback buffers in external ram
+
+config SND_SOC_IMX_3STACK_WM8350
+ tristate "SoC Audio support for IMX - WM8350"
+ depends on MFD_WM8350
+ select SND_MXC_SOC_SSI
+ select SND_SOC_WM8350
+ help
+ Say Y if you want to add support for SoC audio on IMX 3STACK
+ with the WM8350.
+
+config SND_SOC_IMX_3STACK_SGTL5000
+ tristate "SoC Audio support for IMX - SGTL5000"
+ select SND_MXC_SOC_SSI
+ select SND_SOC_SGTL5000
+ help
+ Say Y if you want to add support for SoC audio on IMX 3STACK
+ with the SGTL5000.
+
+config SND_SOC_IMX_3STACK_AK4647
+ tristate "SoC Audio support for IMX - AK4647"
+ select SND_MXC_SOC_SSI
+ select SND_SOC_AK4647
+ help
+ Say Y if you want to add support for SoC audio on IMX 3STACK
+ with the AK4647.
+
+config SND_SOC_IMX_3STACK_WM8580
+ tristate "SoC Audio support for IMX - WM8580"
+ select SND_MXC_SOC_ESAI
+ select SND_SOC_WM8580
+ help
+ Say Y if you want to add support for Soc audio on IMX 3STACK
+ with the WM8580
+
+config SND_SOC_IMX_3STACK_AK5702
+ tristate "SoC Audio support for IMX - AK5702"
+ select SND_MXC_SOC_ESAI
+ select SND_SOC_AK5702
+ help
+ Say Y if you want to add support for Soc audio on IMX 3STACK
+ with the AK5702
+
+config SND_SOC_IMX_3STACK_BLUETOOTH
+ tristate "SoC Audio support for IMX - BLUETOOTH"
+ select SND_MXC_SOC_SSI
+ select SND_SOC_BLUETOOTH
+ help
+ Say Y if you want to add support for Soc audio on IMX 3STACK
+ with the BLUETOOTH
+
+config SND_SOC_IMX_3STACK_CS42888
+ tristate "SoC Audio support for IMX - CS42888"
+ select SND_MXC_SOC_ESAI
+ select SND_SOC_CS42888
+ help
+ Say Y if you want to add support for Soc audio on IMX 3STACK
+ with the CS42888
+
+
+endif
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
new file mode 100644
index 000000000000..8fdda5990e98
--- /dev/null
+++ b/sound/soc/imx/Makefile
@@ -0,0 +1,27 @@
+# i.MX Platform Support
+snd-soc-imx-objs := imx-pcm.o
+snd-soc-imx-ssi-objs := imx-ssi.o
+snd-soc-imx-esai-objs := imx-esai.o
+snd-soc-imx-ac97-objs := imx-ac97.o
+
+obj-$(CONFIG_SND_MXC_SOC) += snd-soc-imx.o
+obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-imx-ssi.o
+obj-$(CONFIG_SND_MXC_SOC_ESAI) += snd-soc-imx-esai.o
+obj-$(CONFIG_SND_MXC_SOC_AC97) += snd-soc-imx-ac97.o
+
+# i.MX Machine Support
+snd-soc-imx-3stack-wm8350-objs := imx-3stack-wm8350.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_WM8350) += snd-soc-imx-3stack-wm8350.o
+snd-soc-imx-3stack-sgtl5000-objs := imx-3stack-sgtl5000.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) += snd-soc-imx-3stack-sgtl5000.o
+snd-soc-imx-3stack-ak4647-objs := imx-3stack-ak4647.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_AK4647) += snd-soc-imx-3stack-ak4647.o
+snd-soc-imx-3stack-wm8580-objs := imx-3stack-wm8580.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_WM8580) += snd-soc-imx-3stack-wm8580.o
+snd-soc-imx-3stack-ak5702-objs := imx-3stack-ak5702.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_AK5702) += snd-soc-imx-3stack-ak5702.o
+snd-soc-imx-3stack-bt-objs := imx-3stack-bt.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH) += snd-soc-imx-3stack-bt.o
+snd-soc-imx-3stack-cs42888-objs := imx-3stack-cs42888.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_CS42888) += snd-soc-imx-3stack-cs42888.o
+
diff --git a/sound/soc/imx/imx-3stack-ak4647.c b/sound/soc/imx/imx-3stack-ak4647.c
new file mode 100644
index 000000000000..56c7a498e5b1
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-ak4647.c
@@ -0,0 +1,445 @@
+/*
+ * imx-3stack-ak4647.c -- SoC audio for imx_3stack
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/clock.h>
+#include <linux/regulator/consumer.h>
+
+#include "imx-pcm.h"
+#include "imx-ssi.h"
+
+#define AK4647_SSI_MASTER 1
+
+extern struct snd_soc_dai ak4647_hifi_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ak4647;
+
+static void headphone_detect_handler(struct work_struct *work);
+static DECLARE_WORK(hp_event, headphone_detect_handler);
+static int ak4647_jack_func;
+static int ak4647_spk_func;
+
+struct imx_3stack_priv {
+ struct platform_device *pdev;
+};
+
+static struct imx_3stack_priv card_priv;
+
+static void imx_3stack_init_dam(int ssi_port, int dai_port)
+{
+ /* AK4647 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */
+ unsigned int ssi_ptcr = 0;
+ unsigned int dai_ptcr = 0;
+ unsigned int ssi_pdcr = 0;
+ unsigned int dai_pdcr = 0;
+
+ /* reset port ssi_port & dai_port */
+ __raw_writel(0, DAM_PTCR(ssi_port));
+ __raw_writel(0, DAM_PTCR(dai_port));
+ __raw_writel(0, DAM_PDCR(ssi_port));
+ __raw_writel(0, DAM_PDCR(dai_port));
+
+ /* set to synchronous */
+ ssi_ptcr |= AUDMUX_PTCR_SYN;
+ dai_ptcr |= AUDMUX_PTCR_SYN;
+
+#if AK4647_SSI_MASTER
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TFSDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port);
+
+ /* set Tx Clock direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port);
+#else
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source ssi_port --> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TFSDIR;
+ dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
+
+ /* set Tx Clock direction and source ssi_port--> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
+#endif
+
+ __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
+ __raw_writel(dai_ptcr, DAM_PTCR(dai_port));
+ __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
+ __raw_writel(dai_pdcr, DAM_PDCR(dai_port));
+}
+
+static int imx_3stack_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ int ret = 0;
+ u32 dai_format;
+
+#if AK4647_SSI_MASTER
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+#else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+#endif
+
+ ssi_mode->sync_mode = 1;
+ if (channels == 1)
+ ssi_mode->network_mode = 0;
+ else
+ ssi_mode->network_mode = 1;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc, 2);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+ snd_soc_dai_set_sysclk(codec_dai, 0, rate, 0);
+
+ /* set codec BCLK division for sample rate */
+ snd_soc_dai_set_clkdiv(codec_dai, 0, 0);
+
+ return 0;
+}
+
+/*
+ * imx_3stack ak4647 HiFi DAI operations.
+ */
+static struct snd_soc_ops imx_3stack_hifi_ops = {
+ .hw_params = imx_3stack_hifi_hw_params,
+};
+
+static int ak4647_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = ak4647_jack_func;
+ return 0;
+}
+
+static int ak4647_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ if (ak4647_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ ak4647_jack_func = ucontrol->value.integer.value[0];
+
+ if (ak4647_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int ak4647_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = ak4647_spk_func;
+ return 0;
+}
+
+static int ak4647_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ if (ak4647_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ ak4647_spk_func = ucontrol->value.integer.value[0];
+ if (ak4647_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Line Out Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Line Out Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int spk_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->amp_enable == NULL)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ plat->amp_enable(1);
+ else
+ plat->amp_enable(0);
+ return 0;
+}
+
+/* imx_3stack card dapm widgets */
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* mic is connected to mic1 - with bias */
+ {"Left Input", NULL, "Mic1 Jack"},
+
+ /* Line in jack */
+ {"Left Input", NULL, "Line In Jack"},
+ {"Right Input", NULL, "Line In Jack"},
+
+ /* Headphone jack */
+ {"Headphone Jack", NULL, "HPL"},
+ {"Headphone Jack", NULL, "HPR"},
+
+ /* Line out jack */
+ {"Line Out Jack", NULL, "LOUT"},
+
+ /* Ext Spk */
+ {"Ext Spk", NULL, "LOUT"},
+
+};
+
+static const char *jack_function[] = { "off", "on" };
+
+static const char *spk_function[] = { "off", "on" };
+
+static const struct soc_enum ak4647_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new ak4647_card_controls[] = {
+ SOC_ENUM_EXT("Jack Function", ak4647_enum[0], ak4647_get_jack,
+ ak4647_set_jack),
+ SOC_ENUM_EXT("Speaker Function", ak4647_enum[1], ak4647_get_spk,
+ ak4647_set_spk),
+};
+
+static void headphone_detect_handler(struct work_struct *work)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+
+ sysfs_notify(&pdev->dev.kobj, NULL, "headphone");
+}
+
+static irqreturn_t imx_headphone_detect_handler(int irq, void *dev_id)
+{
+ schedule_work(&hp_event);
+ return IRQ_HANDLED;
+
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ unsigned int value;
+
+ value = plat->hp_status();
+
+ if (value == 0)
+ strcpy(buf, "speaker\n");
+ else
+ strcpy(buf, "headphone\n");
+
+ return strlen(buf);
+}
+
+DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static int imx_3stack_ak4647_init(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ for (i = 0; i < ARRAY_SIZE(ak4647_card_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&ak4647_card_controls[i],
+ codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "ak4647",
+ .stream_name = "ak4647",
+ .codec_dai = &ak4647_hifi_dai,
+ .init = imx_3stack_ak4647_init,
+ .ops = &imx_3stack_hifi_ops,
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_ak4647,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ * It also registers devices for platform DMA, I2S, SSP and registers an
+ * I2C driver to probe the codec.
+ */
+static int __init imx_3stack_ak4647_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data;
+ struct imx_3stack_priv *priv = &card_priv;
+ struct snd_soc_dai *ak4647_cpu_dai;
+ int ret = 0;
+
+ dev_data->init();
+
+ if (dev_data->src_port == 1)
+ ak4647_cpu_dai = imx_ssi_dai[0];
+ else
+ ak4647_cpu_dai = imx_ssi_dai[2];
+
+ imx_3stack_dai.cpu_dai = ak4647_cpu_dai;
+
+ imx_3stack_init_dam(dev_data->src_port, dev_data->ext_port);
+
+ ret = request_irq(dev_data->intr_id_hp, imx_headphone_detect_handler, 0,
+ "headphone", NULL);
+ if (ret < 0)
+ goto err;
+
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret < 0)
+ goto sysfs_err;
+
+ priv->pdev = pdev;
+ return ret;
+
+sysfs_err:
+ free_irq(dev_data->intr_id_hp, NULL);
+err:
+ return ret;
+}
+
+static int __devexit imx_3stack_ak4647_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data;
+ free_irq(dev_data->intr_id_hp, NULL);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+ return 0;
+}
+
+static struct platform_driver imx_3stack_ak4647_driver = {
+ .probe = imx_3stack_ak4647_probe,
+ .remove = __devexit_p(imx_3stack_ak4647_remove),
+ .driver = {
+ .name = "imx-3stack-ak4647",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_ak4647_driver);
+ if (ret < 0)
+ goto exit;
+
+ if (snd_soc_card_imx_3stack.codec == NULL) {
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 3);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+ platform_device_put(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_ak4647_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_ak4647_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC ak4647 imx_3stack");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-ak5702.c b/sound/soc/imx/imx-3stack-ak5702.c
new file mode 100644
index 000000000000..734399434aa9
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-ak5702.c
@@ -0,0 +1,225 @@
+/*
+ * imx-3stack-ak5702.c -- SoC audio for imx_3stack
+ *
+ * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/hardware.h>
+#include <mach/clock.h>
+
+#include "imx-pcm.h"
+#include "imx-esai.h"
+#include "../codecs/ak5702.h"
+
+struct imx_3stack_pcm_state {
+ int lr_clk_active;
+};
+
+static struct imx_3stack_pcm_state clk_state;
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ clk_state.lr_clk_active++;
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+
+ /* disable the PLL if there are no active Rx channels */
+ if (!codec_dai->active)
+ snd_soc_dai_set_pll(codec_dai, 0, 0, 0);
+ clk_state.lr_clk_active--;
+}
+
+static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ unsigned int rate = params_rate(params);
+ struct imx_esai *esai_mode = (struct imx_esai *)cpu_dai->private_data;
+ u32 dai_format;
+
+ if (clk_state.lr_clk_active > 1)
+ return 0;
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ esai_mode->sync_mode = 0;
+ esai_mode->network_mode = 1;
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffff, 2);
+
+ /* set the ESAI system clock as input */
+ snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+
+ /* set codec BCLK division */
+ snd_soc_dai_set_clkdiv(codec_dai, AK5702_BCLK_CLKDIV,
+ AK5702_BCLK_DIV_32);
+
+ snd_soc_dai_set_sysclk(codec_dai, 0, rate, SND_SOC_CLOCK_OUT);
+
+ snd_soc_dai_set_pll(codec_dai, 1, 12000000, 0);
+ return 0;
+}
+
+/*
+ * imx_3stack ak5702 DAI opserations.
+ */
+static struct snd_soc_ops imx_3stack_surround_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_surround_hw_params,
+};
+
+/* imx_3stack card dapm widgets */
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+/* example card audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line in jack */
+ {"ADCA Left Input", NULL, "Line In Jack"},
+ {"ADCA Right Input", NULL, "Line In Jack"},
+ {"ADCB Left Input", NULL, "Line In Jack"},
+ {"ADCB Right Input", NULL, "Line In Jack"},
+};
+
+static int imx_3stack_ak5702_init(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+ snd_soc_dapm_sync(codec);
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "ak5702",
+ .stream_name = "ak5702",
+ .codec_dai = &ak5702_dai,
+ .init = imx_3stack_ak5702_init,
+ .ops = &imx_3stack_surround_ops,
+};
+
+static int imx_3stack_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ kfree(socdev->codec_data);
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+ .remove = imx_3stack_card_remove,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_ak5702,
+};
+
+static int __devinit imx_3stack_ak5702_probe(struct platform_device *pdev)
+{
+ struct ak5702_setup_data *setup;
+
+ imx_3stack_dai.cpu_dai = &imx_esai_dai[2];
+
+ setup = kzalloc(sizeof(struct ak5702_setup_data), GFP_KERNEL);
+ setup->i2c_bus = 1;
+ setup->i2c_address = 0x13;
+ imx_3stack_snd_devdata.codec_data = setup;
+
+ return 0;
+}
+
+static int imx_3stack_ak5702_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_3stack_ak5702_driver = {
+ .probe = imx_3stack_ak5702_probe,
+ .remove = imx_3stack_ak5702_remove,
+ .driver = {
+ .name = "imx-3stack-ak5702",
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_ak5702_driver);
+ if (ret)
+ return -ENOMEM;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!imx_3stack_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (ret)
+ platform_device_put(imx_3stack_snd_device);
+
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_ak5702_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC ak5702 imx_3stack");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-bt.c b/sound/soc/imx/imx-3stack-bt.c
new file mode 100644
index 000000000000..4b6e36b629e6
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-bt.c
@@ -0,0 +1,256 @@
+/*
+ * imx-3stack-bt.c -- SoC bluetooth audio for imx_3stack
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "imx-pcm.h"
+#include "imx-ssi.h"
+#include "imx-3stack-bt.h"
+
+#define BT_SSI_MASTER 1
+
+struct imx_3stack_priv {
+ struct platform_device *pdev;
+ int active;
+};
+
+static struct imx_3stack_priv card_priv;
+
+static void imx_3stack_init_dam(int ssi_port, int dai_port)
+{
+ /* bt uses SSI1 or SSI2 via AUDMUX port dai_port for audio */
+ unsigned int ssi_ptcr = 0;
+ unsigned int dai_ptcr = 0;
+ unsigned int ssi_pdcr = 0;
+ unsigned int dai_pdcr = 0;
+
+ /* reset port ssi_port & dai_port */
+ __raw_writel(0, DAM_PTCR(ssi_port));
+ __raw_writel(0, DAM_PTCR(dai_port));
+ __raw_writel(0, DAM_PDCR(ssi_port));
+ __raw_writel(0, DAM_PDCR(dai_port));
+
+ /* set to synchronous */
+ ssi_ptcr |= AUDMUX_PTCR_SYN;
+ dai_ptcr |= AUDMUX_PTCR_SYN;
+
+#if BT_SSI_MASTER
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TFSDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port);
+
+ /* set Tx Clock direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port);
+#else
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source ssi_port --> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TFSDIR;
+ dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
+
+ /* set Tx Clock direction and source ssi_port--> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
+#endif
+
+ __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
+ __raw_writel(dai_ptcr, DAM_PTCR(dai_port));
+ __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
+ __raw_writel(dai_pdcr, DAM_PDCR(dai_port));
+}
+
+static int imx_3stack_bt_startup(struct snd_pcm_substream *substream)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+
+ if (!priv->active)
+ gpio_activate_bt_audio_port();
+ priv->active++;
+ return 0;
+}
+
+static int imx_3stack_bt_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ unsigned int channels = params_channels(params);
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ int ret = 0;
+ u32 dai_format;
+
+#if BT_SSI_MASTER
+ dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_IB_IF |
+ SND_SOC_DAIFMT_CBM_CFM;
+#else
+ dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_IB_IF |
+ SND_SOC_DAIFMT_CBS_CFS;
+#endif
+
+ ssi_mode->sync_mode = 1;
+ if (channels == 1)
+ ssi_mode->network_mode = 0;
+ else
+ ssi_mode->network_mode = 1;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc, 2);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static void imx_3stack_bt_shutdown(struct snd_pcm_substream *substream)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+
+ priv->active--;
+ if (!priv->active)
+ gpio_inactivate_bt_audio_port();
+}
+
+/*
+ * imx_3stack bt DAI opserations.
+ */
+static struct snd_soc_ops imx_3stack_bt_ops = {
+ .startup = imx_3stack_bt_startup,
+ .hw_params = imx_3stack_bt_hw_params,
+ .shutdown = imx_3stack_bt_shutdown,
+};
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "bluetooth",
+ .stream_name = "bluetooth",
+ .codec_dai = &bt_dai,
+ .ops = &imx_3stack_bt_ops,
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_bt,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ * It also registers devices for platform DMA, I2S, SSP and registers an
+ * I2C driver to probe the codec.
+ */
+static int __init imx_3stack_bt_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data;
+ struct imx_3stack_priv *priv = &card_priv;
+ struct snd_soc_dai *bt_cpu_dai;
+
+ if (dev_data->src_port == 1)
+ bt_cpu_dai = imx_ssi_dai[0];
+ else
+ bt_cpu_dai = imx_ssi_dai[2];
+
+ bt_cpu_dai->dev = &pdev->dev;
+ imx_3stack_dai.cpu_dai = bt_cpu_dai;
+
+ /* Configure audio port */
+ imx_3stack_init_dam(dev_data->src_port, dev_data->ext_port);
+
+ priv->pdev = pdev;
+ priv->active = 0;
+ return 0;
+
+}
+
+static int __devexit imx_3stack_bt_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_3stack_bt_driver = {
+ .probe = imx_3stack_bt_probe,
+ .remove = __devexit_p(imx_3stack_bt_remove),
+ .driver = {
+ .name = "imx-3stack-bt",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_bt_driver);
+ if (ret < 0)
+ goto exit;
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 4);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+ platform_device_put(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_bt_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_bt_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC bluetooth imx_3stack");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-bt.h b/sound/soc/imx/imx-3stack-bt.h
new file mode 100644
index 000000000000..4e1d3547dd56
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-bt.h
@@ -0,0 +1,21 @@
+/*
+ * imx-3stack-bt.h -- Bluetooth PCM driver header file for Freescale IMX
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _MXC_BTPCM_H
+#define _MXC_BTPCM_H
+
+extern struct snd_soc_dai bt_dai;
+extern struct snd_soc_codec_device soc_codec_dev_bt;
+#endif
diff --git a/sound/soc/imx/imx-3stack-cs42888.c b/sound/soc/imx/imx-3stack-cs42888.c
new file mode 100644
index 000000000000..935a4e544029
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-cs42888.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/soc-dai.h>
+
+#include <mach/hardware.h>
+#include <mach/clock.h>
+
+#include "imx-pcm.h"
+#include "imx-esai.h"
+#include "../codecs/cs42888.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+#include <linux/mxc_asrc.h>
+#endif
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+static unsigned int asrc_rates[] = {
+ 0,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+struct asrc_esai {
+ unsigned int cpu_dai_rates;
+ unsigned int codec_dai_rates;
+ enum asrc_pair_index asrc_index;
+ unsigned int output_sample_rate;
+};
+
+static struct asrc_esai asrc_esai_data;
+
+#endif
+
+struct imx_3stack_pcm_state {
+ int lr_clk_active;
+};
+
+static struct imx_3stack_pcm_state clk_state;
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ clk_state.lr_clk_active++;
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ asrc_esai_data.cpu_dai_rates = cpu_dai->playback.rates;
+ asrc_esai_data.codec_dai_rates = codec_dai->playback.rates;
+ cpu_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ codec_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ }
+#endif
+
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ codec_dai->playback.rates = asrc_esai_data.codec_dai_rates;
+ cpu_dai->playback.rates = asrc_esai_data.cpu_dai_rates;
+ asrc_release_pair(asrc_esai_data.asrc_index);
+ }
+#endif
+
+ clk_state.lr_clk_active--;
+}
+
+static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ unsigned int rate = params_rate(params);
+ u32 dai_format;
+ unsigned int mclk_freq = 0, lrclk_ratio = 0;
+ unsigned int channel = params_channels(params);
+ struct imx_esai *esai_mode = (struct imx_esai *)cpu_dai->private_data;
+ if (clk_state.lr_clk_active > 1)
+ return 0;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ unsigned int asrc_input_rate = rate;
+ struct mxc_runtime_data *pcm_data =
+ substream->runtime->private_data;
+ struct asrc_config config;
+ int retVal = 0;;
+
+ retVal = asrc_req_pair(channel, &asrc_esai_data.asrc_index);
+ if (retVal < 0) {
+ pr_err("Fail to request asrc pair\n");
+ return -1;
+ }
+
+ config.pair = asrc_esai_data.asrc_index;
+ config.channel_num = channel;
+ config.input_sample_rate = asrc_input_rate;
+ config.output_sample_rate = asrc_esai_data.output_sample_rate;
+ config.inclk = INCLK_NONE;
+ config.word_width = 32;
+ config.outclk = OUTCLK_ESAI_TX;
+ retVal = asrc_config_pair(&config);
+ if (retVal < 0) {
+ pr_err("Fail to config asrc\n");
+ asrc_release_pair(asrc_esai_data.asrc_index);
+ return retVal;
+ }
+ rate = asrc_esai_data.output_sample_rate;
+ pcm_data->asrc_index = asrc_esai_data.asrc_index;
+ pcm_data->asrc_enable = 1;
+ }
+#endif
+
+ switch (rate) {
+ case 32000:
+ lrclk_ratio = 3;
+ mclk_freq = 12288000;
+ break;
+ case 48000:
+ lrclk_ratio = 3;
+ mclk_freq = 12288000;
+ break;
+ case 64000:
+ lrclk_ratio = 1;
+ mclk_freq = 12288000;
+ break;
+ case 96000:
+ lrclk_ratio = 1;
+ mclk_freq = 12288000;
+ break;
+ case 128000:
+ lrclk_ratio = 1;
+ mclk_freq = 12288000;
+ break;
+ case 44100:
+ lrclk_ratio = 3;
+ mclk_freq = 11289600;
+ break;
+ case 88200:
+ lrclk_ratio = 1;
+ mclk_freq = 11289600;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ mclk_freq = 11289600;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ mclk_freq = 12288000;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+
+ dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ esai_mode->sync_mode = 0;
+ esai_mode->network_mode = 1;
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai, channel == 1 ? 0x1 : 0x3, 2);
+ /* set the ESAI system clock as output */
+ snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL,
+ mclk_freq, SND_SOC_CLOCK_OUT);
+ /* set the ratio */
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, 0);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, 0);
+ snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio);
+
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+ /* set codec Master clock */
+ snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_3stack_surround_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_surround_hw_params,
+};
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "AOUT1L"},
+ {"Line Out Jack", NULL, "AOUT1R"},
+ {"Line Out Jack", NULL, "AOUT2L"},
+ {"Line Out Jack", NULL, "AOUT2R"},
+ {"Line Out Jack", NULL, "AOUT3L"},
+ {"Line Out Jack", NULL, "AOUT3R"},
+ {"Line Out Jack", NULL, "AOUT4L"},
+ {"Line Out Jack", NULL, "AOUT4R"},
+ {"AIN1L", NULL, "Line In Jack"},
+ {"AIN1R", NULL, "Line In Jack"},
+ {"AIN2L", NULL, "Line In Jack"},
+ {"AIN2R", NULL, "Line In Jack"},
+};
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+static int asrc_func;
+
+static const char *asrc_function[] =
+ { "disable", "32KHz", "44.1KHz",
+ "48KHz", "64KHz", "88.2KHz", "96KHz", "176.4KHz", "192KHz"
+};
+
+static const struct soc_enum asrc_enum[] = {
+ SOC_ENUM_SINGLE_EXT(9, asrc_function),
+};
+
+static int asrc_get_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = asrc_func;
+ return 0;
+}
+
+static int asrc_set_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ if (asrc_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ asrc_func = ucontrol->value.enumerated.item[0];
+ asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4];
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new asrc_controls[] = {
+ SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate,
+ asrc_set_rate),
+};
+
+#endif
+
+static int imx_3stack_cs42888_init(struct snd_soc_codec *codec)
+{
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ int i;
+ int ret;
+ for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&asrc_controls[i], codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+ asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4];
+#endif
+
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "cs42888",
+ .stream_name = "cs42888",
+ .codec_dai = &cs42888_dai,
+ .init = imx_3stack_cs42888_init,
+ .ops = &imx_3stack_surround_ops,
+};
+
+static int imx_3stack_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ kfree(socdev->codec_data);
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+ .remove = imx_3stack_card_remove,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_device_cs42888,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_3stack_cs42888_probe(struct platform_device *pdev)
+{
+ imx_3stack_dai.cpu_dai = &imx_esai_dai[2];
+ imx_3stack_dai.cpu_dai->dev = &pdev->dev;
+
+ return 0;
+}
+
+static int __devexit imx_3stack_cs42888_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_3stack_cs42888_driver = {
+ .probe = imx_3stack_cs42888_probe,
+ .remove = __devexit_p(imx_3stack_cs42888_remove),
+ .driver = {
+ .name = "imx-3stack-cs42888",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&imx_3stack_cs42888_driver);
+ if (ret < 0)
+ goto exit;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret && snd_soc_card_imx_3stack.codec != NULL)
+ goto exit;
+
+ platform_device_unregister(imx_3stack_snd_device);
+err_device_alloc:
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_cs42888_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC cs42888 imx_3stack");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-sgtl5000.c b/sound/soc/imx/imx-3stack-sgtl5000.c
new file mode 100644
index 000000000000..fd117f98210a
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-sgtl5000.c
@@ -0,0 +1,689 @@
+/*
+ * imx-3stack-sgtl5000.c -- i.MX 3Stack Driver for Freescale SGTL5000 Codec
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 21th Oct 2008 Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/dma.h>
+#include <mach/clock.h>
+
+#include "../codecs/sgtl5000.h"
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+#include <linux/mxc_asrc.h>
+
+static unsigned int sgtl5000_rates[] = {
+ 0,
+ 32000,
+ 44100,
+ 48000,
+ 96000,
+};
+
+struct asrc_esai {
+ unsigned int cpu_dai_rates;
+ unsigned int codec_dai_rates;
+ enum asrc_pair_index asrc_index;
+ unsigned int output_sample_rate;
+};
+
+static struct asrc_esai asrc_ssi_data;
+#endif
+
+/* SSI BCLK and LRC master */
+#define SGTL5000_SSI_MASTER 1
+
+struct imx_3stack_priv {
+ int sysclk;
+ int hw;
+ struct platform_device *pdev;
+};
+
+static struct imx_3stack_priv card_priv;
+
+static int imx_3stack_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct imx_3stack_priv *priv = &card_priv;
+ unsigned int rate = params_rate(params);
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ int ret = 0;
+
+ unsigned int channels = params_channels(params);
+ u32 dai_format;
+
+ /* only need to do this once as capture and playback are sync */
+ if (priv->hw)
+ return 0;
+ priv->hw = 1;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if ((asrc_ssi_data.output_sample_rate != 0)
+ && (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) {
+ unsigned int asrc_input_rate = rate;
+ unsigned int channel = params_channels(params);
+ struct mxc_runtime_data *pcm_data =
+ substream->runtime->private_data;
+ struct asrc_config config;
+ struct mxc_audio_platform_data *plat;
+ struct imx_3stack_priv *priv = &card_priv;
+ int retVal = 0;
+ retVal = asrc_req_pair(channel, &asrc_ssi_data.asrc_index);
+ if (retVal < 0) {
+ pr_err("asrc_req_pair fail\n");
+ return -1;
+ }
+ config.pair = asrc_ssi_data.asrc_index;
+ config.channel_num = channel;
+ config.input_sample_rate = asrc_input_rate;
+ config.output_sample_rate = asrc_ssi_data.output_sample_rate;
+ config.inclk = INCLK_NONE;
+ config.word_width = 32;
+ plat = priv->pdev->dev.platform_data;
+ if (plat->src_port == 1)
+ config.outclk = OUTCLK_SSI1_TX;
+ else
+ config.outclk = OUTCLK_SSI2_TX;
+ retVal = asrc_config_pair(&config);
+ if (retVal < 0) {
+ pr_err("Fail to config asrc\n");
+ asrc_release_pair(asrc_ssi_data.asrc_index);
+ return retVal;
+ }
+ rate = asrc_ssi_data.output_sample_rate;
+ pcm_data->asrc_index = asrc_ssi_data.asrc_index;
+ pcm_data->asrc_enable = 1;
+ }
+#endif
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, priv->sysclk, 0);
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, rate, 0);
+
+#if SGTL5000_SSI_MASTER
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+#else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+#endif
+
+ ssi_mode->sync_mode = 1;
+ if (channels == 1)
+ ssi_mode->network_mode = 0;
+ else
+ ssi_mode->network_mode = 1;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc, 2);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SSI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (asrc_ssi_data.output_sample_rate != 0) {
+ struct snd_soc_pcm_runtime *rtd =
+ substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ asrc_ssi_data.cpu_dai_rates = cpu_dai->playback.rates;
+ asrc_ssi_data.codec_dai_rates =
+ codec_dai->playback.rates;
+ cpu_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ codec_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ }
+ }
+#endif
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (asrc_ssi_data.output_sample_rate != 0) {
+ struct snd_soc_pcm_runtime *rtd =
+ substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ codec_dai->playback.rates =
+ asrc_ssi_data.codec_dai_rates;
+ cpu_dai->playback.rates = asrc_ssi_data.cpu_dai_rates;
+ asrc_release_pair(asrc_ssi_data.asrc_index);
+ }
+ }
+#endif
+
+ priv->hw = 0;
+}
+
+/*
+ * imx_3stack SGTL5000 audio DAI opserations.
+ */
+static struct snd_soc_ops imx_3stack_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_audio_hw_params,
+};
+
+static void imx_3stack_init_dam(int ssi_port, int dai_port)
+{
+ unsigned int ssi_ptcr = 0;
+ unsigned int dai_ptcr = 0;
+ unsigned int ssi_pdcr = 0;
+ unsigned int dai_pdcr = 0;
+ /* SGTL5000 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */
+
+ /* reset port ssi_port & dai_port */
+ __raw_writel(0, DAM_PTCR(ssi_port));
+ __raw_writel(0, DAM_PTCR(dai_port));
+ __raw_writel(0, DAM_PDCR(ssi_port));
+ __raw_writel(0, DAM_PDCR(dai_port));
+
+ /* set to synchronous */
+ ssi_ptcr |= AUDMUX_PTCR_SYN;
+ dai_ptcr |= AUDMUX_PTCR_SYN;
+
+#if SGTL5000_SSI_MASTER
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TFSDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port);
+
+ /* set Tx Clock direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port);
+#else
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source ssi_port --> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TFSDIR;
+ dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
+
+ /* set Tx Clock direction and source ssi_port--> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
+#endif
+
+ __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
+ __raw_writel(dai_ptcr, DAM_PTCR(dai_port));
+ __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
+ __raw_writel(dai_pdcr, DAM_PDCR(dai_port));
+}
+
+/* imx_3stack machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MIC_IN", NULL, "Mic Jack"},
+
+ /* Line in Jack --> LINE_IN */
+ {"LINE_IN", NULL, "Line In Jack"},
+
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HP_OUT"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static int sgtl5000_jack_func;
+static int sgtl5000_spk_func;
+static int sgtl5000_line_in_func;
+
+static void headphone_detect_handler(struct work_struct *work)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ int hp_status;
+
+ sysfs_notify(&pdev->dev.kobj, NULL, "headphone");
+ hp_status = plat->hp_status();
+ if (hp_status)
+ set_irq_type(plat->hp_irq, IRQ_TYPE_EDGE_FALLING);
+ else
+ set_irq_type(plat->hp_irq, IRQ_TYPE_EDGE_RISING);
+ enable_irq(plat->hp_irq);
+}
+
+static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler);
+
+static irqreturn_t imx_headphone_detect_handler(int irq, void *data)
+{
+ disable_irq_nosync(irq);
+ schedule_delayed_work(&hp_event, msecs_to_jiffies(200));
+ return IRQ_HANDLED;
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ u16 hp_status;
+
+ /* determine whether hp is plugged in */
+ hp_status = plat->hp_status();
+
+ if (hp_status == 0)
+ strcpy(buf, "speaker\n");
+ else
+ strcpy(buf, "headphone\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const char *line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+ SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_jack_func;
+ return 0;
+}
+
+static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_jack_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_spk_func;
+ return 0;
+}
+
+static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_spk_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func;
+ return 0;
+}
+
+static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_line_in_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_line_in_func)
+ snd_soc_dapm_enable_pin(codec, "Line In Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Line In Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int spk_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->amp_enable == NULL)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ plat->amp_enable(1);
+ else
+ plat->amp_enable(0);
+
+ return 0;
+}
+
+/* imx_3stack card dapm widgets */
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new sgtl5000_machine_controls[] = {
+ SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack,
+ sgtl5000_set_jack),
+ SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk,
+ sgtl5000_set_spk),
+ SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in,
+ sgtl5000_set_line_in),
+};
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+static int asrc_func;
+
+static const char *asrc_function[] =
+ { "disable", "32KHz", "44.1KHz", "48KHz", "96KHz" };
+
+static const struct soc_enum asrc_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, asrc_function),
+};
+
+static int asrc_get_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = asrc_func;
+ return 0;
+}
+
+static int asrc_set_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ if (asrc_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ asrc_func = ucontrol->value.enumerated.item[0];
+ asrc_ssi_data.output_sample_rate = sgtl5000_rates[asrc_func];
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new asrc_controls[] = {
+ SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate,
+ asrc_set_rate),
+};
+#endif
+
+static int imx_3stack_sgtl5000_init(struct snd_soc_codec *codec)
+{
+ int i, ret;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&asrc_controls[i], codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+ asrc_ssi_data.output_sample_rate = sgtl5000_rates[asrc_func];
+#endif
+
+ /* Add imx_3stack specific controls */
+ for (i = 0; i < ARRAY_SIZE(sgtl5000_machine_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&sgtl5000_machine_controls[i],
+ codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Add imx_3stack specific widgets */
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ /* Set up imx_3stack specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_disable_pin(codec, "Line In Jack");
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* imx_3stack digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "SGTL5000",
+ .stream_name = "SGTL5000",
+ .codec_dai = &sgtl5000_dai,
+ .init = imx_3stack_sgtl5000_init,
+ .ops = &imx_3stack_ops,
+};
+
+static int imx_3stack_card_remove(struct platform_device *pdev)
+{
+ struct imx_3stack_priv *priv = &card_priv;
+ struct mxc_audio_platform_data *plat;
+ if (priv->pdev) {
+ plat = priv->pdev->dev.platform_data;
+ if (plat->finit)
+ plat->finit();
+ }
+
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+ .remove = imx_3stack_card_remove,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_sgtl5000,
+};
+
+static int __devinit imx_3stack_sgtl5000_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_3stack_priv *priv = &card_priv;
+ struct snd_soc_dai *sgtl5000_cpu_dai;
+ int ret = 0;
+
+ priv->pdev = pdev;
+
+ gpio_activate_audio_ports();
+ imx_3stack_init_dam(plat->src_port, plat->ext_port);
+
+ if (plat->src_port == 2)
+ sgtl5000_cpu_dai = imx_ssi_dai[2];
+ else
+ sgtl5000_cpu_dai = imx_ssi_dai[0];
+
+ imx_3stack_dai.cpu_dai = sgtl5000_cpu_dai;
+
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret < 0) {
+ pr_err("%s:failed to create driver_attr_headphone\n", __func__);
+ goto sysfs_err;
+ }
+
+ ret = -EINVAL;
+ if (plat->init && plat->init())
+ goto err_plat_init;
+
+ priv->sysclk = plat->sysclk;
+
+ /* The SGTL5000 has an internal reset that is deasserted 8 SYS_MCLK
+ cycles after all power rails have been brought up. After this time
+ communication can start */
+
+ if (plat->hp_status())
+ ret = request_irq(plat->hp_irq,
+ imx_headphone_detect_handler,
+ IRQ_TYPE_EDGE_FALLING, pdev->name, priv);
+ else
+ ret = request_irq(plat->hp_irq,
+ imx_headphone_detect_handler,
+ IRQ_TYPE_EDGE_RISING, pdev->name, priv);
+ if (ret < 0) {
+ pr_err("%s: request irq failed\n", __func__);
+ goto err_card_reg;
+ }
+
+ sgtl5000_jack_func = 1;
+ sgtl5000_spk_func = 1;
+ sgtl5000_line_in_func = 0;
+
+ return 0;
+
+err_card_reg:
+ if (plat->finit)
+ plat->finit();
+err_plat_init:
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+sysfs_err:
+ return ret;
+}
+
+static int imx_3stack_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_3stack_priv *priv = &card_priv;
+
+ free_irq(plat->hp_irq, priv);
+
+ if (plat->finit)
+ plat->finit();
+
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+
+ return 0;
+}
+
+static struct platform_driver imx_3stack_sgtl5000_audio_driver = {
+ .probe = imx_3stack_sgtl5000_probe,
+ .remove = imx_3stack_sgtl5000_remove,
+ .driver = {
+ .name = "imx-3stack-sgtl5000",
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_sgtl5000_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 2);
+ if (!imx_3stack_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+
+ if (ret)
+ platform_device_put(imx_3stack_snd_device);
+
+ return ret;
+}
+
+static void __exit imx_3stack_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_sgtl5000_audio_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_init);
+module_exit(imx_3stack_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SGTL5000 Driver for i.MX 3STACK");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-wm8350.c b/sound/soc/imx/imx-3stack-wm8350.c
new file mode 100644
index 000000000000..1cd51d31e128
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-wm8350.c
@@ -0,0 +1,696 @@
+/*
+ * imx-3stack-wm8350.c -- i.MX 3Stack Driver for Wolfson WM8350 Codec
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Copyright (C) 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 19th Jun 2007 Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/audio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/dma.h>
+#include <mach/clock.h>
+
+#include "../codecs/wm8350.h"
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+void gpio_activate_audio_ports(void);
+
+/* SSI BCLK and LRC master */
+#define WM8350_SSI_MASTER 1
+
+struct imx_3stack_priv {
+ int lr_clk_active;
+ int playback_active;
+ int capture_active;
+ struct platform_device *pdev;
+ struct wm8350 *wm8350;
+};
+
+static struct imx_3stack_priv machine_priv;
+
+struct _wm8350_audio {
+ unsigned int channels;
+ snd_pcm_format_t format;
+ unsigned int rate;
+ unsigned int sysclk;
+ unsigned int bclkdiv;
+ unsigned int clkdiv;
+ unsigned int lr_rate;
+};
+
+/* in order of power consumption per rate (lowest first) */
+static const struct _wm8350_audio wm8350_audio[] = {
+ /* 16bit mono modes */
+
+ {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+ WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+ WM8350_BCLK_DIV_24, WM8350_DACDIV_6, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+ WM8350_BCLK_DIV_12, WM8350_DACDIV_3, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+ WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+ WM8350_BCLK_DIV_16, WM8350_DACDIV_4, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+ {1, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+
+ /* 16 bit stereo modes */
+ {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+ WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+ WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+ WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+ WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+ WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+ {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+ WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+
+ /* 24bit stereo modes */
+ {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+ {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
+ WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+};
+
+#if WM8350_SSI_MASTER
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->card->codec;
+ struct wm8350 *wm8350 = codec->control_data;
+ struct imx_3stack_priv *priv = &machine_priv;
+
+ /* In master mode the LR clock can come from either the DAC or ADC.
+ * We use the LR clock from whatever stream is enabled first.
+ */
+
+ if (!priv->lr_clk_active) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_LRC_ADC_SEL);
+ else
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_LRC_ADC_SEL);
+ }
+ priv->lr_clk_active++;
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ priv->capture_active = 1;
+ else
+ priv->playback_active = 1;
+ return 0;
+}
+#else
+#define imx_3stack_startup NULL
+#endif
+
+static int imx_3stack_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct imx_3stack_priv *priv = &machine_priv;
+ int ret = 0;
+ int i, found = 0;
+ snd_pcm_format_t format = params_format(params);
+ unsigned int rate = params_rate(params);
+ unsigned int channels = params_channels(params);
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ u32 dai_format;
+
+ /* only need to do this once as capture and playback are sync */
+ if (priv->lr_clk_active > 1)
+ return 0;
+
+ /* find the correct audio parameters */
+ for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
+ if (rate == wm8350_audio[i].rate &&
+ format == wm8350_audio[i].format &&
+ channels == wm8350_audio[i].channels) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ printk(KERN_ERR "%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+
+#if WM8350_SSI_MASTER
+ /* codec FLL input is 32768 kHz from MCLK */
+ snd_soc_dai_set_pll(codec_dai, 0, 32768, wm8350_audio[i].sysclk);
+#else
+ /* codec FLL input is rate from DAC LRC */
+ snd_soc_dai_set_pll(codec_dai, 0, rate, wm8350_audio[i].sysclk);
+#endif
+
+#if WM8350_SSI_MASTER
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ ssi_mode->sync_mode = 1;
+ if (channels == 1)
+ ssi_mode->network_mode = 0;
+ else
+ ssi_mode->network_mode = 1;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set cpu DAI configuration */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ dai_format &= ~SND_SOC_DAIFMT_INV_MASK;
+ /* Invert frame to switch mic from right channel to left */
+ dai_format |= SND_SOC_DAIFMT_NB_IF;
+ }
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ channels);
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set 32KHZ as the codec system clock for DAC and ADC */
+ snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_32K,
+ wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+#else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 0xfffffffe : 0xfffffffc,
+ channels);
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set DAC LRC as the codec system clock for DAC and ADC */
+ snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_DAC,
+ wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+#endif
+
+ /* set the SSI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+ /* set codec BCLK division for sample rate */
+ snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
+ wm8350_audio[i].bclkdiv);
+
+ /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_DACLR_CLKDIV,
+ wm8350_audio[i].lr_rate);
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_ADCLR_CLKDIV,
+ wm8350_audio[i].lr_rate);
+
+ /* now configure DAC and ADC clocks */
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
+
+ snd_soc_dai_set_clkdiv(codec_dai,
+ WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
+
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->socdev->card->codec;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct wm8350 *wm8350 = codec->control_data;
+
+ /* disable the PLL if there are no active Tx or Rx channels */
+ if (!codec_dai->active)
+ snd_soc_dai_set_pll(codec_dai, 0, 0, 0);
+ priv->lr_clk_active--;
+
+ /*
+ * We need to keep track of active streams in master mode and
+ * switch LRC source if necessary.
+ */
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ priv->capture_active = 0;
+ else
+ priv->playback_active = 0;
+
+ if (priv->capture_active)
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_LRC_ADC_SEL);
+ else if (priv->playback_active)
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_LRC_ADC_SEL);
+}
+
+/*
+ * imx_3stack WM8350 HiFi DAI operations.
+ */
+static struct snd_soc_ops imx_3stack_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_audio_hw_params,
+};
+
+static void imx_3stack_init_dam(int ssi_port, int dai_port)
+{
+ unsigned int ssi_ptcr = 0;
+ unsigned int dai_ptcr = 0;
+ unsigned int ssi_pdcr = 0;
+ unsigned int dai_pdcr = 0;
+ /* WM8350 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */
+
+ /* reset port ssi_port & dai_port */
+ __raw_writel(0, DAM_PTCR(ssi_port));
+ __raw_writel(0, DAM_PTCR(dai_port));
+ __raw_writel(0, DAM_PDCR(ssi_port));
+ __raw_writel(0, DAM_PDCR(dai_port));
+
+ /* set to synchronous */
+ ssi_ptcr |= AUDMUX_PTCR_SYN;
+ dai_ptcr |= AUDMUX_PTCR_SYN;
+
+#if WM8350_SSI_MASTER
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TFSDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port);
+
+ /* set Tx Clock direction and source dai_port--> ssi_port output */
+ ssi_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port);
+#else
+ /* set Rx sources ssi_port <--> dai_port */
+ ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+ dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+ /* set Tx frame direction and source ssi_port --> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TFSDIR;
+ dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
+
+ /* set Tx Clock direction and source ssi_port--> dai_port output */
+ dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
+ dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
+#endif
+
+ __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
+ __raw_writel(dai_ptcr, DAM_PTCR(dai_port));
+ __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
+ __raw_writel(dai_pdcr, DAM_PDCR(dai_port));
+}
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* SiMIC --> IN1LN (with automatic bias) via SP1 */
+ {"IN1RP", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "SiMIC"},
+
+ /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
+ {"IN1LN", NULL, "Mic Bias"},
+ {"IN1LP", NULL, "Mic1 Jack"},
+ {"Mic Bias", NULL, "Mic1 Jack"},
+
+ /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
+ {"IN1RN", NULL, "Mic2 Jack"},
+ {"IN1RP", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Mic2 Jack"},
+
+ /* Line in Jack --> AUX (L+R) */
+ {"IN3R", NULL, "Line In Jack"},
+ {"IN3L", NULL, "Line In Jack"},
+
+ /* Out1 --> Headphone Jack */
+ {"Headphone Jack", NULL, "OUT1R"},
+ {"Headphone Jack", NULL, "OUT1L"},
+
+ /* Out1 --> Line Out Jack */
+ {"Line Out Jack", NULL, "OUT2R"},
+ {"Line Out Jack", NULL, "OUT2L"},
+};
+
+static int wm8350_jack_func;
+static int wm8350_spk_func;
+
+static void headphone_detect_handler(struct work_struct *work)
+{
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct wm8350 *wm8350 = priv->wm8350;
+
+ sysfs_notify(&pdev->dev.kobj, NULL, "headphone");
+ wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+}
+
+static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler);
+
+static void imx_3stack_jack_handler(struct wm8350 *wm8350, int irq, void *data)
+{
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+ schedule_delayed_work(&hp_event, msecs_to_jiffies(200));
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_3stack_priv *priv = &machine_priv;
+ u16 reg;
+
+ reg = wm8350_reg_read(priv->wm8350, WM8350_JACK_PIN_STATUS);
+
+ if (reg & WM8350_JACK_R_LVL)
+ strcpy(buf, "speaker\n");
+ else
+ strcpy(buf, "headphone\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static const char *jack_function[] = { "off", "on"
+};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const struct soc_enum wm8350_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static int wm8350_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = wm8350_jack_func;
+ return 0;
+}
+
+static int wm8350_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (wm8350_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ wm8350_jack_func = ucontrol->value.enumerated.item[0];
+ if (wm8350_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int wm8350_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = wm8350_spk_func;
+ return 0;
+}
+
+static int wm8350_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (wm8350_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ wm8350_spk_func = ucontrol->value.enumerated.item[0];
+ if (wm8350_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Line Out Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Line Out Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int spk_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct platform_device *pdev = priv->pdev;
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->amp_enable == NULL)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ plat->amp_enable(1);
+ else
+ plat->amp_enable(0);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("SiMIC", NULL),
+ SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new wm8350_machine_controls[] = {
+ SOC_ENUM_EXT("Jack Function", wm8350_enum[0], wm8350_get_jack,
+ wm8350_set_jack),
+ SOC_ENUM_EXT("Speaker Function", wm8350_enum[1], wm8350_get_spk,
+ wm8350_set_spk),
+};
+
+static int imx_3stack_wm8350_init(struct snd_soc_codec *codec)
+{
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct wm8350 *wm8350 = priv->wm8350;
+ int i, ret;
+
+ codec->control_data = wm8350;
+
+ /* Add imx_3stack specific controls */
+ for (i = 0; i < ARRAY_SIZE(wm8350_machine_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&wm8350_machine_controls[i],
+ codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Add imx_3stack specific widgets */
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ /* Set up imx_3stack specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+
+}
+
+/* imx_3stack digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "WM8350",
+ .stream_name = "WM8350",
+ .codec_dai = &wm8350_dai,
+ .init = imx_3stack_wm8350_init,
+ .ops = &imx_3stack_ops,
+};
+
+static int imx_3stack_machine_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct wm8350 *wm8350 = priv->wm8350;
+
+ socdev->codec_data = wm8350;
+
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+ .probe = imx_3stack_machine_probe,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_wm8350,
+};
+
+static int __devinit imx_3stack_wm8350_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct wm8350 *wm8350 = plat->priv;
+ struct snd_soc_dai *wm8350_cpu_dai;
+ int ret = 0;
+ u16 reg;
+
+ priv->pdev = pdev;
+ priv->wm8350 = wm8350;
+
+ gpio_activate_audio_ports();
+ imx_3stack_init_dam(plat->src_port, plat->ext_port);
+
+ if (plat->src_port == 2)
+ wm8350_cpu_dai = imx_ssi_dai[2];
+ else
+ wm8350_cpu_dai = imx_ssi_dai[0];
+
+ imx_3stack_dai.cpu_dai = wm8350_cpu_dai;
+
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret < 0) {
+ pr_err("%s:failed to create driver_attr_headphone\n", __func__);
+ return ret;
+ }
+
+ /* enable slow clock gen for jack detect */
+ reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_4);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_4, reg | WM8350_TOCLK_ENA);
+ /* enable jack detect */
+ reg = wm8350_reg_read(wm8350, WM8350_JACK_DETECT);
+ wm8350_reg_write(wm8350, WM8350_JACK_DETECT, reg | WM8350_JDR_ENA);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+ imx_3stack_jack_handler, NULL);
+ wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+
+ wm8350_jack_func = 1;
+ wm8350_spk_func = 1;
+
+ return 0;
+}
+
+static int imx_3stack_wm8350_remove(struct platform_device *pdev)
+{
+ struct imx_3stack_priv *priv = &machine_priv;
+ struct wm8350 *wm8350 = priv->wm8350;
+
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+
+ return 0;
+}
+
+static struct platform_driver imx_3stack_wm8350_audio_driver = {
+ .probe = imx_3stack_wm8350_probe,
+ .remove = __devexit_p(imx_3stack_wm8350_remove),
+ .driver = {
+ .name = "wm8350-imx-3stack-audio",
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_wm8350_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!imx_3stack_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+
+ if (ret)
+ platform_device_put(imx_3stack_snd_device);
+
+ return ret;
+}
+
+static void __exit imx_3stack_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_wm8350_audio_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_init);
+module_exit(imx_3stack_exit);
+
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_DESCRIPTION("PMIC WM8350 Driver for i.MX 3STACK");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-3stack-wm8580.c b/sound/soc/imx/imx-3stack-wm8580.c
new file mode 100644
index 000000000000..f3f9f85ca0f0
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-wm8580.c
@@ -0,0 +1,433 @@
+/*
+ * imx-3stack-wm8580.c -- SoC 5.1 audio for imx_3stack
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/hardware.h>
+#include <mach/clock.h>
+
+#include "imx-pcm.h"
+#include "imx-esai.h"
+#include "../codecs/wm8580.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+#include <linux/mxc_asrc.h>
+#endif
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+static unsigned int asrc_rates[] = {
+ 0,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+struct asrc_esai {
+ unsigned int cpu_dai_rates;
+ unsigned int codec_dai_rates;
+ enum asrc_pair_index asrc_index;
+ unsigned int output_sample_rate;
+};
+
+static struct asrc_esai asrc_esai_data;
+
+#endif
+
+struct imx_3stack_pcm_state {
+ int lr_clk_active;
+};
+
+static struct imx_3stack_pcm_state clk_state;
+
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+ clk_state.lr_clk_active++;
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ asrc_esai_data.cpu_dai_rates = cpu_dai->playback.rates;
+ asrc_esai_data.codec_dai_rates = codec_dai->playback.rates;
+ cpu_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ codec_dai->playback.rates =
+ SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT;
+ }
+#endif
+
+ return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ codec_dai->playback.rates = asrc_esai_data.codec_dai_rates;
+ cpu_dai->playback.rates = asrc_esai_data.cpu_dai_rates;
+ asrc_release_pair(asrc_esai_data.asrc_index);
+ }
+#endif
+
+ /* disable the PLL if there are no active Tx or Rx channels */
+ if (!codec_dai->active)
+ snd_soc_dai_set_pll(codec_dai, 0, 0, 0);
+ clk_state.lr_clk_active--;
+}
+
+static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *pcm_link = rtd->dai;
+ struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = pcm_link->codec_dai;
+ unsigned int rate = params_rate(params);
+ u32 dai_format;
+ unsigned int pll_out = 0, lrclk_ratio = 0;
+ unsigned int channel = params_channels(params);
+ struct imx_esai *esai_mode = (struct imx_esai *)cpu_dai->private_data;
+
+ if (clk_state.lr_clk_active > 1)
+ return 0;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (asrc_esai_data.output_sample_rate >= 32000) {
+ unsigned int asrc_input_rate = rate;
+ struct mxc_runtime_data *pcm_data =
+ substream->runtime->private_data;
+ struct asrc_config config;
+ int retVal = 0;;
+
+ retVal = asrc_req_pair(channel, &asrc_esai_data.asrc_index);
+ if (retVal < 0) {
+ pr_err("Fail to request asrc pair\n");
+ return -1;
+ }
+
+ config.pair = asrc_esai_data.asrc_index;
+ config.channel_num = channel;
+ config.input_sample_rate = asrc_input_rate;
+ config.output_sample_rate = asrc_esai_data.output_sample_rate;
+ config.inclk = INCLK_NONE;
+ config.word_width = 32;
+ config.outclk = OUTCLK_ESAI_TX;
+ retVal = asrc_config_pair(&config);
+ if (retVal < 0) {
+ pr_err("Fail to config asrc\n");
+ asrc_release_pair(asrc_esai_data.asrc_index);
+ return retVal;
+ }
+ rate = asrc_esai_data.output_sample_rate;
+ pcm_data->asrc_index = asrc_esai_data.asrc_index;
+ pcm_data->asrc_enable = 1;
+ }
+#endif
+
+ switch (rate) {
+ case 8000:
+ lrclk_ratio = 5;
+ pll_out = 6144000;
+ break;
+ case 11025:
+ lrclk_ratio = 4;
+ pll_out = 5644800;
+ break;
+ case 16000:
+ lrclk_ratio = 3;
+ pll_out = 6144000;
+ break;
+ case 32000:
+ lrclk_ratio = 3;
+ pll_out = 12288000;
+ break;
+ case 48000:
+ lrclk_ratio = 2;
+ pll_out = 12288000;
+ break;
+ case 64000:
+ lrclk_ratio = 1;
+ pll_out = 12288000;
+ break;
+ case 96000:
+ lrclk_ratio = 2;
+ pll_out = 24576000;
+ break;
+ case 128000:
+ lrclk_ratio = 1;
+ pll_out = 24576000;
+ break;
+ case 22050:
+ lrclk_ratio = 4;
+ pll_out = 11289600;
+ break;
+ case 44100:
+ lrclk_ratio = 2;
+ pll_out = 11289600;
+ break;
+ case 88200:
+ lrclk_ratio = 0;
+ pll_out = 11289600;
+ break;
+ case 176400:
+ lrclk_ratio = 0;
+ pll_out = 22579200;
+ break;
+ case 192000:
+ lrclk_ratio = 0;
+ pll_out = 24576000;
+ break;
+ default:
+ pr_info("Rate not support.\n");
+ return -EINVAL;;
+ }
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ esai_mode->sync_mode = 0;
+ esai_mode->network_mode = 1;
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+ /* set i.MX active slot mask */
+ snd_soc_dai_set_tdm_slot(cpu_dai, channel == 1 ? 0x1 : 0x3, 2);
+
+ /* set the ESAI system clock as input (unused) */
+ snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+
+ snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, WM8580_CLKSRC_PLLA);
+ snd_soc_dai_set_clkdiv(codec_dai, WM8580_DAC_CLKSEL,
+ WM8580_CLKSRC_PLLA);
+
+ /* set codec LRCLK and BCLK */
+ snd_soc_dai_set_sysclk(codec_dai, WM8580_BCLK_CLKDIV, 0,
+ SND_SOC_CLOCK_OUT);
+ snd_soc_dai_set_sysclk(codec_dai, WM8580_LRCLK_CLKDIV, lrclk_ratio,
+ SND_SOC_CLOCK_OUT);
+
+ snd_soc_dai_set_pll(codec_dai, 1, 12000000, pll_out);
+ return 0;
+}
+
+static struct snd_soc_ops imx_3stack_surround_ops = {
+ .startup = imx_3stack_startup,
+ .shutdown = imx_3stack_shutdown,
+ .hw_params = imx_3stack_surround_hw_params,
+};
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "VOUT1L"},
+ {"Line Out Jack", NULL, "VOUT1R"},
+ {"Line Out Jack", NULL, "VOUT2L"},
+ {"Line Out Jack", NULL, "VOUT2R"},
+ {"Line Out Jack", NULL, "VOUT3L"},
+ {"Line Out Jack", NULL, "VOUT3R"},
+};
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+static int asrc_func;
+
+static const char *asrc_function[] =
+ { "disable", "32KHz", "44.1KHz",
+ "48KHz", "64KHz", "88.2KHz", "96KHz", "176.4KHz", "192KHz"
+};
+
+static const struct soc_enum asrc_enum[] = {
+ SOC_ENUM_SINGLE_EXT(9, asrc_function),
+};
+
+static int asrc_get_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = asrc_func;
+ return 0;
+}
+
+static int asrc_set_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ if (asrc_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ asrc_func = ucontrol->value.enumerated.item[0];
+ asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4];
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new asrc_controls[] = {
+ SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate,
+ asrc_set_rate),
+};
+
+#endif
+
+static int imx_3stack_wm8580_init(struct snd_soc_codec *codec)
+{
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ int i;
+ int ret;
+ for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_soc_cnew(&asrc_controls[i], codec, NULL));
+ if (ret < 0)
+ return ret;
+ }
+ asrc_esai_data.output_sample_rate = asrc_rates[asrc_func + 4];
+#endif
+
+ snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_3stack_dai = {
+ .name = "wm8580",
+ .stream_name = "wm8580",
+ .codec_dai = wm8580_dai,
+ .init = imx_3stack_wm8580_init,
+ .ops = &imx_3stack_surround_ops,
+};
+
+static int imx_3stack_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ kfree(socdev->codec_data);
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-3stack",
+ .platform = &imx_soc_platform,
+ .dai_link = &imx_3stack_dai,
+ .num_links = 1,
+ .remove = imx_3stack_card_remove,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+ .card = &snd_soc_card_imx_3stack,
+ .codec_dev = &soc_codec_dev_wm8580,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int __devinit imx_3stack_wm8580_probe(struct platform_device *pdev)
+{
+ struct wm8580_setup_data *setup;
+
+ imx_3stack_dai.cpu_dai = &imx_esai_dai[2];
+ imx_3stack_dai.cpu_dai->dev = &pdev->dev;
+
+ setup = kzalloc(sizeof(struct wm8580_setup_data), GFP_KERNEL);
+ setup->spi = 1;
+ imx_3stack_snd_devdata.codec_data = setup;
+
+ return 0;
+}
+
+static int __devexit imx_3stack_wm8580_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_3stack_wm8580_driver = {
+ .probe = imx_3stack_wm8580_probe,
+ .remove = __devexit_p(imx_3stack_wm8580_remove),
+ .driver = {
+ .name = "imx-3stack-wm8580",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_asoc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_3stack_wm8580_driver);
+ if (ret < 0)
+ goto exit;
+ imx_3stack_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!imx_3stack_snd_device)
+ goto err_device_alloc;
+ platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+ imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+ ret = platform_device_add(imx_3stack_snd_device);
+ if (0 == ret)
+ goto exit;
+
+ platform_device_put(imx_3stack_snd_device);
+ err_device_alloc:
+ platform_driver_unregister(&imx_3stack_wm8580_driver);
+ exit:
+ return ret;
+}
+
+static void __exit imx_3stack_asoc_exit(void)
+{
+ platform_driver_unregister(&imx_3stack_wm8580_driver);
+ platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_asoc_init);
+module_exit(imx_3stack_asoc_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC wm8580 imx_3stack");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-ac97.c b/sound/soc/imx/imx-ac97.c
new file mode 100644
index 000000000000..019e7f9a8fe8
--- /dev/null
+++ b/sound/soc/imx/imx-ac97.c
@@ -0,0 +1,564 @@
+/*
+ * imx-ac97.c -- AC97 driver for Freescale IMX
+ *
+ *
+ * Copyright 2009 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 26th Nov. 2009 Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <mach/clock.h>
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#include <linux/delay.h>
+
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+static DEFINE_MUTEX(ac97_mutex);
+static DECLARE_COMPLETION(ac97_completion);
+
+struct imx_ac97_runtime {
+ u32 id;
+ u32 playback;
+ u32 capture;
+};
+
+static struct imx_ac97_runtime imx_ac97_para;
+static struct imx_ssi imx_ac97_data[2];
+
+#define IMX_SSI_DUMP 0
+#if IMX_SSI_DUMP
+#define SSI_DUMP() \
+ do { \
+ printk(KERN_INFO "dump @ %s\n", __func__); \
+ printk(KERN_INFO "scr %x\t, %x\n", \
+ __raw_readl(SSI1_SCR), __raw_readl(SSI2_SCR)); \
+ printk(KERN_INFO "sisr %x\t, %x\n", \
+ __raw_readl(SSI1_SISR), __raw_readl(SSI2_SISR)); \
+ printk(KERN_INFO "stcr %x\t, %x\n", \
+ __raw_readl(SSI1_STCR), __raw_readl(SSI2_STCR)); \
+ printk(KERN_INFO "srcr %x\t, %x\n", \
+ __raw_readl(SSI1_SRCR), __raw_readl(SSI2_SRCR)); \
+ printk(KERN_INFO "stccr %x\t, %x\n", \
+ __raw_readl(SSI1_STCCR), __raw_readl(SSI2_STCCR)); \
+ printk(KERN_INFO "srccr %x\t, %x\n", \
+ __raw_readl(SSI1_SRCCR), __raw_readl(SSI2_SRCCR)); \
+ printk(KERN_INFO "sfcsr %x\t, %x\n", \
+ __raw_readl(SSI1_SFCSR), __raw_readl(SSI2_SFCSR)); \
+ printk(KERN_INFO "stmsk %x\t, %x\n", \
+ __raw_readl(SSI1_STMSK), __raw_readl(SSI2_STMSK)); \
+ printk(KERN_INFO "srmsk %x\t, %x\n", \
+ __raw_readl(SSI1_SRMSK), __raw_readl(SSI2_SRMSK)); \
+ printk(KERN_INFO "sier %x\t, %x\n", \
+ __raw_readl(SSI1_SIER), __raw_readl(SSI2_SIER)); \
+ printk(KERN_INFO "sacnt %x\t, %x\n", \
+ __raw_readl(SSI1_SACNT), __raw_readl(SSI2_SACNT)); \
+ printk(KERN_INFO "sacdd %x\t, %x\n", \
+ __raw_readl(SSI1_SACADD), __raw_readl(SSI2_SACADD)); \
+ printk(KERN_INFO "sacdat %x\t, %x\n", \
+ __raw_readl(SSI1_SACDAT), __raw_readl(SSI2_SACDAT)); \
+ printk(KERN_INFO "satag %x\t, %x\n", \
+ __raw_readl(SSI1_SATAG), __raw_readl(SSI2_SATAG)); \
+ printk(KERN_INFO "saccst %x\t, %x\n", \
+ __raw_readl(SSI1_SACCST), __raw_readl(SSI2_SACCST)); \
+ printk(KERN_INFO "saccen %x\t, %x\n", \
+ __raw_readl(SSI1_SACCEN), __raw_readl(SSI2_SACCEN)); \
+ printk(KERN_INFO "saccdis %x\t, %x\n", \
+ __raw_readl(SSI1_SACCDIS), __raw_readl(SSI2_SACCDIS)); \
+ } while (0);
+#else
+#define SSI_DUMP()
+#endif
+
+/*
+ * Read register value from codec, the read command is sent from
+ * AC97 slot 2 - 3. The register value is sent back at slot 2 -3
+ * in next RX frame.
+ */
+static unsigned short imx_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+ u32 sacdat, sier, scr;
+ mutex_lock(&ac97_mutex);
+ if (imx_ac97_para.id == IMX_DAI_AC97_1) {
+ sier = __raw_readl(SSI1_SIER);
+ scr = __raw_readl(SSI1_SCR);
+ __raw_writel((reg << 12), SSI1_SACADD);
+ __raw_writel(sier | SSI_SIER_CMDDU_EN, SSI1_SIER);
+ __raw_writel(SSI_SACNT_RD | __raw_readl(SSI1_SACNT),
+ SSI1_SACNT);
+ __raw_writel(scr | SSI_SCR_TE | SSI_SCR_RE, SSI1_SCR);
+ wait_for_completion_timeout(&ac97_completion, HZ);
+ sacdat = __raw_readl(SSI1_SACDAT);
+ __raw_writel(sier, SSI1_SIER);
+ __raw_writel(scr, SSI1_SCR);
+ } else {
+ sier = __raw_readl(SSI2_SIER);
+ scr = __raw_readl(SSI2_SCR);
+ __raw_writel((reg << 12), SSI2_SACADD);
+ __raw_writel(sier | SSI_SIER_CMDDU_EN, SSI2_SIER);
+ __raw_writel(SSI_SACNT_RD | __raw_readl(SSI2_SACNT),
+ SSI2_SACNT);
+ __raw_writel(scr | SSI_SCR_TE | SSI_SCR_RE, SSI2_SCR);
+ wait_for_completion_timeout(&ac97_completion, HZ);
+ sacdat = __raw_readl(SSI2_SACDAT);
+ __raw_writel(sier, SSI2_SIER);
+ __raw_writel(scr, SSI2_SCR);
+ }
+ mutex_unlock(&ac97_mutex);
+ return (unsigned short)(sacdat >> 4);
+
+}
+
+/*
+ * This fucntion is used to send command to codec.
+ */
+static void imx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ u32 scr;
+ mutex_lock(&ac97_mutex);
+ if (imx_ac97_para.id == IMX_DAI_AC97_1) {
+ scr = __raw_readl(SSI1_SCR);
+ __raw_writel(reg << 12, SSI1_SACADD);
+ __raw_writel(val << 4, SSI1_SACDAT);
+ __raw_writel(SSI_SACNT_WR | __raw_readl(SSI1_SACNT),
+ SSI1_SACNT);
+ __raw_writel(scr | SSI_SCR_TE | SSI_SCR_RE, SSI1_SCR);
+ udelay(100);
+ __raw_writel(scr, SSI1_SCR);
+ } else {
+ scr = __raw_readl(SSI2_SCR);
+ __raw_writel(reg << 12, SSI2_SACADD);
+ __raw_writel(val << 4, SSI2_SACDAT);
+ __raw_writel(SSI_SACNT_WR | __raw_readl(SSI2_SACNT),
+ SSI2_SACNT);
+ __raw_writel(scr | SSI_SCR_TE | SSI_SCR_RE, SSI2_SCR);
+ udelay(100);
+ __raw_writel(scr, SSI1_SCR);
+ }
+ mutex_unlock(&ac97_mutex);
+
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = imx_ac97_read,
+ .write = imx_ac97_write,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static struct clk *ssi1_clk;
+static struct clk *ssi2_clk;
+
+static int imx_ac97_hw_tx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ u32 channel = params_channels(params);
+ u32 stccr, sier;
+
+ if (cpu_dai->id == IMX_DAI_AC97_1) {
+ stccr =
+ __raw_readl(SSI1_STCCR) & ~(SSI_STCCR_WL_MASK |
+ SSI_STCCR_DC_MASK);
+ sier = __raw_readl(SSI1_SIER);
+ } else {
+ stccr =
+ __raw_readl(SSI2_STCCR) & ~(SSI_STCCR_WL_MASK |
+ SSI_STCCR_DC_MASK);
+ sier = __raw_readl(SSI2_SIER);
+ }
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ stccr |= SSI_STCCR_WL(16) | SSI_STCCR_DC(0x0C);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ stccr |= SSI_STCCR_WL(20) | SSI_STCCR_DC(0x0C);
+ break;
+ }
+ if (ssi_mode->ac97_rx_slots)
+ __raw_writel(ssi_mode->ac97_rx_slots, SSI1_SACCEN);
+ else
+ __raw_writel(((1 << channel) - 1) << (10 - channel),
+ SSI1_SACCEN);
+
+ sier |= SSI_SIER_TDMAE;
+
+ if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) {
+ __raw_writel(stccr, SSI1_STCCR);
+ __raw_writel(sier, SSI1_SIER);
+ } else {
+ __raw_writel(stccr, SSI2_STCCR);
+ __raw_writel(sier, SSI2_SIER);
+ }
+
+ return 0;
+}
+
+static int imx_ac97_hw_rx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
+ u32 channel = params_channels(params);
+ u32 srccr, sier;
+
+ if (cpu_dai->id == IMX_DAI_AC97_1) {
+ srccr = __raw_readl(SSI1_SRCCR);
+ sier = __raw_readl(SSI1_SIER);
+ } else {
+ srccr = __raw_readl(SSI2_SRCCR);
+ sier = __raw_readl(SSI2_SIER);
+ }
+ srccr &= ~(SSI_SRCCR_WL_MASK | SSI_SRCCR_DC_MASK);
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ srccr |= SSI_SRCCR_WL(16) | SSI_SRCCR_DC(0x0C);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ srccr |= SSI_SRCCR_WL(20) | SSI_SRCCR_DC(0x0C);
+ break;
+ }
+
+ if (ssi_mode->ac97_tx_slots)
+ __raw_writel(ssi_mode->ac97_tx_slots, SSI1_SACCEN);
+ else
+ __raw_writel(((1 << channel) - 1) << (10 - channel),
+ SSI1_SACCEN);
+
+ /* enable interrupts */
+ sier |= SSI_SIER_RDMAE;
+
+ if (cpu_dai->id == IMX_DAI_AC97_1) {
+ __raw_writel(srccr, SSI1_SRCCR);
+ __raw_writel(sier, SSI1_SIER);
+ } else {
+ __raw_writel(srccr, SSI2_SRCCR);
+ __raw_writel(sier, SSI2_SIER);
+ }
+ return 0;
+}
+
+static int imx_ac97_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ u32 rate = params_rate(params);
+ u32 sacnt;
+
+ if (cpu_dai->id == IMX_DAI_AC97_1)
+ sacnt = __raw_readl(SSI1_SACNT);
+ else
+ sacnt = __raw_readl(SSI2_SACNT);
+
+ switch (rate) {
+ case 8000:
+ sacnt |= SSI_SACNT_FV | SSI_SACNT_FRDIV(0x05);
+ break;
+ case 11050:
+ sacnt |= SSI_SACNT_FV | SSI_SACNT_FRDIV(0x03);
+ break;
+ case 16000:
+ sacnt |= SSI_SACNT_FV | SSI_SACNT_FRDIV(0x02);
+ break;
+ case 22050:
+ sacnt |= SSI_SACNT_FV | SSI_SACNT_FRDIV(0x01);
+ break;
+ case 44100:
+ case 48000:
+ sacnt |= SSI_SACNT_FV | SSI_SACNT_FRDIV(0x00);
+ break;
+ }
+
+ if (cpu_dai->id == IMX_DAI_AC97_1)
+ __raw_writel(sacnt, SSI1_SACNT);
+ else
+ __raw_writel(sacnt, SSI2_SACNT);
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return imx_ac97_hw_tx_params(substream, params, cpu_dai);
+ else
+ return imx_ac97_hw_rx_params(substream, params, cpu_dai);
+}
+
+static int imx_ac97_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ u32 scr, i, reg;
+
+ if (cpu_dai->id == IMX_DAI_AC97_1)
+ scr = __raw_readl(SSI1_SCR);
+ else
+ scr = __raw_readl(SSI2_SCR);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = 0; i < 12; i++)
+ __raw_writel(0x0, SSI1_STX0);
+ } else {
+ for (i = 0; i < 12; i++)
+ reg = __raw_readl(SSI1_SRX0);
+ }
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ imx_ac97_para.playback = 1;
+ else
+ imx_ac97_para.capture = 1;
+ scr |= SSI_SCR_TE | SSI_SCR_RE;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ imx_ac97_para.playback = 0;
+ else
+ imx_ac97_para.capture = 0;
+
+ if ((imx_ac97_para.playback == 0)
+ && (imx_ac97_para.capture == 0))
+ scr &= ~(SSI_SCR_TE | SSI_SCR_RE);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1)
+ __raw_writel(scr, SSI1_SCR);
+ else
+ __raw_writel(scr, SSI2_SCR);
+ SSI_DUMP();
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_ac97_suspend(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* do we need to disable any clocks? */
+
+ return 0;
+}
+
+static int imx_ac97_resume(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* do we need to enable any clocks? */
+
+ return 0;
+}
+#else
+#define imx_ac97_suspend NULL
+#define imx_ac97_resume NULL
+#endif
+
+static irqreturn_t ssi1_irq(int irq, void *dev_id)
+{
+ u32 sier, sisr, reg;
+ sier = __raw_readl(SSI1_SIER);
+ sisr = __raw_readl(SSI1_SISR);
+ if ((sier & SSI_SIER_CMDDU_EN) && (sisr & SSI_SISR_CMDDU)) {
+ reg = __raw_readl(SSI1_SACADD);
+ reg = __raw_readl(SSI1_SACDAT);
+ complete(&ac97_completion);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ssi2_irq(int irq, void *dev_id)
+{
+ u32 sier, sisr, reg;
+ sier = __raw_readl(SSI1_SIER);
+ sisr = __raw_readl(SSI1_SISR);
+ if ((sier & SSI_SIER_CMDDU_EN) && (sisr & SSI_SISR_CMDDU)) {
+ reg = __raw_readl(SSI2_SACADD);
+ reg = __raw_readl(SSI2_SACDAT);
+ complete(&ac97_completion);
+ }
+ return IRQ_HANDLED;
+}
+
+static int imx_ac97_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+
+ u32 stccr;
+ stccr = SSI_STCCR_WL(16) | SSI_STCCR_DC(0x0C);
+
+ if (!strcmp(dai->name, "imx-ac97-1")) {
+ dai->id = IMX_DAI_AC97_1;
+ ssi1_clk = clk_get(NULL, "ssi_clk.0");
+ clk_enable(ssi1_clk);
+
+ __raw_writel(SSI_SCR_SSIEN, SSI1_SCR);
+
+ __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)),
+ SSI1_SFCSR);
+
+ __raw_writel(stccr, SSI1_STCCR);
+ __raw_writel(stccr, SSI1_SRCCR);
+ __raw_writel(SSI_SACNT_AC97EN, SSI1_SACNT);
+ } else if (!strcmp(dai->name, "imx-ac97-2")) {
+ dai->id = IMX_DAI_AC97_2;
+ ssi2_clk = clk_get(NULL, "ssi_clk.1");
+ clk_enable(ssi2_clk);
+
+ __raw_writel(SSI_SCR_SSIEN, SSI2_SCR);
+
+ __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)),
+ SSI2_SFCSR);
+
+ __raw_writel(stccr, SSI2_STCCR);
+ __raw_writel(stccr, SSI2_SRCCR);
+
+ __raw_writel(SSI_SACNT_AC97EN, SSI2_SACNT);
+ } else {
+ printk(KERN_ERR "%s: invalid device %s\n", __func__, dai->name);
+ return -ENODEV;
+ }
+
+ if (!strcmp(dai->name, "imx-ac97-1")) {
+ if (request_irq(MXC_INT_SSI1, ssi1_irq, 0, "ssi1", dai)) {
+ printk(KERN_ERR
+ "%s: failure requesting irq %s\n",
+ __func__, "ssi1");
+ return -EBUSY;
+ }
+ }
+
+ if (!strcmp(dai->name, "imx-ac97-2")) {
+ if (request_irq(MXC_INT_SSI2, ssi2_irq, 0, "ssi2", dai)) {
+ printk(KERN_ERR
+ "%s: failure requesting irq %s\n",
+ __func__, "ssi2");
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static void imx_ac97_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ if (!strcmp(dai->name, "imx-ac97-1"))
+ free_irq(MXC_INT_SSI1, dai);
+
+ if (!strcmp(dai->name, "imx-ac97-2"))
+ free_irq(MXC_INT_SSI2, dai);
+}
+
+#define IMX_AC97_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000)
+
+#define IMX_AC97_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE)
+
+static struct snd_soc_dai_ops imx_ac97_dai_ops = {
+ .hw_params = imx_ac97_hw_params,
+ .trigger = imx_ac97_trigger,
+};
+
+struct snd_soc_dai imx_ac97_dai[] = {
+ {
+ .name = "imx-ac97-1",
+ .id = 0,
+ .ac97_control = 1,
+ .probe = imx_ac97_probe,
+ .remove = imx_ac97_remove,
+ .suspend = imx_ac97_suspend,
+ .resume = imx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 10,
+ .rates = IMX_AC97_RATES,
+ .formats = IMX_AC97_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = IMX_AC97_RATES,
+ .formats = IMX_AC97_FORMATS,
+ },
+ .ops = &imx_ac97_dai_ops,
+ .private_data = &imx_ac97_data[IMX_DAI_AC97_1],
+ },
+ {
+ .name = "imx-ac97-2",
+ .id = 1,
+ .ac97_control = 1,
+ .probe = imx_ac97_probe,
+ .remove = imx_ac97_remove,
+ .suspend = imx_ac97_suspend,
+ .resume = imx_ac97_resume,
+ .playback = {
+ .stream_name = "AC97 Playback",
+ .channels_min = 1,
+ .channels_max = 10,
+ .rates = IMX_AC97_RATES,
+ .formats = IMX_AC97_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AC97 Capture",
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = IMX_AC97_RATES,
+ .formats = IMX_AC97_FORMATS,
+ },
+ .ops = &imx_ac97_dai_ops,
+ .private_data = &imx_ac97_data[IMX_DAI_AC97_2],
+ },
+};
+EXPORT_SYMBOL_GPL(imx_ac97_dai);
+
+static int __init imx_ac97_init(void)
+{
+ return snd_soc_register_dais(imx_ac97_dai, ARRAY_SIZE(imx_ac97_dai));
+}
+
+static void __exit imx_ac97_exit(void)
+{
+ snd_soc_unregister_dais(imx_ac97_dai, ARRAY_SIZE(imx_ac97_dai));
+}
+
+module_init(imx_ac97_init);
+module_exit(imx_ac97_exit);
+
+MODULE_DESCRIPTION("i.MX ASoC AC97 driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-esai.c b/sound/soc/imx/imx-esai.c
new file mode 100644
index 000000000000..0c6b234f29ec
--- /dev/null
+++ b/sound/soc/imx/imx-esai.c
@@ -0,0 +1,709 @@
+/*
+ * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+ /*!
+ * @file imx-esai.c
+ * @brief this file implements the esai interface
+ * in according to ASoC architeture
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "imx-esai.h"
+#include "imx-pcm.h"
+
+static int imx_esai_txrx_state;
+static struct imx_esai imx_esai_priv[3];
+static void __iomem *esai_ioaddr;
+
+static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 ecr, tccr, rccr;
+
+ ecr = __raw_readl(ESAI_ECR);
+ tccr = __raw_readl(ESAI_TCCR);
+ rccr = __raw_readl(ESAI_RCCR);
+
+ if (dir == SND_SOC_CLOCK_IN) {
+ if (cpu_dai->id & IMX_DAI_ESAI_TX)
+ tccr &=
+ ~(ESAI_TCCR_THCKD | ESAI_TCCR_TCKD |
+ ESAI_TCCR_TFSD);
+ if (cpu_dai->id & IMX_DAI_ESAI_RX)
+ rccr &=
+ ~(ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD |
+ ESAI_RCCR_RFSD);
+ } else {
+ tccr |=
+ ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD;
+ rccr |=
+ ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD;
+ if (clk_id == ESAI_CLK_FSYS) {
+ if (cpu_dai->id & IMX_DAI_ESAI_TX)
+ ecr &= ~(ESAI_ECR_ETI | ESAI_ECR_ETO);
+ if (cpu_dai->id & IMX_DAI_ESAI_RX)
+ ecr &= ~(ESAI_ECR_ERI | ESAI_ECR_ERO);
+ } else if (clk_id == ESAI_CLK_EXTAL) {
+ ecr |= ESAI_ECR_ETI;
+ ecr &= ~ESAI_ECR_ETO;
+ ecr |= ESAI_ECR_ERI;
+ ecr &= ~ESAI_ECR_ERO;
+ }
+ }
+
+ __raw_writel(ecr, ESAI_ECR);
+ if (cpu_dai->id & IMX_DAI_ESAI_TX)
+ __raw_writel(tccr, ESAI_TCCR);
+ if (cpu_dai->id & IMX_DAI_ESAI_RX)
+ __raw_writel(rccr, ESAI_RCCR);
+
+ ESAI_DUMP();
+
+ return 0;
+}
+
+static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 tccr, rccr;
+
+ tccr = __raw_readl(ESAI_TCCR);
+ rccr = __raw_readl(ESAI_RCCR);
+
+ switch (div_id) {
+ case ESAI_TX_DIV_PSR:
+ tccr &= ESAI_TCCR_TPSR_MASK;
+ if (div)
+ tccr |= ESAI_TCCR_TPSR_BYPASS;
+ else
+ tccr &= ~ESAI_TCCR_TPSR_DIV8;
+ break;
+ case ESAI_TX_DIV_PM:
+ tccr &= ESAI_TCCR_TPM_MASK;
+ tccr |= ESAI_TCCR_TPM(div);
+ break;
+ case ESAI_TX_DIV_FP:
+ tccr &= ESAI_TCCR_TFP_MASK;
+ tccr |= ESAI_TCCR_TFP(div);
+ break;
+ case ESAI_RX_DIV_PSR:
+ rccr &= ESAI_RCCR_RPSR_MASK;
+ if (div)
+ rccr |= ESAI_RCCR_RPSR_BYPASS;
+ else
+ rccr &= ~ESAI_RCCR_RPSR_DIV8;
+ break;
+ case ESAI_RX_DIV_PM:
+ rccr &= ESAI_RCCR_RPM_MASK;
+ rccr |= ESAI_RCCR_RPM(div);
+ break;
+ case ESAI_RX_DIV_FP:
+ rccr &= ESAI_RCCR_RFP_MASK;
+ rccr |= ESAI_RCCR_RFP(div);
+ break;
+ return -EINVAL;
+ }
+ if (cpu_dai->id & IMX_DAI_ESAI_TX)
+ __raw_writel(tccr, ESAI_TCCR);
+ if (cpu_dai->id & IMX_DAI_ESAI_RX)
+ __raw_writel(rccr, ESAI_RCCR);
+ return 0;
+}
+
+/*
+ * ESAI Network Mode or TDM slots configuration.
+ */
+static int imx_esai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int mask, int slots)
+{
+ u32 tccr, rccr;
+
+ if (cpu_dai->id & IMX_DAI_ESAI_TX) {
+ tccr = __raw_readl(ESAI_TCCR);
+
+ tccr &= ESAI_TCCR_TDC_MASK;
+ tccr |= ESAI_TCCR_TDC(slots - 1);
+
+ __raw_writel(tccr, ESAI_TCCR);
+ __raw_writel((mask & 0xffff), ESAI_TSMA);
+ __raw_writel(((mask >> 16) & 0xffff), ESAI_TSMB);
+ }
+
+ if (cpu_dai->id & IMX_DAI_ESAI_RX) {
+ rccr = __raw_readl(ESAI_RCCR);
+
+ rccr &= ESAI_RCCR_RDC_MASK;
+ rccr |= ESAI_RCCR_RDC(slots - 1);
+
+ __raw_writel(rccr, ESAI_RCCR);
+ __raw_writel((mask & 0xffff), ESAI_RSMA);
+ __raw_writel(((mask >> 16) & 0xffff), ESAI_RSMB);
+ }
+
+ ESAI_DUMP();
+
+ return 0;
+}
+
+/*
+ * ESAI DAI format configuration.
+ */
+static int imx_esai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct imx_esai *esai_mode = (struct imx_esai *)cpu_dai->private_data;
+ u32 tcr, tccr, rcr, rccr, saicr;
+
+ tcr = __raw_readl(ESAI_TCR);
+ tccr = __raw_readl(ESAI_TCCR);
+ rcr = __raw_readl(ESAI_RCR);
+ rccr = __raw_readl(ESAI_RCCR);
+ saicr = __raw_readl(ESAI_SAICR);
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data on rising edge of bclk, frame low 1clk before data */
+ tcr &= ~ESAI_TCR_TFSL;
+ tcr |= ESAI_TCR_TFSR;
+ rcr &= ~ESAI_RCR_RFSL;
+ rcr |= ESAI_RCR_RFSR;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data on rising edge of bclk, frame high with data */
+ tcr &= ~(ESAI_TCR_TFSL | ESAI_TCR_TFSR);
+ rcr &= ~(ESAI_RCR_RFSL | ESAI_RCR_RFSR);
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* data on rising edge of bclk, frame high with data */
+ tcr |= ESAI_TCR_TFSL;
+ rcr |= ESAI_RCR_RFSL;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* data on rising edge of bclk, frame high 1clk before data */
+ tcr |= ESAI_TCR_TFSL;
+ rcr |= ESAI_RCR_RFSL;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ tccr |= ESAI_TCCR_TFSP;
+ tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP);
+ rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP);
+ rccr |= ESAI_RCCR_RFSP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP);
+ rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP;
+ rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ tccr &= ~ESAI_TCCR_TFSP;
+ tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP;
+ rccr &= ~ESAI_RCCR_RFSP;
+ rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ tccr &= ~(ESAI_TCCR_TFSD | ESAI_TCCR_TCKD);
+ rccr &= ~(ESAI_RCCR_RFSD | ESAI_RCCR_RCKD);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ tccr &= ~ESAI_TCCR_TFSD;
+ tccr |= ESAI_TCCR_TCKD;
+ rccr &= ~ESAI_RCCR_RFSD;
+ rccr |= ESAI_RCCR_RCKD;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ tccr &= ~ESAI_TCCR_TCKD;
+ tccr |= ESAI_TCCR_TFSD;
+ rccr &= ~ESAI_RCCR_RCKD;
+ rccr |= ESAI_RCCR_RFSD;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ tccr |= (ESAI_TCCR_TFSD | ESAI_TCCR_TCKD);
+ rccr |= (ESAI_RCCR_RFSD | ESAI_RCCR_RCKD);
+ }
+
+ /* sync */
+ if (esai_mode->sync_mode)
+ saicr |= ESAI_SAICR_SYNC;
+ else
+ saicr &= ~ESAI_SAICR_SYNC;
+
+ tcr &= ESAI_TCR_TMOD_MASK;
+ rcr &= ESAI_RCR_RMOD_MASK;
+ if (esai_mode->network_mode) {
+ tcr |= ESAI_TCR_TMOD_NETWORK;
+ rcr |= ESAI_RCR_RMOD_NETWORK;
+ } else {
+ tcr |= ESAI_TCR_TMOD_NORMAL;
+ rcr |= ESAI_RCR_RMOD_NORMAL;
+ }
+
+ if (cpu_dai->id & IMX_DAI_ESAI_TX) {
+ __raw_writel(tcr, ESAI_TCR);
+ __raw_writel(tccr, ESAI_TCCR);
+ }
+ if (cpu_dai->id & IMX_DAI_ESAI_RX) {
+ __raw_writel(rcr, ESAI_RCR);
+ __raw_writel(rccr, ESAI_RCCR);
+ }
+
+ __raw_writel(saicr, ESAI_SAICR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+static struct clk *esai_clk;
+
+static int fifo_err_counter;
+
+static irqreturn_t esai_irq(int irq, void *dev_id)
+{
+ if (fifo_err_counter++ % 1000 == 0)
+ printk(KERN_ERR
+ "esai_irq SAISR %x fifo_errs=%d\n",
+ __raw_readl(ESAI_SAISR), fifo_err_counter);
+ return IRQ_HANDLED;
+}
+
+static int imx_esai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ if (cpu_dai->playback.active && (cpu_dai->id & IMX_DAI_ESAI_TX))
+ return 0;
+ if (cpu_dai->capture.active && (cpu_dai->id & IMX_DAI_ESAI_RX))
+ return 0;
+
+ if (!(imx_esai_txrx_state & IMX_DAI_ESAI_TXRX)) {
+ if (request_irq(MXC_INT_ESAI, esai_irq, 0, "esai", NULL)) {
+ pr_err("%s: failure requesting esai irq\n", __func__);
+ return -EBUSY;
+ }
+ clk_enable(esai_clk);
+ __raw_writel(ESAI_ECR_ERST, ESAI_ECR);
+ __raw_writel(ESAI_ECR_ESAIEN, ESAI_ECR);
+
+ __raw_writel(ESAI_GPIO_ESAI, ESAI_PRRC);
+ __raw_writel(ESAI_GPIO_ESAI, ESAI_PCRC);
+ }
+
+ if (cpu_dai->id & IMX_DAI_ESAI_TX) {
+ imx_esai_txrx_state |= IMX_DAI_ESAI_TX;
+ __raw_writel(ESAI_TCR_TPR, ESAI_TCR);
+ }
+ if (cpu_dai->id & IMX_DAI_ESAI_RX) {
+ imx_esai_txrx_state |= IMX_DAI_ESAI_RX;
+ __raw_writel(ESAI_RCR_RPR, ESAI_RCR);
+ }
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * This function is called to initialize the TX port before enable
+ * the tx port.
+ */
+static int imx_esai_hw_tx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ u32 tcr, tfcr;
+ unsigned int channels;
+
+ tcr = __raw_readl(ESAI_TCR);
+ tfcr = __raw_readl(ESAI_TFCR);
+
+ tfcr |= ESAI_TFCR_TFR;
+ __raw_writel(tfcr, ESAI_TFCR);
+ tfcr &= ~ESAI_TFCR_TFR;
+ /* DAI data (word) size */
+ tfcr &= ESAI_TFCR_TWA_MASK;
+ tcr &= ESAI_TCR_TSWS_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ tfcr |= ESAI_WORD_LEN_16;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ tfcr |= ESAI_WORD_LEN_20;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ tfcr |= ESAI_WORD_LEN_24;
+ tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24;
+ break;
+ }
+
+ channels = params_channels(params);
+ tfcr &= ESAI_TFCR_TE_MASK;
+ tfcr |= ESAI_TFCR_TE(channels);
+
+ tfcr |= ESAI_TFCR_TFWM(64);
+
+ /* Left aligned, Zero padding */
+ tcr |= ESAI_TCR_PADC;
+
+ __raw_writel(tcr, ESAI_TCR);
+ __raw_writel(tfcr, ESAI_TFCR);
+
+ ESAI_DUMP();
+ return 0;
+}
+
+/*
+ * This function is called to initialize the RX port before enable
+ * the rx port.
+ */
+static int imx_esai_hw_rx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ u32 rcr, rfcr;
+ unsigned int channels;
+
+ rcr = __raw_readl(ESAI_RCR);
+ rfcr = __raw_readl(ESAI_RFCR);
+
+ rfcr |= ESAI_RFCR_RFR;
+ __raw_writel(rfcr, ESAI_RFCR);
+ rfcr &= ~ESAI_RFCR_RFR;
+
+ rfcr &= ESAI_RFCR_RWA_MASK;
+ rcr &= ESAI_RCR_RSWS_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ rfcr |= ESAI_WORD_LEN_16;
+ rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL16;
+ break;
+ }
+
+ channels = params_channels(params);
+ rfcr &= ESAI_RFCR_RE_MASK;
+ rfcr |= ESAI_RFCR_RE(channels);
+
+ rfcr |= ESAI_RFCR_RFWM(64);
+
+ __raw_writel(rcr, ESAI_RCR);
+ __raw_writel(rfcr, ESAI_RFCR);
+ return 0;
+}
+
+/*
+ * This function is called to initialize the TX or RX port,
+ */
+static int imx_esai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (__raw_readl(ESAI_TCR) & ESAI_TCR_TE0)
+ return 0;
+ return imx_esai_hw_tx_params(substream, params, dai);
+ } else {
+ if (__raw_readl(ESAI_RCR) & ESAI_RCR_RE1)
+ return 0;
+ return imx_esai_hw_rx_params(substream, params, dai);
+ }
+}
+
+static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ u32 reg, tfcr = 0, rfcr = 0;
+ u32 temp;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tfcr = __raw_readl(ESAI_TFCR);
+ reg = __raw_readl(ESAI_TCR);
+ } else {
+ rfcr = __raw_readl(ESAI_RFCR);
+ reg = __raw_readl(ESAI_RCR);
+ }
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tfcr |= ESAI_TFCR_TFEN;
+ __raw_writel(tfcr, ESAI_TFCR);
+ reg &= ~ESAI_TCR_TPR;
+ reg |= ESAI_TCR_TE(substream->runtime->channels);
+ __raw_writel(reg, ESAI_TCR);
+ } else {
+ temp = __raw_readl(ESAI_TCR);
+ temp &= ~ESAI_TCR_TPR;
+ __raw_writel(temp, ESAI_TCR);
+ rfcr |= ESAI_RFCR_RFEN;
+ __raw_writel(rfcr, ESAI_RFCR);
+ reg &= ~ESAI_RCR_RPR;
+ reg |= ESAI_RCR_RE(substream->runtime->channels);
+ __raw_writel(reg, ESAI_RCR);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg &= ~ESAI_TCR_TE(substream->runtime->channels);
+ __raw_writel(reg, ESAI_TCR);
+ reg |= ESAI_TCR_TPR;
+ __raw_writel(reg, ESAI_TCR);
+ tfcr |= ESAI_TFCR_TFR;
+ tfcr &= ~ESAI_TFCR_TFEN;
+ __raw_writel(tfcr, ESAI_TFCR);
+ tfcr &= ~ESAI_TFCR_TFR;
+ __raw_writel(tfcr, ESAI_TFCR);
+ } else {
+ reg &= ~ESAI_RCR_RE(substream->runtime->channels);
+ __raw_writel(reg, ESAI_RCR);
+ reg |= ESAI_RCR_RPR;
+ __raw_writel(reg, ESAI_RCR);
+ rfcr |= ESAI_RFCR_RFR;
+ rfcr &= ~ESAI_RFCR_RFEN;
+ __raw_writel(rfcr, ESAI_RFCR);
+ rfcr &= ~ESAI_RFCR_RFR;
+ __raw_writel(rfcr, ESAI_RFCR);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ ESAI_DUMP();
+ return 0;
+}
+
+static void imx_esai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (dai->id & IMX_DAI_ESAI_TX)
+ imx_esai_txrx_state &= ~IMX_DAI_ESAI_TX;
+ if (dai->id & IMX_DAI_ESAI_RX)
+ imx_esai_txrx_state &= ~IMX_DAI_ESAI_RX;
+
+ /* shutdown ESAI if neither Tx or Rx is active */
+ if (!(imx_esai_txrx_state & IMX_DAI_ESAI_TXRX)) {
+ free_irq(MXC_INT_ESAI, NULL);
+ clk_disable(esai_clk);
+ }
+}
+
+#ifdef CONFIG_PM
+static int imx_esai_suspend(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /*do we need to disable any clocks */
+ return 0;
+}
+
+static int imx_esai_resume(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* do we need to enable any clocks */
+ return 0;
+}
+
+#else
+#define imx_esai_suspend NULL
+#define imx_esai_resume NULL
+#endif
+
+static int imx_esai_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ imx_esai_txrx_state = 0;
+
+ esai_clk = clk_get(NULL, "esai_clk");
+
+ return 0;
+}
+
+static void imx_esai_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+
+ clk_put(esai_clk);
+}
+
+#define IMX_ESAI_RATES SNDRV_PCM_RATE_8000_192000
+
+#define IMX_ESAI_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops imx_esai_dai_ops = {
+ .startup = imx_esai_startup,
+ .shutdown = imx_esai_shutdown,
+ .trigger = imx_esai_trigger,
+ .hw_params = imx_esai_hw_params,
+ .set_sysclk = imx_esai_set_dai_sysclk,
+ .set_clkdiv = imx_esai_set_dai_clkdiv,
+ .set_fmt = imx_esai_set_dai_fmt,
+ .set_tdm_slot = imx_esai_set_dai_tdm_slot,
+};
+
+struct snd_soc_dai imx_esai_dai[] = {
+ {
+ .name = "imx-esai-tx",
+ .id = IMX_DAI_ESAI_TX,
+ .probe = imx_esai_probe,
+ .remove = imx_esai_remove,
+ .suspend = imx_esai_suspend,
+ .resume = imx_esai_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .ops = &imx_esai_dai_ops,
+ .private_data = &imx_esai_priv[0],
+ },
+ {
+ .name = "imx-esai-rx",
+ .id = IMX_DAI_ESAI_RX,
+ .probe = imx_esai_probe,
+ .remove = imx_esai_remove,
+ .suspend = imx_esai_suspend,
+ .resume = imx_esai_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .ops = &imx_esai_dai_ops,
+ .private_data = &imx_esai_priv[1],
+ },
+ {
+ .name = "imx-esai-txrx",
+ .id = IMX_DAI_ESAI_TXRX,
+ .probe = imx_esai_probe,
+ .remove = imx_esai_remove,
+ .suspend = imx_esai_suspend,
+ .resume = imx_esai_resume,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 6,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = IMX_ESAI_RATES,
+ .formats = IMX_ESAI_FORMATS,
+ },
+ .ops = &imx_esai_dai_ops,
+ .private_data = &imx_esai_priv[2],
+ },
+
+};
+
+EXPORT_SYMBOL_GPL(imx_esai_dai);
+
+static int imx_esai_dev_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct mxc_esai_platform_data *plat_data = pdev->dev.platform_data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ esai_ioaddr = ioremap(res->start, res->end - res->start + 1);
+
+ if (plat_data->activate_esai_ports)
+ plat_data->activate_esai_ports();
+
+ snd_soc_register_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai));
+ return 0;
+}
+
+static int __devexit imx_esai_dev_remove(struct platform_device *pdev)
+{
+
+ struct mxc_esai_platform_data *plat_data = pdev->dev.platform_data;
+ iounmap(esai_ioaddr);
+ if (plat_data->deactivate_esai_ports)
+ plat_data->deactivate_esai_ports();
+
+ snd_soc_unregister_dais(imx_esai_dai, ARRAY_SIZE(imx_esai_dai));
+ return 0;
+}
+
+
+static struct platform_driver imx_esai_driver = {
+ .probe = imx_esai_dev_probe,
+ .remove = __devexit_p(imx_esai_dev_remove),
+ .driver = {
+ .name = "mxc_esai",
+ },
+};
+
+static int __init imx_esai_init(void)
+{
+ return platform_driver_register(&imx_esai_driver);
+}
+
+static void __exit imx_esai_exit(void)
+{
+ platform_driver_unregister(&imx_esai_driver);
+}
+
+module_init(imx_esai_init);
+module_exit(imx_esai_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX ASoC ESAI driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-esai.h b/sound/soc/imx/imx-esai.h
new file mode 100644
index 000000000000..6e2cce0eff4a
--- /dev/null
+++ b/sound/soc/imx/imx-esai.h
@@ -0,0 +1,314 @@
+/*
+ * imx-esai.h -- ESAI driver header file for Freescale IMX
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _MXC_ESAI_H
+#define _MXC_ESAI_H
+
+/*#define IMX_ESAI_DUMP 1*/
+
+#ifdef IMX_ESAI_DUMP
+#define ESAI_DUMP() \
+ do {pr_info("dump @ %s\n", __func__); \
+ pr_info("ecr %x\n", __raw_readl(ESAI_ECR)); \
+ pr_info("esr %x\n", __raw_readl(ESAI_ESR)); \
+ pr_info("tfcr %x\n", __raw_readl(ESAI_TFCR)); \
+ pr_info("tfsr %x\n", __raw_readl(ESAI_TFSR)); \
+ pr_info("rfcr %x\n", __raw_readl(ESAI_RFCR)); \
+ pr_info("rfsr %x\n", __raw_readl(ESAI_RFSR)); \
+ pr_info("tsr %x\n", __raw_readl(ESAI_TSR)); \
+ pr_info("saisr %x\n", __raw_readl(ESAI_SAISR)); \
+ pr_info("saicr %x\n", __raw_readl(ESAI_SAICR)); \
+ pr_info("tcr %x\n", __raw_readl(ESAI_TCR)); \
+ pr_info("tccr %x\n", __raw_readl(ESAI_TCCR)); \
+ pr_info("rcr %x\n", __raw_readl(ESAI_RCR)); \
+ pr_info("rccr %x\n", __raw_readl(ESAI_RCCR)); \
+ pr_info("tsma %x\n", __raw_readl(ESAI_TSMA)); \
+ pr_info("tsmb %x\n", __raw_readl(ESAI_TSMB)); \
+ pr_info("rsma %x\n", __raw_readl(ESAI_RSMA)); \
+ pr_info("rsmb %x\n", __raw_readl(ESAI_RSMB)); \
+ pr_info("prrc %x\n", __raw_readl(ESAI_PRRC)); \
+ pr_info("pcrc %x\n", __raw_readl(ESAI_PCRC)); } while (0);
+#else
+#define ESAI_DUMP()
+#endif
+
+#define ESAI_IO_BASE_ADDR (esai_ioaddr)
+
+#define ESAI_ETDR (ESAI_IO_BASE_ADDR + 0x00)
+#define ESAI_ERDR (ESAI_IO_BASE_ADDR + 0x04)
+#define ESAI_ECR (ESAI_IO_BASE_ADDR + 0x08)
+#define ESAI_ESR (ESAI_IO_BASE_ADDR + 0x0C)
+#define ESAI_TFCR (ESAI_IO_BASE_ADDR + 0x10)
+#define ESAI_TFSR (ESAI_IO_BASE_ADDR + 0x14)
+#define ESAI_RFCR (ESAI_IO_BASE_ADDR + 0x18)
+#define ESAI_RFSR (ESAI_IO_BASE_ADDR + 0x1C)
+#define ESAI_TX0 (ESAI_IO_BASE_ADDR + 0x80)
+#define ESAI_TX1 (ESAI_IO_BASE_ADDR + 0x84)
+#define ESAI_TX2 (ESAI_IO_BASE_ADDR + 0x88)
+#define ESAI_TX3 (ESAI_IO_BASE_ADDR + 0x8C)
+#define ESAI_TX4 (ESAI_IO_BASE_ADDR + 0x90)
+#define ESAI_TX5 (ESAI_IO_BASE_ADDR + 0x94)
+#define ESAI_TSR (ESAI_IO_BASE_ADDR + 0x98)
+#define ESAI_RX0 (ESAI_IO_BASE_ADDR + 0xA0)
+#define ESAI_RX1 (ESAI_IO_BASE_ADDR + 0xA4)
+#define ESAI_RX2 (ESAI_IO_BASE_ADDR + 0xA8)
+#define ESAI_RX3 (ESAI_IO_BASE_ADDR + 0xAC)
+#define ESAI_SAISR (ESAI_IO_BASE_ADDR + 0xCC)
+#define ESAI_SAICR (ESAI_IO_BASE_ADDR + 0xD0)
+#define ESAI_TCR (ESAI_IO_BASE_ADDR + 0xD4)
+#define ESAI_TCCR (ESAI_IO_BASE_ADDR + 0xD8)
+#define ESAI_RCR (ESAI_IO_BASE_ADDR + 0xDC)
+#define ESAI_RCCR (ESAI_IO_BASE_ADDR + 0xE0)
+#define ESAI_TSMA (ESAI_IO_BASE_ADDR + 0xE4)
+#define ESAI_TSMB (ESAI_IO_BASE_ADDR + 0xE8)
+#define ESAI_RSMA (ESAI_IO_BASE_ADDR + 0xEC)
+#define ESAI_RSMB (ESAI_IO_BASE_ADDR + 0xF0)
+#define ESAI_PRRC (ESAI_IO_BASE_ADDR + 0xF8)
+#define ESAI_PCRC (ESAI_IO_BASE_ADDR + 0xFC)
+
+#define ESAI_ECR_ETI (1 << 19)
+#define ESAI_ECR_ETO (1 << 18)
+#define ESAI_ECR_ERI (1 << 17)
+#define ESAI_ECR_ERO (1 << 16)
+#define ESAI_ECR_ERST (1 << 1)
+#define ESAI_ECR_ESAIEN (1 << 0)
+
+#define ESAI_ESR_TINIT (1 << 10)
+#define ESAI_ESR_RFF (1 << 9)
+#define ESAI_ESR_TFE (1 << 8)
+#define ESAI_ESR_TLS (1 << 7)
+#define ESAI_ESR_TDE (1 << 6)
+#define ESAI_ESR_TED (1 << 5)
+#define ESAI_ESR_TD (1 << 4)
+#define ESAI_ESR_RLS (1 << 3)
+#define ESAI_ESR_RDE (1 << 2)
+#define ESAI_ESR_RED (1 << 1)
+#define ESAI_ESR_RD (1 << 0)
+
+#define ESAI_TFCR_TIEN (1 << 19)
+#define ESAI_TFCR_TE5 (1 << 7)
+#define ESAI_TFCR_TE4 (1 << 6)
+#define ESAI_TFCR_TE3 (1 << 5)
+#define ESAI_TFCR_TE2 (1 << 4)
+#define ESAI_TFCR_TE1 (1 << 3)
+#define ESAI_TFCR_TE0 (1 << 2)
+#define ESAI_TFCR_TFR (1 << 1)
+#define ESAI_TFCR_TFEN (1 << 0)
+#define ESAI_TFCR_TE(x) ((0x3f >> (6 - ((x + 1) >> 1))) << 2)
+#define ESAI_TFCR_TE_MASK 0xfff03
+#define ESAI_TFCR_TFWM(x) ((x - 1) << 8)
+#define ESAI_TFCR_TWA_MASK 0xf8ffff
+
+#define ESAI_RFCR_REXT (1 << 19)
+#define ESAI_RFCR_RE3 (1 << 5)
+#define ESAI_RFCR_RE2 (1 << 4)
+#define ESAI_RFCR_RE1 (1 << 3)
+#define ESAI_RFCR_RE0 (1 << 2)
+#define ESAI_RFCR_RFR (1 << 1)
+#define ESAI_RFCR_RFEN (1 << 0)
+#define ESAI_RFCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 2)
+#define ESAI_RFCR_RE_MASK 0xfffc3
+#define ESAI_RFCR_RFWM(x) ((x-1) << 8)
+#define ESAI_RFCR_RWA_MASK 0xf8ffff
+
+#define ESAI_WORD_LEN_32 (0x00 << 16)
+#define ESAI_WORD_LEN_28 (0x01 << 16)
+#define ESAI_WORD_LEN_24 (0x02 << 16)
+#define ESAI_WORD_LEN_20 (0x03 << 16)
+#define ESAI_WORD_LEN_16 (0x04 << 16)
+#define ESAI_WORD_LEN_12 (0x05 << 16)
+#define ESAI_WORD_LEN_8 (0x06 << 16)
+#define ESAI_WORD_LEN_4 (0x07 << 16)
+
+#define ESAI_SAISR_TODFE (1 << 17)
+#define ESAI_SAISR_TEDE (1 << 16)
+#define ESAI_SAISR_TDE (1 << 15)
+#define ESAI_SAISR_TUE (1 << 14)
+#define ESAI_SAISR_TFS (1 << 13)
+#define ESAI_SAISR_RODF (1 << 10)
+#define ESAI_SAISR_REDF (1 << 9)
+#define ESAI_SAISR_RDF (1 << 8)
+#define ESAI_SAISR_ROE (1 << 7)
+#define ESAI_SAISR_RFS (1 << 6)
+#define ESAI_SAISR_IF2 (1 << 2)
+#define ESAI_SAISR_IF1 (1 << 1)
+#define ESAI_SAISR_IF0 (1 << 0)
+
+#define ESAI_SAICR_ALC (1 << 8)
+#define ESAI_SAICR_TEBE (1 << 7)
+#define ESAI_SAICR_SYNC (1 << 6)
+#define ESAI_SAICR_OF2 (1 << 2)
+#define ESAI_SAICR_OF1 (1 << 1)
+#define ESAI_SAICR_OF0 (1 << 0)
+
+#define ESAI_TCR_TLIE (1 << 23)
+#define ESAI_TCR_TIE (1 << 22)
+#define ESAI_TCR_TEDIE (1 << 21)
+#define ESAI_TCR_TEIE (1 << 20)
+#define ESAI_TCR_TPR (1 << 19)
+#define ESAI_TCR_PADC (1 << 17)
+#define ESAI_TCR_TFSR (1 << 16)
+#define ESAI_TCR_TFSL (1 << 15)
+#define ESAI_TCR_TWA (1 << 7)
+#define ESAI_TCR_TSHFD_MSB (0 << 6)
+#define ESAI_TCR_TSHFD_LSB (1 << 6)
+#define ESAI_TCR_TE5 (1 << 5)
+#define ESAI_TCR_TE4 (1 << 4)
+#define ESAI_TCR_TE3 (1 << 3)
+#define ESAI_TCR_TE2 (1 << 2)
+#define ESAI_TCR_TE1 (1 << 1)
+#define ESAI_TCR_TE0 (1 << 0)
+#define ESAI_TCR_TE(x) (0x3f >> (6 - ((x + 1) >> 1)))
+
+#define ESAI_TCR_TSWS_MASK 0xff83ff
+#define ESAI_TCR_TSWS_STL8_WDL8 (0x00 << 10)
+#define ESAI_TCR_TSWS_STL12_WDL8 (0x04 << 10)
+#define ESAI_TCR_TSWS_STL12_WDL12 (0x01 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL8 (0x08 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL12 (0x05 << 10)
+#define ESAI_TCR_TSWS_STL16_WDL16 (0x02 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL8 (0x0c << 10)
+#define ESAI_TCR_TSWS_STL20_WDL12 (0x09 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL16 (0x06 << 10)
+#define ESAI_TCR_TSWS_STL20_WDL20 (0x03 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL8 (0x10 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL12 (0x0d << 10)
+#define ESAI_TCR_TSWS_STL24_WDL16 (0x0a << 10)
+#define ESAI_TCR_TSWS_STL24_WDL20 (0x07 << 10)
+#define ESAI_TCR_TSWS_STL24_WDL24 (0x1e << 10)
+#define ESAI_TCR_TSWS_STL32_WDL8 (0x18 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL12 (0x15 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL16 (0x12 << 10)
+#define ESAI_TCR_TSWS_STL32_WDL20 (0x0f << 10)
+#define ESAI_TCR_TSWS_STL32_WDL24 (0x1f << 10)
+
+#define ESAI_TCR_TMOD_MASK 0xfffcff
+#define ESAI_TCR_TMOD_NORMAL (0x00 << 8)
+#define ESAI_TCR_TMOD_ONDEMAND (0x01 << 8)
+#define ESAI_TCR_TMOD_NETWORK (0x01 << 8)
+#define ESAI_TCR_TMOD_RESERVED (0x02 << 8)
+#define ESAI_TCR_TMOD_AC97 (0x03 << 8)
+
+#define ESAI_TCCR_THCKD (1 << 23)
+#define ESAI_TCCR_TFSD (1 << 22)
+#define ESAI_TCCR_TCKD (1 << 21)
+#define ESAI_TCCR_THCKP (1 << 20)
+#define ESAI_TCCR_TFSP (1 << 19)
+#define ESAI_TCCR_TCKP (1 << 18)
+
+#define ESAI_TCCR_TPSR_MASK 0xfffeff
+#define ESAI_TCCR_TPSR_BYPASS (1 << 8)
+#define ESAI_TCCR_TPSR_DIV8 (0 << 8)
+
+#define ESAI_TCCR_TFP_MASK 0xfc3fff
+#define ESAI_TCCR_TFP(x) ((x & 0xf) << 14)
+
+#define ESAI_TCCR_TDC_MASK 0xffc1ff
+#define ESAI_TCCR_TDC(x) (((x) & 0x1f) << 9)
+
+#define ESAI_TCCR_TPM_MASK 0xffff00
+#define ESAI_TCCR_TPM(x) (x & 0xff)
+
+#define ESAI_RCR_RLIE (1 << 23)
+#define ESAI_RCR_RIE (1 << 22)
+#define ESAI_RCR_REDIE (1 << 21)
+#define ESAI_RCR_REIE (1 << 20)
+#define ESAI_RCR_RPR (1 << 19)
+#define ESAI_RCR_RFSR (1 << 16)
+#define ESAI_RCR_RFSL (1 << 15)
+#define ESAI_RCR_RWA (1 << 7)
+#define ESAI_RCR_RSHFD_MSB (0 << 6)
+#define ESAI_RCR_RSHFD_LSB (1 << 6)
+#define ESAI_RCR_RE3 (1 << 3)
+#define ESAI_RCR_RE2 (1 << 2)
+#define ESAI_RCR_RE1 (1 << 1)
+#define ESAI_RCR_RE0 (1 << 0)
+#define ESAI_RCR_RE(x) (0xf >> (4 - ((x + 1) >> 1)))
+
+#define ESAI_RCR_RSWS_MASK 0xff83ff
+#define ESAI_RCR_RSWS_STL8_WDL8 (0x00 << 10)
+#define ESAI_RCR_RSWS_STL12_WDL8 (0x04 << 10)
+#define ESAI_RCR_RSWS_STL12_WDL12 (0x01 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL8 (0x08 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL12 (0x05 << 10)
+#define ESAI_RCR_RSWS_STL16_WDL16 (0x02 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL8 (0x0c << 10)
+#define ESAI_RCR_RSWS_STL20_WDL12 (0x09 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL16 (0x06 << 10)
+#define ESAI_RCR_RSWS_STL20_WDL20 (0x03 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL8 (0x10 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL12 (0x0d << 10)
+#define ESAI_RCR_RSWS_STL24_WDL16 (0x0a << 10)
+#define ESAI_RCR_RSWS_STL24_WDL20 (0x07 << 10)
+#define ESAI_RCR_RSWS_STL24_WDL24 (0x1e << 10)
+#define ESAI_RCR_RSWS_STL32_WDL8 (0x18 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL12 (0x15 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL16 (0x12 << 10)
+#define ESAI_RCR_RSWS_STL32_WDL20 (0x0f << 10)
+#define ESAI_RCR_RSWS_STL32_WDL24 (0x1f << 10)
+
+#define ESAI_RCR_RMOD_MASK 0xfffcff
+#define ESAI_RCR_RMOD_NORMAL (0x00 << 8)
+#define ESAI_RCR_RMOD_ONDEMAND (0x01 << 8)
+#define ESAI_RCR_RMOD_NETWORK (0x01 << 8)
+#define ESAI_RCR_RMOD_RESERVED (0x02 << 8)
+#define ESAI_RCR_RMOD_AC97 (0x03 << 8)
+
+#define ESAI_RCCR_RHCKD (1 << 23)
+#define ESAI_RCCR_RFSD (1 << 22)
+#define ESAI_RCCR_RCKD (1 << 21)
+#define ESAI_RCCR_RHCKP (1 << 20)
+#define ESAI_RCCR_RFSP (1 << 19)
+#define ESAI_RCCR_RCKP (1 << 18)
+
+#define ESAI_RCCR_RPSR_MASK 0xfffeff
+#define ESAI_RCCR_RPSR_BYPASS (1 << 8)
+#define ESAI_RCCR_RPSR_DIV8 (0 << 8)
+
+#define ESAI_RCCR_RFP_MASK 0xfc3fff
+#define ESAI_RCCR_RFP(x) ((x & 0xf) << 14)
+
+#define ESAI_RCCR_RDC_MASK 0xffc1ff
+#define ESAI_RCCR_RDC(x) (((x) & 0x1f) << 9)
+
+#define ESAI_RCCR_RPM_MASK 0xffff00
+#define ESAI_RCCR_RPM(x) (x & 0xff)
+
+#define ESAI_GPIO_ESAI 0xfff
+
+/* ESAI clock source */
+#define ESAI_CLK_FSYS 0
+#define ESAI_CLK_EXTAL 1
+
+/* ESAI clock divider */
+#define ESAI_TX_DIV_PSR 0
+#define ESAI_TX_DIV_PM 1
+#define ESAI_TX_DIV_FP 2
+#define ESAI_RX_DIV_PSR 3
+#define ESAI_RX_DIV_PM 4
+#define ESAI_RX_DIV_FP 5
+
+#define IMX_DAI_ESAI_TX 0x04
+#define IMX_DAI_ESAI_RX 0x08
+#define IMX_DAI_ESAI_TXRX (IMX_DAI_ESAI_TX | IMX_DAI_ESAI_RX)
+
+struct imx_esai {
+ bool network_mode;
+ bool sync_mode;
+};
+
+extern struct snd_soc_dai imx_esai_dai[];
+
+#endif
diff --git a/sound/soc/imx/imx-pcm.c b/sound/soc/imx/imx-pcm.c
new file mode 100644
index 000000000000..3e8a90da35c2
--- /dev/null
+++ b/sound/soc/imx/imx-pcm.c
@@ -0,0 +1,688 @@
+/*
+ * imx-pcm.c -- ALSA SoC interface for the Freescale i.MX3 CPU's
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Copyright (C) 2006-2010 Freescale Semiconductor, Inc.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Based on imx31-pcm.c by Nicolas Pitre, (C) 2004 MontaVista Software, Inc.
+ * and on mxc-alsa-mc13783 (C) 2006 Freescale.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/iram_alloc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "imx-pcm.h"
+#include "imx-ssi.h"
+#include "imx-esai.h"
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+#include <linux/delay.h>
+#include <linux/mxc_asrc.h>
+#endif
+
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+static bool UseIram = 1;
+#else
+static bool UseIram;
+#endif
+
+/* debug */
+#define IMX_PCM_DEBUG 0
+#if IMX_PCM_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+static const struct snd_pcm_hardware imx_pcm_hardware = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+#ifdef CONFIG_SND_MXC_SOC_IRAM
+ .buffer_bytes_max = SND_RAM_SIZE,
+ .period_bytes_max = SND_RAM_SIZE / 4,
+#else
+ .buffer_bytes_max = 64 * 1024,
+ .period_bytes_max = 16 * 1024,
+#endif
+ .period_bytes_min = 2 * SZ_1K,
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = {
+ .open = snd_pcm_mmap_data_open,
+ .close = snd_pcm_mmap_data_close,
+};
+
+/*
+ enable user space access to iram buffer
+*/
+static int imx_iram_audio_playback_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *area)
+{
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ unsigned long off;
+ unsigned long phys;
+ unsigned long size;
+ int ret = 0;
+
+ area->vm_ops = &snd_mxc_audio_playback_vm_ops;
+ area->vm_private_data = substream;
+
+ off = area->vm_pgoff << PAGE_SHIFT;
+ phys = buf->addr + off;
+ size = area->vm_end - area->vm_start;
+
+ if (off + size > SND_RAM_SIZE)
+ return -EINVAL;
+
+ area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
+ area->vm_flags |= VM_IO;
+ ret =
+ remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT,
+ size, area->vm_page_prot);
+ if (ret == 0)
+ area->vm_ops->open(area);
+
+ return ret;
+}
+
+static int imx_get_sdma_transfer(int format, int dai_port,
+ struct snd_pcm_substream *substream)
+{
+ int transfer = -1;
+
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+ if (prtd->asrc_enable == 1) {
+ if (dai_port == IMX_DAI_SSI0) {
+ if (prtd->asrc_index == 0)
+ transfer = MXC_DMA_ASRCA_SSI1_TX0;
+ else if (prtd->asrc_index == 1)
+ transfer = MXC_DMA_ASRCB_SSI1_TX0;
+ } else if (dai_port == IMX_DAI_SSI1) {
+ if (prtd->asrc_index == 0)
+ transfer = MXC_DMA_ASRCA_SSI1_TX1;
+ else if (prtd->asrc_index == 1)
+ transfer = MXC_DMA_ASRCB_SSI1_TX1;
+ } else if (dai_port == IMX_DAI_SSI2) {
+ if (prtd->asrc_index == 0)
+ transfer = MXC_DMA_ASRCA_SSI2_TX0;
+ else if (prtd->asrc_index == 1)
+ transfer = MXC_DMA_ASRCB_SSI2_TX0;
+ } else if (dai_port == IMX_DAI_SSI3) {
+ if (prtd->asrc_index == 0)
+ transfer = MXC_DMA_ASRCA_SSI2_TX1;
+ else if (prtd->asrc_index == 1)
+ transfer = MXC_DMA_ASRCB_SSI2_TX1;
+ } else if (dai_port & IMX_DAI_ESAI_TX) {
+ if (prtd->asrc_index == 0)
+ transfer = MXC_DMA_ASRCA_ESAI;
+ else if (prtd->asrc_index == 1)
+ transfer = MXC_DMA_ASRCB_ESAI;
+ else
+ transfer = MXC_DMA_ASRCC_ESAI;
+ }
+ } else {
+#endif
+
+ if (dai_port == IMX_DAI_SSI0) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI1_16BIT_TX0;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI1_24BIT_TX0;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI1_24BIT_TX0;
+ } else {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI1_16BIT_RX0;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI1_24BIT_RX0;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI1_24BIT_RX0;
+ }
+ } else if (dai_port == IMX_DAI_SSI1) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI1_16BIT_TX1;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI1_24BIT_TX1;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI1_24BIT_TX1;
+ } else {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI1_16BIT_RX1;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI1_24BIT_RX1;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI1_24BIT_RX1;
+ }
+ } else if (dai_port == IMX_DAI_SSI2) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI2_16BIT_TX0;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI2_24BIT_TX0;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI2_24BIT_TX0;
+ } else {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI2_16BIT_RX0;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI2_24BIT_RX0;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI2_24BIT_RX0;
+ }
+ } else if (dai_port == IMX_DAI_SSI3) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI2_16BIT_TX1;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI2_24BIT_TX1;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI2_24BIT_TX1;
+ } else {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_SSI2_16BIT_RX1;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_SSI2_24BIT_RX1;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_SSI2_24BIT_RX1;
+ }
+ } else if ((dai_port & IMX_DAI_ESAI_TX)
+ || (dai_port & IMX_DAI_ESAI_RX)) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_ESAI_16BIT_TX;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_ESAI_24BIT_TX;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_ESAI_24BIT_TX;
+ } else {
+ if (format == SNDRV_PCM_FORMAT_S16_LE)
+ transfer = MXC_DMA_ESAI_16BIT_RX;
+ else if (format == SNDRV_PCM_FORMAT_S24_LE)
+ transfer = MXC_DMA_ESAI_24BIT_RX;
+ else if (format == SNDRV_PCM_FORMAT_S20_3LE)
+ transfer = MXC_DMA_ESAI_24BIT_RX;
+ }
+ }
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ }
+#endif
+ return transfer;
+}
+
+static int dma_new_period(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+ unsigned int dma_size = frames_to_bytes(runtime, runtime->period_size);
+ unsigned int offset = dma_size * prtd->period;
+ int ret = 0;
+ mxc_dma_requestbuf_t sdma_request;
+
+ if (!prtd->active)
+ return 0;
+
+ memset(&sdma_request, 0, sizeof(mxc_dma_requestbuf_t));
+
+ dbg("period pos ALSA %x DMA %x\n", runtime->periods, prtd->period);
+ dbg("period size ALSA %x DMA %x Offset %x dmasize %x\n",
+ (unsigned int)runtime->period_size,
+ runtime->dma_bytes, offset, dma_size);
+ dbg("DMA addr %x\n", runtime->dma_addr + offset);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sdma_request.src_addr =
+ (dma_addr_t) (runtime->dma_addr + offset);
+ else
+ sdma_request.dst_addr =
+ (dma_addr_t) (runtime->dma_addr + offset);
+
+ sdma_request.num_of_bytes = dma_size;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ mxc_dma_config(prtd->dma_wchannel,
+ &sdma_request, 1, MXC_DMA_MODE_WRITE);
+ ret = mxc_dma_enable(prtd->dma_wchannel);
+ } else {
+
+ mxc_dma_config(prtd->dma_wchannel,
+ &sdma_request, 1, MXC_DMA_MODE_READ);
+ ret = mxc_dma_enable(prtd->dma_wchannel);
+ }
+ prtd->dma_active = 1;
+ prtd->period++;
+ prtd->period %= runtime->periods;
+
+ return ret;
+}
+
+static void audio_dma_irq(void *data)
+{
+ struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+
+ prtd->dma_active = 0;
+ prtd->periods++;
+ prtd->periods %= runtime->periods;
+
+ dbg("irq per %d offset %x\n", prtd->periods,
+ frames_to_bytes(runtime, runtime->period_size) * prtd->periods);
+
+ if (prtd->active)
+ snd_pcm_period_elapsed(substream);
+ dma_new_period(substream);
+}
+
+static int imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+ int ret = 0, channel = 0;
+
+ if (prtd->dma_alloc) {
+ mxc_dma_free(prtd->dma_wchannel);
+ prtd->dma_alloc = 0;
+ }
+
+ /* only allocate the DMA chn once */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (prtd->asrc_enable == 1) {
+ struct dma_channel_info info;
+ mxc_dma_requestbuf_t sdma_request;
+ info.asrc.channs = runtime->channels;
+ if (prtd->dma_asrc) {
+ mxc_dma_free(prtd->dma_asrc);
+ prtd->dma_asrc = 0;
+ }
+ memset(&sdma_request, 0, sizeof(mxc_dma_requestbuf_t));
+ /* num_of_bytes can be set any value except for zero */
+ sdma_request.num_of_bytes = 0x40;
+ channel =
+ mxc_dma_request_ext(prtd->dma_ch,
+ "ALSA TX SDMA", &info);
+
+ mxc_dma_config(channel, &sdma_request,
+ 1, MXC_DMA_MODE_WRITE);
+ prtd->dma_asrc = channel;
+ if (prtd->asrc_index == 0)
+ prtd->dma_ch = MXC_DMA_ASRC_A_RX;
+ else if (prtd->asrc_index == 1)
+ prtd->dma_ch = MXC_DMA_ASRC_B_RX;
+ else
+ prtd->dma_ch = MXC_DMA_ASRC_C_RX;
+
+ channel =
+ mxc_dma_request(MXC_DMA_ASRC_A_RX, "ALSA ASRC RX");
+ } else
+ channel = mxc_dma_request(prtd->dma_ch, "ALSA TX SDMA");
+#else
+ channel = mxc_dma_request(prtd->dma_ch, "ALSA TX SDMA");
+#endif
+ if (channel < 0) {
+ pr_err("imx-pcm: error requesting \
+ a write dma channel\n");
+ return channel;
+ }
+ ret = mxc_dma_callback_set(channel, (mxc_dma_callback_t)
+ audio_dma_irq, (void *)substream);
+
+ } else {
+ channel = mxc_dma_request(prtd->dma_ch, "ALSA RX SDMA");
+ if (channel < 0) {
+ pr_err("imx-pcm: error requesting \
+ a read dma channel\n");
+ return channel;
+ }
+ ret = mxc_dma_callback_set(channel, (mxc_dma_callback_t)
+ audio_dma_irq, (void *)substream);
+ }
+ prtd->dma_wchannel = channel;
+ prtd->dma_alloc = 1;
+
+ prtd->period = 0;
+ prtd->periods = 0;
+ return 0;
+}
+
+static int imx_pcm_hw_params(struct snd_pcm_substream
+ *substream, struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ prtd->dma_ch =
+ imx_get_sdma_transfer(params_format(params),
+ rtd->dai->cpu_dai->id, substream);
+
+ if (prtd->dma_ch < 0) {
+ printk(KERN_ERR "imx-pcm: invaild sdma transfer type");
+ return -1;
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+
+ if (prtd->dma_wchannel) {
+ mxc_dma_free(prtd->dma_wchannel);
+ prtd->dma_wchannel = 0;
+ prtd->dma_alloc = 0;
+ }
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if ((prtd->asrc_enable == 1) && prtd->dma_asrc) {
+ mxc_dma_free(prtd->dma_asrc);
+ prtd->dma_asrc = 0;
+ }
+#endif
+
+ return 0;
+}
+
+static int imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct mxc_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->dma_active = 0;
+ prtd->active = 1;
+ ret = dma_new_period(substream);
+ ret = dma_new_period(substream);
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (prtd->asrc_enable == 1) {
+ ret = mxc_dma_enable(prtd->dma_asrc);
+ asrc_start_conv(prtd->asrc_index);
+ /* There is underrun, if immediately enable SSI after
+ start ASRC */
+ mdelay(1);
+ }
+#endif
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->active = 0;
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ if (prtd->asrc_enable == 1) {
+ mxc_dma_disable(prtd->dma_asrc);
+ asrc_stop_conv(prtd->asrc_index);
+ }
+#endif
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t imx_pcm_pointer(struct
+ snd_pcm_substream
+ *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+ unsigned int offset = 0;
+
+ offset = (runtime->period_size * (prtd->periods));
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+ dbg("pointer offset %x\n", offset);
+
+ return offset;
+}
+
+static int imx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ prtd = kzalloc(sizeof(struct mxc_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+ return 0;
+}
+
+static int imx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxc_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+static int
+imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai;
+ struct mxc_audio_platform_data *dev_data;
+ int ext_ram = 0;
+ int ret = 0;
+
+ dbg("+imx_pcm_mmap:"
+ "UseIram=%d dma_addr=%x dma_area=%x dma_bytes=%d\n",
+ UseIram, (unsigned int)runtime->dma_addr,
+ runtime->dma_area, runtime->dma_bytes);
+
+ if (cpu_dai->dev && cpu_dai->dev->platform_data) {
+ dev_data = cpu_dai->dev->platform_data;
+ ext_ram = dev_data->ext_ram;
+ }
+
+ if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ || ext_ram || !UseIram) {
+ ret =
+ dma_mmap_writecombine(substream->pcm->card->
+ dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+ return ret;
+ } else
+ return imx_iram_audio_playback_mmap(substream, vma);
+}
+
+struct snd_pcm_ops imx_pcm_ops = {
+ .open = imx_pcm_open,
+ .close = imx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = imx_pcm_hw_params,
+ .hw_free = imx_pcm_hw_free,
+ .prepare = imx_pcm_prepare,
+ .trigger = imx_pcm_trigger,
+ .pointer = imx_pcm_pointer,
+ .mmap = imx_pcm_mmap,
+};
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai;
+ struct mxc_audio_platform_data *dev_data;
+ unsigned long buf_paddr;
+ int ext_ram = 0;
+ size_t size = imx_pcm_hardware.buffer_bytes_max;
+
+ if (cpu_dai->dev && cpu_dai->dev->platform_data) {
+ dev_data = cpu_dai->dev->platform_data;
+ ext_ram = dev_data->ext_ram;
+ }
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ if ((stream == SNDRV_PCM_STREAM_CAPTURE) || ext_ram || !UseIram)
+ buf->area =
+ dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ else {
+ buf->area = iram_alloc(size, &buf_paddr);
+ buf->addr = buf_paddr;
+
+ if (!buf->area) {
+ pr_warning("imx-pcm: Falling back to external ram.\n");
+ UseIram = 0;
+ buf->area =
+ dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ }
+ }
+
+ if (!buf->area)
+ return -ENOMEM;
+ buf->bytes = size;
+ printk(KERN_INFO "DMA Sound Buffers Allocated:"
+ "UseIram=%d buf->addr=%x buf->area=%p size=%d\n",
+ UseIram, buf->addr, buf->area, size);
+ return 0;
+}
+
+static void imx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai;
+ struct mxc_audio_platform_data *dev_data;
+ int ext_ram = 0;
+ int stream;
+
+ if (cpu_dai->dev && cpu_dai->dev->platform_data) {
+ dev_data = cpu_dai->dev->platform_data;
+ ext_ram = dev_data->ext_ram;
+ }
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ if ((stream == SNDRV_PCM_STREAM_CAPTURE)
+ || ext_ram || !UseIram)
+ dma_free_writecombine(pcm->card->dev,
+ buf->bytes, buf->area, buf->addr);
+ else {
+ iram_free(buf->addr, imx_pcm_hardware.buffer_bytes_max);
+ }
+ buf->area = NULL;
+ }
+}
+
+static u64 imx_pcm_dmamask = 0xffffffff;
+
+static int imx_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &imx_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->playback.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ return ret;
+}
+
+struct snd_soc_platform imx_soc_platform = {
+ .name = "imx-audio",
+ .pcm_ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_new,
+ .pcm_free = imx_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(imx_soc_platform);
+
+static int __init imx_pcm_init(void)
+{
+ return snd_soc_register_platform(&imx_soc_platform);
+}
+module_init(imx_pcm_init);
+
+static void __exit imx_pcm_exit(void)
+{
+ snd_soc_unregister_platform(&imx_soc_platform);
+}
+module_exit(imx_pcm_exit);
+
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_DESCRIPTION("Freescale i.MX3x PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-pcm.h b/sound/soc/imx/imx-pcm.h
new file mode 100644
index 000000000000..c22a42b70364
--- /dev/null
+++ b/sound/soc/imx/imx-pcm.h
@@ -0,0 +1,83 @@
+/*
+ * imx-pcm.h :- ASoC platform header for Freescale i.MX
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Copyright 2006, 2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MXC_PCM_H
+#define _MXC_PCM_H
+
+#include <mach/dma.h>
+
+/* AUDMUX regs definition */
+#define AUDMUX_IO_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR)
+
+#define DAM_PTCR1 ((AUDMUX_IO_BASE_ADDR) + 0x00)
+#define DAM_PDCR1 ((AUDMUX_IO_BASE_ADDR) + 0x04)
+#define DAM_PTCR2 ((AUDMUX_IO_BASE_ADDR) + 0x08)
+#define DAM_PDCR2 ((AUDMUX_IO_BASE_ADDR) + 0x0C)
+#define DAM_PTCR3 ((AUDMUX_IO_BASE_ADDR) + 0x10)
+#define DAM_PDCR3 ((AUDMUX_IO_BASE_ADDR) + 0x14)
+#define DAM_PTCR4 ((AUDMUX_IO_BASE_ADDR) + 0x18)
+#define DAM_PDCR4 ((AUDMUX_IO_BASE_ADDR) + 0x1C)
+#define DAM_PTCR5 ((AUDMUX_IO_BASE_ADDR) + 0x20)
+#define DAM_PDCR5 ((AUDMUX_IO_BASE_ADDR) + 0x24)
+#define DAM_PTCR6 ((AUDMUX_IO_BASE_ADDR) + 0x28)
+#define DAM_PDCR6 ((AUDMUX_IO_BASE_ADDR) + 0x2C)
+#define DAM_PTCR7 ((AUDMUX_IO_BASE_ADDR) + 0x30)
+#define DAM_PDCR7 ((AUDMUX_IO_BASE_ADDR) + 0x34)
+#define DAM_CNMCR ((AUDMUX_IO_BASE_ADDR) + 0x38)
+#define DAM_PTCR(a) ((AUDMUX_IO_BASE_ADDR) + (a-1)*8)
+#define DAM_PDCR(a) ((AUDMUX_IO_BASE_ADDR) + 4 + (a-1)*8)
+
+#define AUDMUX_PTCR_TFSDIR (1 << 31)
+#define AUDMUX_PTCR_TFSSEL(x, y) \
+ ((x << 30) | (((y - 1) & 0x7) << 27))
+#define AUDMUX_PTCR_TCLKDIR (1 << 26)
+#define AUDMUX_PTCR_TCSEL(x, y) \
+ ((x << 25) | (((y - 1) & 0x7) << 22))
+#define AUDMUX_PTCR_RFSDIR (1 << 21)
+#define AUDMUX_PTCR_RFSSEL(x, y) \
+ ((x << 20) | (((y - 1) & 0x7) << 17))
+#define AUDMUX_PTCR_RCLKDIR (1 << 16)
+#define AUDMUX_PTCR_RCSEL(x, y) \
+ ((x << 15) | (((y - 1) & 0x7) << 12))
+#define AUDMUX_PTCR_SYN (1 << 11)
+
+#define AUDMUX_FROM_TXFS 0
+#define AUDMUX_FROM_RXFS 1
+
+#define AUDMUX_PDCR_RXDSEL(x) (((x - 1) & 0x7) << 13)
+#define AUDMUX_PDCR_TXDXEN (1 << 12)
+#define AUDMUX_PDCR_MODE(x) (((x) & 0x3) << 8)
+#define AUDMUX_PDCR_INNMASK(x) (((x) & 0xff) << 0)
+
+#define AUDMUX_CNMCR_CEN (1 << 18)
+#define AUDMUX_CNMCR_FSPOL (1 << 17)
+#define AUDMUX_CNMCR_CLKPOL (1 << 16)
+#define AUDMUX_CNMCR_CNTHI(x) (((x) & 0xff) << 8)
+#define AUDMUX_CNMCR_CNTLOW(x) (((x) & 0xff) << 0)
+
+
+struct mxc_runtime_data {
+ int dma_ch;
+ spinlock_t dma_lock;
+ int active, period, periods;
+ int dma_wchannel;
+ int dma_active;
+ int dma_alloc;
+#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE)
+ int dma_asrc;
+ int asrc_index;
+ int asrc_enable;
+#endif
+};
+
+extern struct snd_soc_platform imx_soc_platform;
+
+#endif
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c
new file mode 100644
index 000000000000..027e32df938b
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.c
@@ -0,0 +1,784 @@
+/*
+ * imx-ssi.c -- SSI driver for Freescale IMX
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright (C) 2006-2010 Freescale Semiconductor, Inc.
+ * Based on mxc-alsa-mc13783
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 29th Aug 2006 Initial version.
+ *
+ * TODO:
+ * Need to rework SSI register defs when new defs go into mainline.
+ * Add support for TDM and FIFO 1.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+#define SSI_STX0 (0x00)
+#define SSI_STX1 (0x04)
+#define SSI_SRX0 (0x08)
+#define SSI_SRX1 (0x0c)
+#define SSI_SCR (0x10)
+#define SSI_SISR (0x14)
+#define SSI_SIER (0x18)
+#define SSI_STCR (0x1c)
+#define SSI_SRCR (0x20)
+#define SSI_STCCR (0x24)
+#define SSI_SRCCR (0x28)
+#define SSI_SFCSR (0x2c)
+#define SSI_STR (0x30)
+#define SSI_SOR (0x34)
+#define SSI_SACNT (0x38)
+#define SSI_SACADD (0x3c)
+#define SSI_SACDAT (0x40)
+#define SSI_SATAG (0x44)
+#define SSI_STMSK (0x48)
+#define SSI_SRMSK (0x4c)
+#define SSI_SACCST (0x50)
+#define SSI_SACCEN (0x54)
+#define SSI_SACCDIS (0x58)
+
+/* debug */
+#define IMX_SSI_DEBUG 0
+#if IMX_SSI_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+#define IMX_SSI_DUMP 0
+#if IMX_SSI_DUMP
+#define SSI_DUMP() \
+ do { \
+ printk(KERN_INFO "dump @ %s\n", __func__); \
+ printk(KERN_INFO "scr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SCR), \
+ __raw_readl(ioaddr + SSI_SCR)); \
+ printk(KERN_INFO "sisr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SISR), \
+ __raw_readl(ioaddr + SSI_SISR)); \
+ printk(KERN_INFO "stcr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STCR), \
+ __raw_readl(ioaddr + SSI_STCR)); \
+ printk(KERN_INFO "srcr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRCR), \
+ __raw_readl(ioaddr + SSI_SRCR)); \
+ printk(KERN_INFO "stccr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STCCR), \
+ __raw_readl(ioaddr + SSI_STCCR)); \
+ printk(KERN_INFO "srccr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRCCR), \
+ __raw_readl(ioaddr + SSI_SRCCR)); \
+ printk(KERN_INFO "sfcsr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SFCSR), \
+ __raw_readl(ioaddr + SSI_SFCSR)); \
+ printk(KERN_INFO "stmsk %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STMSK), \
+ __raw_readl(ioaddr + SSI_STMSK)); \
+ printk(KERN_INFO "srmsk %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRMSK), \
+ __raw_readl(ioaddr + SSI_SRMSK)); \
+ printk(KERN_INFO "sier %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SIER), \
+ __raw_readl(ioaddr + SSI_SIER)); \
+ } while (0);
+#else
+#define SSI_DUMP()
+#endif
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
+
+ scr = __raw_readl(ioaddr + SSI_SCR);
+
+ if (scr & SSI_SCR_SSIEN)
+ return 0;
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ if (dir == SND_SOC_CLOCK_OUT)
+ scr |= SSI_SCR_SYS_CLK_EN;
+ else
+ scr &= ~SSI_SCR_SYS_CLK_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ __raw_writel(scr, ioaddr + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stccr, srccr;
+
+ if (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN)
+ return 0;
+
+ srccr = __raw_readl(ioaddr + SSI_SRCCR);
+ stccr = __raw_readl(ioaddr + SSI_STCCR);
+
+ switch (div_id) {
+ case IMX_SSI_TX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ case IMX_SSI_RX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(srccr, ioaddr + SSI_SRCCR);
+
+ return 0;
+}
+
+/*
+ * SSI Network Mode or TDM slots configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+ unsigned int mask, int slots)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stmsk, srmsk, stccr;
+
+ if (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN)
+ return 0;
+ stccr = __raw_readl(ioaddr + SSI_STCCR);
+
+ stmsk = srmsk = mask;
+ stccr &= ~SSI_STCCR_DC_MASK;
+ stccr |= SSI_STCCR_DC(slots - 1);
+
+ __raw_writel(stmsk, ioaddr + SSI_STMSK);
+ __raw_writel(srmsk, ioaddr + SSI_SRMSK);
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(stccr, ioaddr + SSI_SRCCR);
+
+ return 0;
+}
+
+/*
+ * SSI DAI format configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ * Note: We don't use the I2S modes but instead manually configure the
+ * SSI for I2S.
+ */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stcr = 0, srcr = 0, scr;
+
+ scr = __raw_readl(ioaddr + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+
+ if (scr & SSI_SCR_SSIEN)
+ return 0;
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data on rising edge of bclk, frame low 1clk before data */
+ stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+ srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data on rising edge of bclk, frame high with data */
+ stcr |= SSI_STCR_TXBIT0;
+ srcr |= SSI_SRCR_RXBIT0;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /* data on rising edge of bclk, frame high with data */
+ stcr |= SSI_STCR_TFSL;
+ srcr |= SSI_SRCR_RFSL;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /* data on rising edge of bclk, frame high 1clk before data */
+ stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
+ srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+ srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ stcr |= SSI_STCR_TFSI;
+ stcr &= ~SSI_STCR_TSCKP;
+ srcr |= SSI_SRCR_RFSI;
+ srcr &= ~SSI_SRCR_RSCKP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ stcr &= ~SSI_STCR_TFSI;
+ stcr |= SSI_STCR_TSCKP;
+ srcr &= ~SSI_SRCR_RFSI;
+ srcr |= SSI_SRCR_RSCKP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+ srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
+ if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ && priv->network_mode) {
+ scr &= ~SSI_SCR_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_MSTR;
+ }
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ stcr |= SSI_STCR_TFDIR;
+ srcr |= SSI_SRCR_RFDIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ stcr |= SSI_STCR_TXDIR;
+ srcr |= SSI_SRCR_RXDIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ && priv->network_mode) {
+ scr &= ~SSI_SCR_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_SLAVE;
+ }
+ break;
+ }
+
+ /* sync */
+ if (priv->sync_mode)
+ scr |= SSI_SCR_SYN;
+
+ /* tdm - only for stereo atm */
+ if (priv->network_mode)
+ scr |= SSI_SCR_NET;
+
+ __raw_writel(stcr, ioaddr + SSI_STCR);
+ __raw_writel(srcr, ioaddr + SSI_SRCR);
+ __raw_writel(scr, ioaddr + SSI_SCR);
+
+ SSI_DUMP();
+ return 0;
+}
+
+static int imx_ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+
+ /* we cant really change any SSI values after SSI is enabled
+ * need to fix in software for max flexibility - lrg */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return 0;
+
+ /* reset the SSI port - Sect 45.4.4 */
+ if (clk_get_usecount(priv->ssi_clk) != 0) {
+ clk_enable(priv->ssi_clk);
+ return 0;
+ }
+
+ __raw_writel(0, ioaddr + SSI_SCR);
+ clk_enable(priv->ssi_clk);
+
+ /* BIG FAT WARNING
+ * SDMA FIFO watermark must == SSI FIFO watermark for best results.
+ */
+ __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)),
+ ioaddr + SSI_SFCSR);
+ __raw_writel(0, ioaddr + SSI_SIER);
+
+ SSI_DUMP();
+ return 0;
+}
+
+static int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stccr, stcr, sier;
+
+ stccr = __raw_readl(ioaddr + SSI_STCCR) & ~SSI_STCCR_WL_MASK;
+ stcr = __raw_readl(ioaddr + SSI_STCR);
+ sier = __raw_readl(ioaddr + SSI_SIER);
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ stccr |= SSI_STCCR_WL(16);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ stccr |= SSI_STCCR_WL(20);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ stccr |= SSI_STCCR_WL(24);
+ break;
+ }
+
+ /* enable interrupts */
+ if ((cpu_dai->id & 0x1) == 0)
+ stcr |= SSI_STCR_TFEN0;
+ else
+ stcr |= SSI_STCR_TFEN1;
+ sier |= SSI_SIER_TDMAE | SSI_SIER_TIE | SSI_SIER_TUE0_EN;
+
+ __raw_writel(stcr, ioaddr + SSI_STCR);
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(sier, ioaddr + SSI_SIER);
+
+ return 0;
+}
+
+static int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ u32 srccr, srcr, sier;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ bool sync_mode = priv->sync_mode;
+
+ srccr =
+ sync_mode ? __raw_readl(ioaddr + SSI_STCCR) :
+ __raw_readl(ioaddr + SSI_SRCCR);
+ srcr = __raw_readl(ioaddr + SSI_SRCR);
+ sier = __raw_readl(ioaddr + SSI_SIER);
+ srccr &= ~SSI_SRCCR_WL_MASK;
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ srccr |= SSI_SRCCR_WL(16);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ srccr |= SSI_SRCCR_WL(20);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ srccr |= SSI_SRCCR_WL(24);
+ break;
+ }
+
+ /* enable interrupts */
+ if ((cpu_dai->id & 0x1) == 0)
+ srcr |= SSI_SRCR_RFEN0;
+ else
+ srcr |= SSI_SRCR_RFEN1;
+ sier |= SSI_SIER_RDMAE | SSI_SIER_RIE | SSI_SIER_ROE0_EN;
+
+ __raw_writel(srcr, ioaddr + SSI_SRCR);
+ if (sync_mode)
+ __raw_writel(srccr, ioaddr + SSI_STCCR);
+ else
+ __raw_writel(srccr, ioaddr + SSI_SRCCR);
+ __raw_writel(sier, ioaddr + SSI_SIER);
+
+ return 0;
+}
+
+/*
+ * Should only be called when port is inactive (i.e. SSIEN = 0),
+ * although can be called multiple times by upper layers.
+ */
+static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ int id;
+
+ id = cpu_dai->id;
+
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* cant change any parameters when SSI is running */
+ if ((__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN) &&
+ (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_TE))
+ return 0;
+ return imx_ssi_hw_tx_params(substream, params, cpu_dai);
+ } else {
+ /* cant change any parameters when SSI is running */
+ if ((__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN) &&
+ (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_RE))
+ return 0;
+ return imx_ssi_hw_rx_params(substream, params, cpu_dai);
+ }
+}
+
+static int imx_ssi_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
+
+ /* enable the SSI port, note that no other port config
+ * should happen after SSIEN is set */
+ scr = __raw_readl(ioaddr + SSI_SCR);
+ __raw_writel((scr | SSI_SCR_SSIEN), ioaddr + SSI_SCR);
+
+ SSI_DUMP();
+ return 0;
+}
+
+static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
+
+ scr = __raw_readl(ioaddr + SSI_SCR);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (scr & SSI_SCR_RE) {
+ __raw_writel(0, ioaddr + SSI_SCR);
+ }
+ scr |= SSI_SCR_TE;
+ } else
+ scr |= SSI_SCR_RE;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ scr &= ~SSI_SCR_TE;
+ else
+ scr &= ~SSI_SCR_RE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ __raw_writel(scr, ioaddr + SSI_SCR);
+
+ SSI_DUMP();
+ return 0;
+}
+
+static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ int id;
+
+ id = cpu_dai->id;
+
+ /* shutdown SSI if neither Tx or Rx is active */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return;
+
+ if (clk_get_usecount(priv->ssi_clk) > 1) {
+ clk_disable(priv->ssi_clk);
+ return;
+ }
+
+ __raw_writel(0, ioaddr + SSI_SCR);
+
+ clk_disable(priv->ssi_clk);
+}
+
+#ifdef CONFIG_PM
+static int imx_ssi_suspend(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* do we need to disable any clocks? */
+
+ return 0;
+}
+
+static int imx_ssi_resume(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+
+ /* do we need to enable any clocks? */
+
+ return 0;
+}
+#else
+#define imx_ssi_suspend NULL
+#define imx_ssi_resume NULL
+#endif
+
+static int fifo_err_counter;
+
+static irqreturn_t imx_ssi_irq(int irq, void *dev_id)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)dev_id;
+ void __iomem *ioaddr = priv->ioaddr;
+ if (fifo_err_counter++ % 1000 == 0)
+ printk(KERN_ERR "%s %s SISR %x SIER %x fifo_errs=%d\n",
+ __func__, priv->pdev->name,
+ __raw_readl(ioaddr + SSI_SISR),
+ __raw_readl(ioaddr + SSI_SIER), fifo_err_counter);
+ __raw_writel((SSI_SIER_TUE0_EN | SSI_SIER_ROE0_EN), ioaddr + SSI_SISR);
+ return IRQ_HANDLED;
+}
+
+static int imx_ssi_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)dai->private_data;
+
+ if (priv->irq >= 0) {
+ if (request_irq(priv->irq, imx_ssi_irq, IRQF_SHARED,
+ pdev->name, priv)) {
+ printk(KERN_ERR "%s: failure requesting irq for %s\n",
+ __func__, pdev->name);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static void imx_ssi_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)dai->private_data;
+
+ if (priv->irq >= 0)
+ free_irq(priv->irq, dai);
+}
+
+#define IMX_SSI_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define IMX_SSI_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops imx_ssi_dai_ops = {
+ .startup = imx_ssi_startup,
+ .shutdown = imx_ssi_shutdown,
+ .trigger = imx_ssi_trigger,
+ .prepare = imx_ssi_prepare,
+ .hw_params = imx_ssi_hw_params,
+ .set_sysclk = imx_ssi_set_dai_sysclk,
+ .set_clkdiv = imx_ssi_set_dai_clkdiv,
+ .set_fmt = imx_ssi_set_dai_fmt,
+ .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
+};
+
+struct snd_soc_dai *imx_ssi_dai[MAX_SSI_CHANNELS];
+EXPORT_SYMBOL_GPL(imx_ssi_dai);
+
+static int imx_ssi_dev_probe(struct platform_device *pdev)
+{
+ int fifo0_channel = pdev->id * 2;
+ struct snd_soc_dai *dai;
+ struct imx_ssi *priv;
+ int fifo, channel;
+ struct resource *res;
+ int ret;
+
+ BUG_ON(fifo0_channel >= MAX_SSI_CHANNELS);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ priv = kzalloc(sizeof(struct imx_ssi), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Each SSI block has 2 fifos which share the same
+ private data (struct imx_ssi) */
+ priv->baseaddr = res->start;
+ priv->ioaddr = ioremap(res->start, 0x5C);
+ priv->irq = platform_get_irq(pdev, 0);
+ priv->ssi_clk = clk_get(&pdev->dev, "ssi_clk");
+ priv->pdev = pdev;
+
+ for (fifo = 0; fifo < 2; fifo++) {
+ channel = (pdev->id * 2) + fifo;
+
+ dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+ if (IS_ERR(dai)) {
+ ret = -ENOMEM;
+ goto DAI_ERR;
+ }
+
+ dai->name = kasprintf(GFP_KERNEL, "imx-ssi-%d-%d",
+ pdev->id + 1, fifo);
+ if (IS_ERR(dai->name)) {
+ kfree(dai);
+ ret = -ENOMEM;
+ goto DAI_ERR;
+ }
+
+ dai->probe = imx_ssi_probe;
+ dai->suspend = imx_ssi_suspend;
+ dai->remove = imx_ssi_remove;
+ dai->resume = imx_ssi_resume;
+
+ dai->playback.channels_min = 1;
+ dai->playback.channels_max = 2;
+ dai->playback.rates = IMX_SSI_RATES;
+ dai->playback.formats = IMX_SSI_FORMATS;
+
+ dai->capture.channels_min = 1;
+ dai->capture.channels_max = 2;
+ dai->capture.rates = IMX_SSI_RATES;
+ dai->capture.formats = IMX_SSI_FORMATS;
+
+ dai->ops = &imx_ssi_dai_ops;
+
+ dai->private_data = priv;
+
+ dai->id = channel;
+ imx_ssi_dai[channel] = dai;
+
+ ret = snd_soc_register_dai(dai);
+ if (ret < 0) {
+ kfree(dai->name);
+ kfree(dai);
+ goto DAI_ERR;
+ }
+ }
+ return 0;
+
+DAI_ERR:
+ if (fifo == 1) {
+ dai = imx_ssi_dai[fifo0_channel];
+ snd_soc_unregister_dai(dai);
+ kfree(dai->name);
+ kfree(dai);
+ }
+
+ clk_put(priv->ssi_clk);
+ iounmap(priv->ioaddr);
+ kfree(priv);
+ return ret;
+}
+
+static int __devexit imx_ssi_dev_remove(struct platform_device *pdev)
+{
+ int fifo0_channel = pdev->id * 2;
+ int fifo1_channel = (pdev->id * 2) + 1;
+ struct imx_ssi *priv = imx_ssi_dai[fifo0_channel]->private_data;
+
+ snd_soc_unregister_dai(imx_ssi_dai[fifo0_channel]);
+ snd_soc_unregister_dai(imx_ssi_dai[fifo1_channel]);
+
+ kfree(imx_ssi_dai[fifo0_channel]->name);
+ kfree(imx_ssi_dai[fifo0_channel]);
+
+ kfree(imx_ssi_dai[fifo1_channel]->name);
+ kfree(imx_ssi_dai[fifo1_channel]);
+
+ clk_put(priv->ssi_clk);
+ iounmap(priv->ioaddr);
+ kfree(priv);
+ return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+ .probe = imx_ssi_dev_probe,
+ .remove = __devexit_p(imx_ssi_dev_remove),
+ .driver = {
+ .name = "mxc_ssi",
+ },
+};
+
+static int __init imx_ssi_init(void)
+{
+ return platform_driver_register(&imx_ssi_driver);
+}
+
+static void __exit imx_ssi_exit(void)
+{
+ platform_driver_unregister(&imx_ssi_driver);
+}
+
+module_init(imx_ssi_init);
+module_exit(imx_ssi_exit);
+MODULE_AUTHOR
+ ("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("i.MX ASoC I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h
new file mode 100644
index 000000000000..6da50a5ae415
--- /dev/null
+++ b/sound/soc/imx/imx-ssi.h
@@ -0,0 +1,244 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IMX_SSI_H
+#define _IMX_SSI_H
+
+#include <mach/hardware.h>
+
+/* SSI regs definition */
+#define SSI1_IO_BASE_ADDR IO_ADDRESS(SSI1_BASE_ADDR)
+#define SSI2_IO_BASE_ADDR IO_ADDRESS(SSI2_BASE_ADDR)
+
+#define SSI1_STX0 ((SSI1_IO_BASE_ADDR) + 0x00)
+#define SSI1_STX1 ((SSI1_IO_BASE_ADDR) + 0x04)
+#define SSI1_SRX0 ((SSI1_IO_BASE_ADDR) + 0x08)
+#define SSI1_SRX1 ((SSI1_IO_BASE_ADDR) + 0x0c)
+#define SSI1_SCR ((SSI1_IO_BASE_ADDR) + 0x10)
+#define SSI1_SISR ((SSI1_IO_BASE_ADDR) + 0x14)
+#define SSI1_SIER ((SSI1_IO_BASE_ADDR) + 0x18)
+#define SSI1_STCR ((SSI1_IO_BASE_ADDR) + 0x1c)
+#define SSI1_SRCR ((SSI1_IO_BASE_ADDR) + 0x20)
+#define SSI1_STCCR ((SSI1_IO_BASE_ADDR) + 0x24)
+#define SSI1_SRCCR ((SSI1_IO_BASE_ADDR) + 0x28)
+#define SSI1_SFCSR ((SSI1_IO_BASE_ADDR) + 0x2c)
+#define SSI1_STR ((SSI1_IO_BASE_ADDR) + 0x30)
+#define SSI1_SOR ((SSI1_IO_BASE_ADDR) + 0x34)
+#define SSI1_SACNT ((SSI1_IO_BASE_ADDR) + 0x38)
+#define SSI1_SACADD ((SSI1_IO_BASE_ADDR) + 0x3c)
+#define SSI1_SACDAT ((SSI1_IO_BASE_ADDR) + 0x40)
+#define SSI1_SATAG ((SSI1_IO_BASE_ADDR) + 0x44)
+#define SSI1_STMSK ((SSI1_IO_BASE_ADDR) + 0x48)
+#define SSI1_SRMSK ((SSI1_IO_BASE_ADDR) + 0x4c)
+#define SSI1_SACCST ((SSI1_IO_BASE_ADDR) + 0x50)
+#define SSI1_SACCEN ((SSI1_IO_BASE_ADDR) + 0x54)
+#define SSI1_SACCDIS ((SSI1_IO_BASE_ADDR) + 0x58)
+
+#define SSI2_STX0 ((SSI2_IO_BASE_ADDR) + 0x00)
+#define SSI2_STX1 ((SSI2_IO_BASE_ADDR) + 0x04)
+#define SSI2_SRX0 ((SSI2_IO_BASE_ADDR) + 0x08)
+#define SSI2_SRX1 ((SSI2_IO_BASE_ADDR) + 0x0c)
+#define SSI2_SCR ((SSI2_IO_BASE_ADDR) + 0x10)
+#define SSI2_SISR ((SSI2_IO_BASE_ADDR) + 0x14)
+#define SSI2_SIER ((SSI2_IO_BASE_ADDR) + 0x18)
+#define SSI2_STCR ((SSI2_IO_BASE_ADDR) + 0x1c)
+#define SSI2_SRCR ((SSI2_IO_BASE_ADDR) + 0x20)
+#define SSI2_STCCR ((SSI2_IO_BASE_ADDR) + 0x24)
+#define SSI2_SRCCR ((SSI2_IO_BASE_ADDR) + 0x28)
+#define SSI2_SFCSR ((SSI2_IO_BASE_ADDR) + 0x2c)
+#define SSI2_STR ((SSI2_IO_BASE_ADDR) + 0x30)
+#define SSI2_SOR ((SSI2_IO_BASE_ADDR) + 0x34)
+#define SSI2_SACNT ((SSI2_IO_BASE_ADDR) + 0x38)
+#define SSI2_SACADD ((SSI2_IO_BASE_ADDR) + 0x3c)
+#define SSI2_SACDAT ((SSI2_IO_BASE_ADDR) + 0x40)
+#define SSI2_SATAG ((SSI2_IO_BASE_ADDR) + 0x44)
+#define SSI2_STMSK ((SSI2_IO_BASE_ADDR) + 0x48)
+#define SSI2_SRMSK ((SSI2_IO_BASE_ADDR) + 0x4c)
+#define SSI2_SACCST ((SSI2_IO_BASE_ADDR) + 0x50)
+#define SSI2_SACCEN ((SSI2_IO_BASE_ADDR) + 0x54)
+#define SSI2_SACCDIS ((SSI2_IO_BASE_ADDR) + 0x58)
+
+#define SSI_SCR_CLK_IST (1 << 9)
+#define SSI_SCR_TCH_EN (1 << 8)
+#define SSI_SCR_SYS_CLK_EN (1 << 7)
+#define SSI_SCR_I2S_MODE_NORM (0 << 5)
+#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
+#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
+#define SSI_SCR_SYN (1 << 4)
+#define SSI_SCR_NET (1 << 3)
+#define SSI_SCR_RE (1 << 2)
+#define SSI_SCR_TE (1 << 1)
+#define SSI_SCR_SSIEN (1 << 0)
+#define SSI_SCR_I2S_MODE_MASK (3 << 5)
+
+#define SSI_SISR_CMDAU (1 << 18)
+#define SSI_SISR_CMDDU (1 << 17)
+#define SSI_SISR_RXT (1 << 16)
+#define SSI_SISR_RDR1 (1 << 15)
+#define SSI_SISR_RDR0 (1 << 14)
+#define SSI_SISR_TDE1 (1 << 13)
+#define SSI_SISR_TDE0 (1 << 12)
+#define SSI_SISR_ROE1 (1 << 11)
+#define SSI_SISR_ROE0 (1 << 10)
+#define SSI_SISR_TUE1 (1 << 9)
+#define SSI_SISR_TUE0 (1 << 8)
+#define SSI_SISR_TFS (1 << 7)
+#define SSI_SISR_RFS (1 << 6)
+#define SSI_SISR_TLS (1 << 5)
+#define SSI_SISR_RLS (1 << 4)
+#define SSI_SISR_RFF1 (1 << 3)
+#define SSI_SISR_RFF0 (1 << 2)
+#define SSI_SISR_TFE1 (1 << 1)
+#define SSI_SISR_TFE0 (1 << 0)
+
+#define SSI_SIER_RDMAE (1 << 22)
+#define SSI_SIER_RIE (1 << 21)
+#define SSI_SIER_TDMAE (1 << 20)
+#define SSI_SIER_TIE (1 << 19)
+#define SSI_SIER_CMDAU_EN (1 << 18)
+#define SSI_SIER_CMDDU_EN (1 << 17)
+#define SSI_SIER_RXT_EN (1 << 16)
+#define SSI_SIER_RDR1_EN (1 << 15)
+#define SSI_SIER_RDR0_EN (1 << 14)
+#define SSI_SIER_TDE1_EN (1 << 13)
+#define SSI_SIER_TDE0_EN (1 << 12)
+#define SSI_SIER_ROE1_EN (1 << 11)
+#define SSI_SIER_ROE0_EN (1 << 10)
+#define SSI_SIER_TUE1_EN (1 << 9)
+#define SSI_SIER_TUE0_EN (1 << 8)
+#define SSI_SIER_TFS_EN (1 << 7)
+#define SSI_SIER_RFS_EN (1 << 6)
+#define SSI_SIER_TLS_EN (1 << 5)
+#define SSI_SIER_RLS_EN (1 << 4)
+#define SSI_SIER_RFF1_EN (1 << 3)
+#define SSI_SIER_RFF0_EN (1 << 2)
+#define SSI_SIER_TFE1_EN (1 << 1)
+#define SSI_SIER_TFE0_EN (1 << 0)
+
+#define SSI_STCR_TXBIT0 (1 << 9)
+#define SSI_STCR_TFEN1 (1 << 8)
+#define SSI_STCR_TFEN0 (1 << 7)
+#define SSI_STCR_TFDIR (1 << 6)
+#define SSI_STCR_TXDIR (1 << 5)
+#define SSI_STCR_TSHFD (1 << 4)
+#define SSI_STCR_TSCKP (1 << 3)
+#define SSI_STCR_TFSI (1 << 2)
+#define SSI_STCR_TFSL (1 << 1)
+#define SSI_STCR_TEFS (1 << 0)
+
+#define SSI_SRCR_RXBIT0 (1 << 9)
+#define SSI_SRCR_RFEN1 (1 << 8)
+#define SSI_SRCR_RFEN0 (1 << 7)
+#define SSI_SRCR_RFDIR (1 << 6)
+#define SSI_SRCR_RXDIR (1 << 5)
+#define SSI_SRCR_RSHFD (1 << 4)
+#define SSI_SRCR_RSCKP (1 << 3)
+#define SSI_SRCR_RFSI (1 << 2)
+#define SSI_SRCR_RFSL (1 << 1)
+#define SSI_SRCR_REFS (1 << 0)
+
+#define SSI_STCCR_DIV2 (1 << 18)
+#define SSI_STCCR_PSR (1 << 15)
+#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_STCCR_WL_MASK (0xf << 13)
+#define SSI_STCCR_DC_MASK (0x1f << 8)
+#define SSI_STCCR_PM_MASK (0xff << 0)
+
+#define SSI_SRCCR_DIV2 (1 << 18)
+#define SSI_SRCCR_PSR (1 << 15)
+#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
+#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
+#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
+#define SSI_SRCCR_WL_MASK (0xf << 13)
+#define SSI_SRCCR_DC_MASK (0x1f << 8)
+#define SSI_SRCCR_PM_MASK (0xff << 0)
+
+
+#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
+#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
+#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
+#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
+#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
+#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
+#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
+#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
+
+#define SSI_STR_TEST (1 << 15)
+#define SSI_STR_RCK2TCK (1 << 14)
+#define SSI_STR_RFS2TFS (1 << 13)
+#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
+#define SSI_STR_TXD2RXD (1 << 7)
+#define SSI_STR_TCK2RCK (1 << 6)
+#define SSI_STR_TFS2RFS (1 << 5)
+#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
+
+#define SSI_SOR_CLKOFF (1 << 6)
+#define SSI_SOR_RX_CLR (1 << 5)
+#define SSI_SOR_TX_CLR (1 << 4)
+#define SSI_SOR_INIT (1 << 3)
+#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
+#define SSI_SOR_SYNRST (1 << 0)
+
+#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
+#define SSI_SACNT_WR (1 << 4)
+#define SSI_SACNT_RD (1 << 3)
+#define SSI_SACNT_TIF (1 << 2)
+#define SSI_SACNT_FV (1 << 1)
+#define SSI_SACNT_AC97EN (1 << 0)
+
+/* SDMA & SSI watermarks for FIFO's */
+#define SDMA_TXFIFO_WATERMARK 0x4
+#define SDMA_RXFIFO_WATERMARK 0x6
+#define SSI_TXFIFO_WATERMARK 0x4
+#define SSI_RXFIFO_WATERMARK 0x6
+
+/* Maximum number of ssi channels (counting two channels per block) */
+#define MAX_SSI_CHANNELS 8
+
+/* i.MX DAI SSP ID's */
+#define IMX_DAI_SSI0 0 /* SSI1 FIFO 0 */
+#define IMX_DAI_SSI1 1 /* SSI1 FIFO 1 */
+#define IMX_DAI_SSI2 2 /* SSI2 FIFO 0 */
+#define IMX_DAI_SSI3 3 /* SSI2 FIFO 1 */
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK 0
+
+/* SSI audio dividers */
+#define IMX_SSI_TX_DIV_2 0
+#define IMX_SSI_TX_DIV_PSR 1
+#define IMX_SSI_TX_DIV_PM 2
+#define IMX_SSI_RX_DIV_2 3
+#define IMX_SSI_RX_DIV_PSR 4
+#define IMX_SSI_RX_DIV_PM 5
+
+
+/* SSI Div 2 */
+#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2)
+#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2
+
+#define IMX_DAI_AC97_1 0
+#define IMX_DAI_AC97_2 1
+
+/* private info */
+struct imx_ssi {
+ bool network_mode;
+ bool sync_mode;
+ unsigned int ac97_tx_slots;
+ unsigned int ac97_rx_slots;
+ void __iomem *ioaddr;
+ unsigned long baseaddr;
+ int irq;
+ struct platform_device *pdev;
+ struct clk *ssi_clk;
+};
+
+extern struct snd_soc_dai *imx_ssi_dai[];
+extern struct snd_soc_dai imx_ac97_dai[];
+
+#endif
diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig
new file mode 100644
index 000000000000..213feb404a6b
--- /dev/null
+++ b/sound/soc/mxs/Kconfig
@@ -0,0 +1,51 @@
+config SND_MXS_SOC
+ tristate "SoC Audio for the MXS chips"
+ depends on ARCH_MXS && SND_SOC
+ select SND_PCM
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the MXS I2S or SSP interface.
+
+
+config SND_MXS_SOC_SPDIF_DAI
+ tristate
+
+config SND_MXS_SOC_EVK_DEVB
+ tristate "SoC Audio support for MXS-EVK SGTL5000"
+ depends on SND_MXS_SOC && ARCH_MX28
+ select SND_SOC_SGTL5000
+ help
+ Say Y if you want to add support for SoC audio on MXS EVK development
+ board with the sgtl5000 codec.
+
+config SND_MXS_SOC_EVK_ADC
+ tristate "SoC Audio support for MXS-EVK ADC/DAC"
+ depends on SND_MXS_SOC && ARCH_MX23
+ select SND_SOC_MXS_ADC_CODEC
+ help
+ Say Y if you want to add support for SoC audio on MXS EVK development
+ board with ADC/DAC audio interface.
+
+config SND_MXS_SOC_DAI
+ tristate "MXS Digital Audio Interface SAIF"
+ default y
+ depends on SND_MXS_SOC_EVK_DEVB
+ help
+ Enable MXS Digital Audio Interface SAIF
+
+config SND_MXS_SOC_ADC
+ tristate "MXS ADC/DAC Audio Interface"
+ default y
+ depends on SND_MXS_SOC_EVK_ADC
+ help
+ Enable MXS ADC/DAC Audio Interface
+
+config SND_MXS_SOC_EVK_DEVB_SPDIF
+ tristate "SoC SPDIF support for MXS EVK Development Board"
+ default n
+ depends on SND_MXS_SOC && ARCH_MXS
+ select SND_MXS_SOC_SPDIF_DAI
+ select SND_SOC_MXS_SPDIF
+ help
+ Say Y if you want to add support for SoC audio on MXS EVK development
+ board with the SPDIF transmitter.
diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile
new file mode 100644
index 000000000000..971c622a34fb
--- /dev/null
+++ b/sound/soc/mxs/Makefile
@@ -0,0 +1,16 @@
+# MXS platfrom support
+snd-soc-mxs-objs := mxs-pcm.o
+snd-soc-mxs-dai-objs := mxs-dai.o
+snd-soc-mxs-spdif-dai-objs := mxs-spdif-dai.o
+snd-soc-mxs-devb-objs := mxs-devb.o
+snd-soc-mxs-devb-spdif-objs := mxs-devb-spdif.o
+snd-soc-mxs-adc-objs := mxs-adc.o
+snd-soc-mxs-evk-adc-objs := mxs-evk-adc.o
+
+obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o
+obj-$(CONFIG_SND_MXS_SOC_DAI) += snd-soc-mxs-dai.o
+obj-$(CONFIG_SND_MXS_SOC_SPDIF_DAI) += snd-soc-mxs-spdif-dai.o
+obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB) += snd-soc-mxs-devb.o
+obj-$(CONFIG_SND_MXS_SOC_EVK_DEVB_SPDIF) += snd-soc-mxs-devb-spdif.o
+obj-$(CONFIG_SND_MXS_SOC_ADC) += snd-soc-mxs-adc.o
+obj-$(CONFIG_SND_MXS_SOC_EVK_ADC) += snd-soc-mxs-evk-adc.o
diff --git a/sound/soc/mxs/mxs-adc.c b/sound/soc/mxs/mxs-adc.c
new file mode 100644
index 000000000000..7069927b1ac3
--- /dev/null
+++ b/sound/soc/mxs/mxs-adc.c
@@ -0,0 +1,453 @@
+/*
+ * ASoC Audio Layer for Freescale MXS ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+
+#include "mxs-pcm.h"
+
+#define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+#define ADC_VOLUME_MIN 0x37
+
+struct mxs_pcm_dma_params mxs_audio_in = {
+ .name = "mxs-audio-in",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_AUDIOADC,
+ .irq = IRQ_ADC_DMA,
+};
+
+struct mxs_pcm_dma_params mxs_audio_out = {
+ .name = "mxs-audio-out",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_AUDIODAC,
+ .irq = IRQ_DAC_DMA,
+};
+
+static struct delayed_work work;
+static struct delayed_work adc_ramp_work;
+static struct delayed_work dac_ramp_work;
+static bool adc_ramp_done = 1;
+static bool dac_ramp_done = 1;
+
+static void mxs_adc_schedule_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, HZ / 10);
+}
+static void mxs_adc_work(struct work_struct *work)
+{
+ /* disable irq */
+ disable_irq(IRQ_HEADPHONE_SHORT);
+
+ while (true) {
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
+ msleep(10);
+ if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL)
+ & BM_AUDIOOUT_ANACTRL_SHORT_LR_STS) != 0) {
+ /* rearm the short protection */
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ printk(KERN_WARNING "WARNING : Headphone LR short!\r\n");
+ } else {
+ printk(KERN_WARNING "INFO : Headphone LR no longer short!\r\n");
+ break;
+ }
+ msleep(1000);
+ }
+
+ /* power up the HEADPHONE and un-mute the HPVOL */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR);
+
+ /* enable irq for next short detect*/
+ enable_irq(IRQ_HEADPHONE_SHORT);
+}
+
+static void mxs_adc_schedule_ramp_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, msecs_to_jiffies(2));
+ adc_ramp_done = 0;
+}
+
+static void mxs_adc_ramp_work(struct work_struct *work)
+{
+ u32 reg = 0;
+ u32 reg1 = 0;
+ u32 reg2 = 0;
+ u32 l, r;
+ u32 ll, rr;
+ int i;
+
+ reg = __raw_readl(REGS_AUDIOIN_BASE + \
+ HW_AUDIOIN_ADCVOLUME);
+
+ reg1 = reg & ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ reg1 = reg1 & ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+ /* minimize adc volume */
+ reg2 = reg1 |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ADC_VOLUME_MIN) |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(ADC_VOLUME_MIN);
+ __raw_writel(reg2,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ msleep(1);
+
+ l = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT) >>
+ BP_AUDIOIN_ADCVOLUME_VOLUME_LEFT;
+ r = (reg & BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT) >>
+ BP_AUDIOIN_ADCVOLUME_VOLUME_RIGHT;
+
+ /* fade in adc vol */
+ for (i = ADC_VOLUME_MIN; (i < l) || (i < r);) {
+ i += 0x8;
+ ll = i < l ? i : l;
+ rr = i < r ? i : r;
+ reg2 = reg1 |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_LEFT(ll) |
+ BF_AUDIOIN_ADCVOLUME_VOLUME_RIGHT(rr);
+ __raw_writel(reg2,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME);
+ msleep(1);
+ }
+ adc_ramp_done = 1;
+}
+
+static void mxs_dac_schedule_ramp_work(struct delayed_work *work)
+{
+ schedule_delayed_work(work, msecs_to_jiffies(2));
+ dac_ramp_done = 0;
+}
+
+static void mxs_dac_ramp_work(struct work_struct *work)
+{
+ u32 reg = 0;
+ u32 reg1 = 0;
+ u32 l, r;
+ u32 ll, rr;
+ int i;
+
+ /* unmute hp and speaker */
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR);
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR);
+
+ reg = __raw_readl(REGS_AUDIOOUT_BASE + \
+ HW_AUDIOOUT_HPVOL);
+
+ reg1 = reg & ~BM_AUDIOOUT_HPVOL_VOL_LEFT;
+ reg1 = reg1 & ~BM_AUDIOOUT_HPVOL_VOL_RIGHT;
+
+ l = (reg & BM_AUDIOOUT_HPVOL_VOL_LEFT) >>
+ BP_AUDIOOUT_HPVOL_VOL_LEFT;
+ r = (reg & BM_AUDIOOUT_HPVOL_VOL_RIGHT) >>
+ BP_AUDIOOUT_HPVOL_VOL_RIGHT;
+ /* fade in hp vol */
+ for (i = 0x7f; i > 0 ;) {
+ i -= 0x8;
+ ll = i > (int)l ? i : l;
+ rr = i > (int)r ? i : r;
+ reg = reg1 | BF_AUDIOOUT_HPVOL_VOL_LEFT(ll)
+ | BF_AUDIOOUT_HPVOL_VOL_RIGHT(rr);
+ __raw_writel(reg,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL);
+ msleep(1);
+ }
+ dac_ramp_done = 1;
+}
+
+static irqreturn_t mxs_short_irq(int irq, void *dev_id)
+{
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORTMODE_LR,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR);
+ __raw_writel(BF_AUDIOOUT_ANACTRL_SHORTMODE_LR(0x1),
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET);
+ __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET);
+
+ mxs_adc_schedule_work(&work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t mxs_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL);
+ underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ;
+ } else {
+ ctrl_reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL);
+ underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "%s underflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ __raw_writel(
+ BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(
+ BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "%s overflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ __raw_writel(
+ BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ } else
+ printk(KERN_WARNING "Unknown DAC error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int mxs_adc_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+
+ if (playback) {
+ /* enable the fifo error interrupt */
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ /* write a data to data reg to trigger the transfer */
+ __raw_writel(0x0,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA);
+ mxs_dac_schedule_ramp_work(&dac_ramp_work);
+ } else {
+ __raw_writel(BM_AUDIOIN_CTRL_RUN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ mxs_adc_schedule_ramp_work(&adc_ramp_work);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+
+ if (playback) {
+ if (dac_ramp_done == 0) {
+ cancel_delayed_work(&dac_ramp_work);
+ dac_ramp_done = 1;
+ }
+ __raw_writel(BM_AUDIOOUT_HPVOL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET);
+ __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET);
+ /* disable the fifo error interrupt */
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ } else {
+ if (adc_ramp_done == 0) {
+ cancel_delayed_work(&adc_ramp_work);
+ adc_ramp_done = 1;
+ }
+ __raw_writel(BM_AUDIOIN_CTRL_RUN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mxs_adc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int irq_short;
+ int ret;
+
+ INIT_DELAYED_WORK(&work, mxs_adc_work);
+ INIT_DELAYED_WORK(&adc_ramp_work, mxs_adc_ramp_work);
+ INIT_DELAYED_WORK(&dac_ramp_work, mxs_dac_ramp_work);
+
+ if (playback) {
+ irq = IRQ_DAC_ERROR;
+ cpu_dai->dma_data = &mxs_audio_out;
+ } else {
+ irq = IRQ_ADC_ERROR;
+ cpu_dai->dma_data = &mxs_audio_in;
+ }
+
+ ret = request_irq(irq, mxs_err_irq, 0, "MXS DAC/ADC Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n",
+ __func__, IRQ_DAC_ERROR);
+ return ret;
+ }
+
+ irq_short = IRQ_HEADPHONE_SHORT;
+ ret = request_irq(irq_short, mxs_short_irq,
+ IRQF_DISABLED | IRQF_SHARED, "MXS DAC/ADC HP SHORT", substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request ADC/DAC HP SHORT irq %d\n",
+ __func__, IRQ_DAC_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ } else {
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ }
+
+ return 0;
+}
+
+static void mxs_adc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ free_irq(IRQ_DAC_ERROR, substream);
+ } else {
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ free_irq(IRQ_ADC_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int mxs_adc_suspend(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int mxs_adc_resume(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define mxs_adc_suspend NULL
+#define mxs_adc_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai_ops mxs_adc_dai_ops = {
+ .startup = mxs_adc_startup,
+ .shutdown = mxs_adc_shutdown,
+ .trigger = mxs_adc_trigger,
+};
+
+struct snd_soc_dai mxs_adc_dai = {
+ .name = "mxs adc/dac",
+ .id = 0,
+ .suspend = mxs_adc_suspend,
+ .resume = mxs_adc_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_ADC_RATES,
+ .formats = MXS_ADC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_ADC_RATES,
+ .formats = MXS_ADC_FORMATS,
+ },
+ .ops = &mxs_adc_dai_ops,
+};
+EXPORT_SYMBOL_GPL(mxs_adc_dai);
+
+static int __init mxs_adc_dai_init(void)
+{
+ return snd_soc_register_dai(&mxs_adc_dai);
+}
+
+static void __exit mxs_adc_dai_exit(void)
+{
+ snd_soc_unregister_dai(&mxs_adc_dai);
+}
+module_init(mxs_adc_dai_init);
+module_exit(mxs_adc_dai_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("MXS ADC/DAC DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-adc.h b/sound/soc/mxs/mxs-adc.h
new file mode 100644
index 000000000000..b922c14cb894
--- /dev/null
+++ b/sound/soc/mxs/mxs-adc.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale MXS ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _MXS_ADC_H
+#define _MXS_ADC_H
+extern struct snd_soc_dai mxs_adc_dai;
+#endif
diff --git a/sound/soc/mxs/mxs-dai.c b/sound/soc/mxs/mxs-dai.c
new file mode 100644
index 000000000000..9ca22ecdb4b0
--- /dev/null
+++ b/sound/soc/mxs/mxs-dai.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include <mach/mx28.h>
+
+#include "mxs-pcm.h"
+#include "mxs-dai.h"
+
+#define SAIF0_CTRL (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF1_CTRL (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL)
+#define SAIF0_CTRL_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_SET)
+#define SAIF1_CTRL_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_SET)
+#define SAIF0_CTRL_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_CTRL_CLR)
+#define SAIF1_CTRL_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_CTRL_CLR)
+
+#define SAIF0_STAT (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT)
+#define SAIF1_STAT (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT)
+#define SAIF0_STAT_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_SET)
+#define SAIF1_STAT_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_SET)
+#define SAIF0_STAT_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_STAT_CLR)
+#define SAIF1_STAT_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_STAT_CLR)
+
+#define SAIF0_DATA (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA)
+#define SAIF1_DATA (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA)
+#define SAIF0_DATA_SET (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_SET)
+#define SAIF1_DATA_SET (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_SET)
+#define SAIF0_DATA_CLR (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_DATA_CLR)
+#define SAIF1_DATA_CLR (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_DATA_CLR)
+
+#define SAIF0_VERSION (IO_ADDRESS(SAIF0_PHYS_ADDR) + HW_SAIF_VERSION)
+#define SAIF1_VERSION (IO_ADDRESS(SAIF1_PHYS_ADDR) + HW_SAIF_VERSION)
+
+#define HW_SAIF_CTRL (0x00000000)
+#define HW_SAIF_CTRL_SET (0x00000004)
+#define HW_SAIF_CTRL_CLR (0x00000008)
+#define HW_SAIF_CTRL_TOG (0x0000000c)
+
+#define BM_SAIF_CTRL_SFTRST 0x80000000
+#define BM_SAIF_CTRL_CLKGATE 0x40000000
+#define BP_SAIF_CTRL_BITCLK_MULT_RATE 27
+#define BM_SAIF_CTRL_BITCLK_MULT_RATE 0x38000000
+#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \
+ (((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE)
+#define BM_SAIF_CTRL_BITCLK_BASE_RATE 0x04000000
+#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN 0x02000000
+#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN 0x01000000
+#define BP_SAIF_CTRL_RSRVD2 21
+#define BM_SAIF_CTRL_RSRVD2 0x00E00000
+#define BF_SAIF_CTRL_RSRVD2(v) \
+ (((v) << 21) & BM_SAIF_CTRL_RSRVD2)
+#define BP_SAIF_CTRL_DMAWAIT_COUNT 16
+#define BM_SAIF_CTRL_DMAWAIT_COUNT 0x001F0000
+#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \
+ (((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT)
+#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14
+#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000
+#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \
+ (((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT)
+#define BM_SAIF_CTRL_LRCLK_PULSE 0x00002000
+#define BM_SAIF_CTRL_BIT_ORDER 0x00001000
+#define BM_SAIF_CTRL_DELAY 0x00000800
+#define BM_SAIF_CTRL_JUSTIFY 0x00000400
+#define BM_SAIF_CTRL_LRCLK_POLARITY 0x00000200
+#define BM_SAIF_CTRL_BITCLK_EDGE 0x00000100
+#define BP_SAIF_CTRL_WORD_LENGTH 4
+#define BM_SAIF_CTRL_WORD_LENGTH 0x000000F0
+#define BF_SAIF_CTRL_WORD_LENGTH(v) \
+ (((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH)
+#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE 0x00000008
+#define BM_SAIF_CTRL_SLAVE_MODE 0x00000004
+#define BM_SAIF_CTRL_READ_MODE 0x00000002
+#define BM_SAIF_CTRL_RUN 0x00000001
+
+#define HW_SAIF_STAT (0x00000010)
+#define HW_SAIF_STAT_SET (0x00000014)
+#define HW_SAIF_STAT_CLR (0x00000018)
+#define HW_SAIF_STAT_TOG (0x0000001c)
+
+#define BM_SAIF_STAT_PRESENT 0x80000000
+#define BP_SAIF_STAT_RSRVD2 17
+#define BM_SAIF_STAT_RSRVD2 0x7FFE0000
+#define BF_SAIF_STAT_RSRVD2(v) \
+ (((v) << 17) & BM_SAIF_STAT_RSRVD2)
+#define BM_SAIF_STAT_DMA_PREQ 0x00010000
+#define BP_SAIF_STAT_RSRVD1 7
+#define BM_SAIF_STAT_RSRVD1 0x0000FF80
+#define BF_SAIF_STAT_RSRVD1(v) \
+ (((v) << 7) & BM_SAIF_STAT_RSRVD1)
+#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040
+#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ 0x00000020
+#define BM_SAIF_STAT_FIFO_SERVICE_IRQ 0x00000010
+#define BP_SAIF_STAT_RSRVD0 1
+#define BM_SAIF_STAT_RSRVD0 0x0000000E
+#define BF_SAIF_STAT_RSRVD0(v) \
+ (((v) << 1) & BM_SAIF_STAT_RSRVD0)
+#define BM_SAIF_STAT_BUSY 0x00000001
+
+#define HW_SAIF_DATA (0x00000020)
+#define HW_SAIF_DATA_SET (0x00000024)
+#define HW_SAIF_DATA_CLR (0x00000028)
+#define HW_SAIF_DATA_TOG (0x0000002c)
+
+#define BP_SAIF_DATA_PCM_RIGHT 16
+#define BM_SAIF_DATA_PCM_RIGHT 0xFFFF0000
+#define BF_SAIF_DATA_PCM_RIGHT(v) \
+ (((v) << 16) & BM_SAIF_DATA_PCM_RIGHT)
+#define BP_SAIF_DATA_PCM_LEFT 0
+#define BM_SAIF_DATA_PCM_LEFT 0x0000FFFF
+#define BF_SAIF_DATA_PCM_LEFT(v) \
+ (((v) << 0) & BM_SAIF_DATA_PCM_LEFT)
+
+#define HW_SAIF_VERSION (0x00000030)
+
+#define BP_SAIF_VERSION_MAJOR 24
+#define BM_SAIF_VERSION_MAJOR 0xFF000000
+#define BF_SAIF_VERSION_MAJOR(v) \
+ (((v) << 24) & BM_SAIF_VERSION_MAJOR)
+#define BP_SAIF_VERSION_MINOR 16
+#define BM_SAIF_VERSION_MINOR 0x00FF0000
+#define BF_SAIF_VERSION_MINOR(v) \
+ (((v) << 16) & BM_SAIF_VERSION_MINOR)
+#define BP_SAIF_VERSION_STEP 0
+#define BM_SAIF_VERSION_STEP 0x0000FFFF
+#define BF_SAIF_VERSION_STEP(v) \
+ (((v) << 0) & BM_SAIF_VERSION_STEP)
+/* debug */
+#define MXS_SAIF_DEBUG 0
+#if MXS_SAIF_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+#define MXS_SAIF_DUMP 0
+#if MXS_SAIF_DUMP
+#define SAIF_DUMP() \
+ do { \
+ printk(KERN_INFO "dump @ %s\n", __func__);\
+ printk(KERN_INFO "scr %x\t, %x\n", \
+ __raw_readl(SAIF0_CTRL), __raw_readl(SAIF1_CTRL));\
+ printk(KERN_INFO "stat %x\t, %x\n", \
+ __raw_readl(SAIF0_STAT), __raw_readl(SAIF1_STAT));\
+ printk(KERN_INFO "data %x\t, %x\n", \
+ __raw_readl(SAIF0_DATA), __raw_readl(SAIF1_DATA));\
+ printk(KERN_INFO "version %x\t, %x\n", \
+ __raw_readl(SAIF0_VERSION), __raw_readl(SAIF1_VERSION));
+ } while (0);
+#else
+#define SAIF_DUMP()
+#endif
+
+#define SAIF0_PORT 0
+#define SAIF1_PORT 1
+
+#define MXS_DAI_SAIF0 0
+#define MXS_DAI_SAIF1 1
+
+static struct mxs_saif mxs_saif_en;
+
+static int saif_active[2] = { 0, 0 };
+
+struct mxs_pcm_dma_params mxs_saif_0 = {
+ .name = "mxs-saif-0",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF0,
+ .irq = IRQ_SAIF0_DMA,
+};
+
+struct mxs_pcm_dma_params mxs_saif_1 = {
+ .name = "mxs-saif-1",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SAIF1,
+ .irq = IRQ_SAIF1_DMA,
+};
+
+/*
+* SAIF system clock configuration.
+* Should only be called when port is inactive.
+*/
+static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct clk *saif_clk;
+ u32 scr;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ saif_clk = saif_select->saif_mclk;
+ if (IS_ERR(saif_clk)) {
+ pr_err("%s:failed to get sys_clk\n", __func__);
+ return -EINVAL;
+ }
+ clk_set_rate(saif_clk, freq);
+ clk_enable(saif_clk);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * SAIF Clock dividers
+ * Should only be called when port is inactive.
+ */
+static int mxs_saif_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 scr;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+
+ if (saif_select->saif_en == SAIF0)
+ scr = __raw_readl(SAIF0_CTRL);
+ else
+ scr = __raw_readl(SAIF1_CTRL);
+
+ scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE;
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+
+ switch (div_id) {
+ case IMX_SSP_SYS_MCLK:
+ switch (div) {
+ case 32:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4);
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 64:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 128:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 256:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 512:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
+ scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 48:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
+ scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 96:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
+ scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 192:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
+ scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ case 384:
+ scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
+ scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (saif_select->saif_en == SAIF0)
+ __raw_writel(scr, SAIF0_CTRL);
+ else
+ __raw_writel(scr, SAIF1_CTRL);
+
+ return 0;
+}
+
+/*
+ * SAIF DAI format configuration.
+ * Should only be called when port is inactive.
+ * Note: We don't use the I2S modes but instead manually configure the
+ * SAIF for I2S.
+ */
+static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ u32 scr, stat;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+ if (saif_select->saif_en == SAIF0) {
+ scr = __raw_readl(SAIF0_CTRL);
+ stat = __raw_readl(SAIF0_STAT);
+ } else {
+ scr = __raw_readl(SAIF1_CTRL);
+ stat = __raw_readl(SAIF1_STAT);
+ }
+
+ if (stat & BM_SAIF_STAT_BUSY)
+ return 0;
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /* data frame low 1clk before data */
+ scr |= BM_SAIF_CTRL_DELAY;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /* data frame high with data */
+ scr &= ~BM_SAIF_CTRL_DELAY;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ scr &= ~BM_SAIF_CTRL_JUSTIFY;
+ break;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+ scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+ scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+ scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+ break;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ scr &= ~BM_SAIF_CTRL_SLAVE_MODE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ scr |= BM_SAIF_CTRL_SLAVE_MODE;
+ break;
+ }
+ if (saif_select->saif_en == SAIF0)
+ __raw_writel(scr, SAIF0_CTRL);
+ else
+ __raw_writel(scr, SAIF1_CTRL);
+
+ SAIF_DUMP();
+ return 0;
+}
+
+static int mxs_saif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ /* we cant really change any saif values after saif is enabled*/
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+
+ if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \
+ (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \
+ ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \
+ && (substream->stream == SNDRV_PCM_STREAM_CAPTURE)))
+ cpu_dai->dma_data = &mxs_saif_0;
+ else
+ cpu_dai->dma_data = &mxs_saif_1;
+
+ if (cpu_dai->playback.active && cpu_dai->capture.active)
+ return 0;
+
+ if (saif_select->saif_en == SAIF0)
+ if (saif_active[SAIF0_PORT]++)
+ return 0;
+ if (saif_select->saif_en == SAIF1)
+ if (saif_active[SAIF1_PORT]++)
+ return 0;
+ SAIF_DUMP();
+ return 0;
+}
+
+/*
+ * Should only be called when port is inactive.
+ * although can be called multiple times by upper layers.
+ */
+static int mxs_saif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ u32 scr, stat;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+ if (saif_select->saif_en == SAIF0) {
+ scr = __raw_readl(SAIF0_CTRL);
+ stat = __raw_readl(SAIF0_STAT);
+ } else {
+ scr = __raw_readl(SAIF1_CTRL);
+ stat = __raw_readl(SAIF1_STAT);
+ }
+ /* cant change any parameters when SAIF is running */
+ /* DAI data (word) size */
+ scr &= ~BM_SAIF_CTRL_WORD_LENGTH;
+ scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(0);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(4);
+ scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ scr |= BF_SAIF_CTRL_WORD_LENGTH(8);
+ scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+ break;
+ }
+ /* Tx/Rx config */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* enable TX mode */
+ scr &= ~BM_SAIF_CTRL_READ_MODE;
+ } else {
+ /* enable RX mode */
+ scr |= BM_SAIF_CTRL_READ_MODE;
+ }
+
+ if (saif_select->saif_en == SAIF0)
+ __raw_writel(scr, SAIF0_CTRL);
+ else
+ __raw_writel(scr, SAIF1_CTRL);
+ return 0;
+}
+
+static int mxs_saif_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+ if (saif_select->saif_en == SAIF0)
+ __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF0_CTRL_CLR);
+ else
+ __raw_writel(BM_SAIF_CTRL_CLKGATE, SAIF1_CTRL_CLR);
+ SAIF_DUMP();
+ return 0;
+}
+
+static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ void __iomem *reg;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+
+ if (saif_select->saif_en == SAIF0)
+ reg = (void __iomem *)SAIF0_DATA;
+ else
+ reg = (void __iomem *)SAIF1_DATA;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ /*write a data to saif data register to trigger
+ the transfer*/
+ __raw_writel(0, reg);
+ else
+ /*read a data from saif data register to trigger
+ the receive*/
+ __raw_readl(reg);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ return -EINVAL;
+ }
+ SAIF_DUMP();
+ return 0;
+}
+
+static void mxs_saif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+ /* shutdown SAIF if neither Tx or Rx is active */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return;
+
+ if (saif_select->saif_en == SAIF0) {
+ if (--saif_active[SAIF0_PORT] > 1)
+ return;
+ }
+ if (saif_select->saif_en == SAIF1) {
+ if (--saif_active[SAIF1_PORT])
+ return;
+ }
+}
+
+#ifdef CONFIG_PM
+static int mxs_saif_suspend(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+ /* do we need to disable any clocks? */
+ return 0;
+}
+
+static int mxs_saif_resume(struct snd_soc_dai *dai)
+{
+ if (!dai->active)
+ return 0;
+ /* do we need to enable any clocks? */
+ return 0;
+}
+#else
+#define mxs_saif_suspend NULL
+#define mxs_saif_resume NULL
+#endif
+
+static int fifo_err_counter;
+
+static irqreturn_t saif0_irq(int irq, void *dev_id)
+{
+ if (fifo_err_counter++ % 100 == 0)
+ printk(KERN_ERR "saif0_irq SAIF_STAT %x SAIF_CTRL %x fifo_errs=\
+ %d\n",
+ __raw_readl(SAIF0_STAT),
+ __raw_readl(SAIF0_CTRL),
+ fifo_err_counter);
+ __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \
+ BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF0_STAT_CLR);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t saif1_irq(int irq, void *dev_id)
+{
+ if (fifo_err_counter++ % 100 == 0)
+ printk(KERN_ERR "saif1_irq SAIF_STAT %x SAIF_CTRL %x \
+ fifo_errs=%d\n",
+ __raw_readl(SAIF1_STAT),
+ __raw_readl(SAIF1_CTRL),
+ fifo_err_counter);
+ __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | \
+ BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, SAIF1_STAT_CLR);
+ return IRQ_HANDLED;
+}
+
+static int mxs_saif_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ if (request_irq(IRQ_SAIF0, saif0_irq, 0, "saif0", dai)) {
+ printk(KERN_ERR "%s: failure requesting irq %s\n",
+ __func__, "saif0");
+ return -EBUSY;
+ }
+
+ if (request_irq(IRQ_SAIF1, saif1_irq, 0, "saif1", dai)) {
+ printk(KERN_ERR "%s: failure requesting irq %s\n",
+ __func__, "saif1");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static void mxs_saif_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
+{
+ free_irq(IRQ_SAIF0, dai);
+ free_irq(IRQ_SAIF1, dai);
+}
+
+#define MXS_SAIF_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define MXS_SAIF_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops mxs_saif_dai_ops = {
+ .startup = mxs_saif_startup,
+ .shutdown = mxs_saif_shutdown,
+ .trigger = mxs_saif_trigger,
+ .prepare = mxs_saif_prepare,
+ .hw_params = mxs_saif_hw_params,
+ .set_sysclk = mxs_saif_set_dai_sysclk,
+ .set_clkdiv = mxs_saif_set_dai_clkdiv,
+ .set_fmt = mxs_saif_set_dai_fmt,
+};
+
+struct snd_soc_dai mxs_saif_dai[] = {
+ {
+ .name = "mxs-saif",
+ .probe = mxs_saif_probe,
+ .remove = mxs_saif_remove,
+ .suspend = mxs_saif_suspend,
+ .resume = mxs_saif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SAIF_RATES,
+ .formats = MXS_SAIF_FORMATS,
+ },
+ .ops = &mxs_saif_dai_ops,
+ .private_data = &mxs_saif_en,
+ }
+};
+EXPORT_SYMBOL_GPL(mxs_saif_dai);
+
+static int __init mxs_saif_init(void)
+{
+ return snd_soc_register_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai));
+}
+
+static void __exit mxs_saif_exit(void)
+{
+ snd_soc_unregister_dais(mxs_saif_dai, ARRAY_SIZE(mxs_saif_dai));
+}
+
+module_init(mxs_saif_init);
+module_exit(mxs_saif_exit);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX28 ASoC I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-dai.h b/sound/soc/mxs/mxs-dai.h
new file mode 100644
index 000000000000..c75fa09ae82b
--- /dev/null
+++ b/sound/soc/mxs/mxs-dai.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MXS_SAIF_H
+#define _MXS_SAIF_H
+
+#include <mach/hardware.h>
+
+/* SAIF clock sources */
+#define IMX_SSP_SYS_CLK 0
+#define IMX_SSP_SYS_MCLK 1
+
+#define SAIF0 0
+#define SAIF1 1
+
+/*private info*/
+struct mxs_saif {
+ u8 saif_en;
+#define PLAYBACK_SAIF0_CAPTURE_SAIF1 0
+#define PLAYBACK_SAIF1_CAPTURE_SAIF0 1
+ u16 stream_mapping;
+ struct clk *saif_mclk;
+};
+
+extern struct snd_soc_dai mxs_saif_dai[];
+
+#endif
diff --git a/sound/soc/mxs/mxs-devb-spdif.c b/sound/soc/mxs/mxs-devb-spdif.c
new file mode 100644
index 000000000000..9ed62110dff7
--- /dev/null
+++ b/sound/soc/mxs/mxs-devb-spdif.c
@@ -0,0 +1,90 @@
+/*
+ * ASoC driver for MXS Evk development board
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_spdif.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include "mxs-spdif-dai.h"
+#include "../codecs/mxs_spdif.h"
+#include "mxs-pcm.h"
+
+/* mxs devb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link mxs_devb_dai = {
+ .name = "MXS SPDIF",
+ .stream_name = "MXS SPDIF",
+ .cpu_dai = &mxs_spdif_dai,
+ .codec_dai = &mxs_spdif_codec_dai,
+};
+
+/* mxs devb audio machine driver */
+static struct snd_soc_card snd_soc_machine_mxs_devb = {
+ .name = "mxs-evk",
+ .platform = &mxs_soc_platform,
+ .dai_link = &mxs_devb_dai,
+ .num_links = 1,
+};
+
+/* mxs devb audio subsystem */
+static struct snd_soc_device mxs_devb_snd_devdata = {
+ .card = &snd_soc_machine_mxs_devb,
+ .codec_dev = &soc_spdif_codec_dev_mxs,
+};
+
+static struct platform_device *mxs_devb_snd_device;
+
+static int __init mxs_devb_init(void)
+{
+ int ret = 0;
+
+ mxs_devb_snd_device = platform_device_alloc("soc-audio", 2);
+ if (!mxs_devb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mxs_devb_snd_device,
+ &mxs_devb_snd_devdata);
+ mxs_devb_snd_devdata.dev = &mxs_devb_snd_device->dev;
+ mxs_devb_snd_device->dev.platform_data =
+ &mxs_devb_snd_devdata;
+
+ ret = platform_device_add(mxs_devb_snd_device);
+ if (ret)
+ platform_device_put(mxs_devb_snd_device);
+
+ return ret;
+}
+
+static void __exit mxs_devb_exit(void)
+{
+ platform_device_unregister(mxs_devb_snd_device);
+}
+
+module_init(mxs_devb_init);
+module_exit(mxs_devb_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("MXS EVK development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-devb.c b/sound/soc/mxs/mxs-devb.c
new file mode 100644
index 000000000000..1c73d72466e5
--- /dev/null
+++ b/sound/soc/mxs/mxs-devb.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/dma.h>
+#include <mach/device.h>
+
+#include "mxs-dai.h"
+#include "mxs-pcm.h"
+#include "../codecs/sgtl5000.h"
+
+struct mxs_evk_priv {
+ int sysclk;
+ int hw;
+ struct platform_device *pdev;
+};
+
+static struct mxs_evk_priv card_priv;
+
+static int mxs_evk_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct snd_soc_dai *codec_dai = machine->codec_dai;
+ struct mxs_evk_priv *priv = &card_priv;
+ unsigned int rate = params_rate(params);
+ int ret = 0;
+
+ u32 dai_format;
+
+ /* only need to do this once as capture and playback are sync */
+ if (priv->hw)
+ return 0;
+ priv->hw = 1;
+ priv->sysclk = 512 * rate;
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, (priv->sysclk)/2, 0);
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, rate, 0);
+
+ snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSP_SYS_MCLK, 256);
+ /* set codec to slave mode */
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+ /* set cpu_dai to master mode for playback, slave mode for record */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ /* set the SAIF system clock as output */
+ snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, priv->sysclk, \
+ SND_SOC_CLOCK_OUT);
+
+ return 0;
+}
+
+static int mxs_evk_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+ struct mxs_saif *saif_select = (struct mxs_saif *)cpu_dai->private_data;
+
+ if (((saif_select->stream_mapping == PLAYBACK_SAIF0_CAPTURE_SAIF1) && \
+ (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) || \
+ ((saif_select->stream_mapping == PLAYBACK_SAIF1_CAPTURE_SAIF0) \
+ && (substream->stream == SNDRV_PCM_STREAM_CAPTURE)))
+ saif_select->saif_en = 0;
+ else
+ saif_select->saif_en = 1;
+ return 0;
+}
+
+static void mxs_evk_shutdown(struct snd_pcm_substream *substream)
+{
+ struct mxs_evk_priv *priv = &card_priv;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_link *machine = rtd->dai;
+ struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ priv->hw = 1;
+ else
+ priv->hw = 0;
+}
+
+/*
+ * mxs_evk SGTL5000 audio DAI opserations.
+ */
+static struct snd_soc_ops mxs_evk_ops = {
+ .startup = mxs_evk_startup,
+ .shutdown = mxs_evk_shutdown,
+ .hw_params = mxs_evk_audio_hw_params,
+};
+
+/* mxs_evk machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MIC_IN", NULL, "Mic Jack"},
+
+ /* Line in Jack --> LINE_IN */
+ {"LINE_IN", NULL, "Line In Jack"},
+
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HP_OUT"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const char *line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+ SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+/* mxs_evk card dapm widgets */
+static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static int mxs_evk_sgtl5000_init(struct snd_soc_codec *codec)
+{
+ /* Add mxs_evk specific widgets */
+ snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets,
+ ARRAY_SIZE(mxs_evk_dapm_widgets));
+
+ /* Set up mxs_evk specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_disable_pin(codec, "Line In Jack");
+
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+/* mxs_evk digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link mxs_evk_dai = {
+ .name = "SGTL5000",
+ .stream_name = "SGTL5000",
+ .codec_dai = &sgtl5000_dai,
+ .init = mxs_evk_sgtl5000_init,
+ .ops = &mxs_evk_ops,
+};
+
+static int mxs_evk_card_remove(struct platform_device *pdev)
+{
+ struct mxs_evk_priv *priv = &card_priv;
+ struct mxs_audio_platform_data *plat;
+ if (priv->pdev) {
+ plat = priv->pdev->dev.platform_data;
+ if (plat->finit)
+ plat->finit();
+ }
+
+ return 0;
+}
+
+static struct snd_soc_card snd_soc_card_mxs_evk = {
+ .name = "mxs-evk",
+ .platform = &mxs_soc_platform,
+ .dai_link = &mxs_evk_dai,
+ .num_links = 1,
+ .remove = mxs_evk_card_remove,
+};
+
+static struct snd_soc_device mxs_evk_snd_devdata = {
+ .card = &snd_soc_card_mxs_evk,
+ .codec_dev = &soc_codec_dev_sgtl5000,
+};
+
+static int __devinit mxs_evk_sgtl5000_probe(struct platform_device *pdev)
+{
+ struct mxs_audio_platform_data *plat = pdev->dev.platform_data;
+ struct mxs_saif *saif_select;
+ int ret = -EINVAL;
+ if (plat->init && plat->init())
+ goto err_plat_init;
+ mxs_evk_dai.cpu_dai = &mxs_saif_dai[0];
+ saif_select = (struct mxs_saif *)mxs_evk_dai.cpu_dai->private_data;
+ saif_select->stream_mapping = PLAYBACK_SAIF0_CAPTURE_SAIF1;
+ saif_select->saif_mclk = plat->saif_mclock;
+ return 0;
+err_plat_init:
+ if (plat->finit)
+ plat->finit();
+ return ret;
+}
+
+static int mxs_evk_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct mxs_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->finit)
+ plat->finit();
+ return 0;
+}
+
+static struct platform_driver mxs_evk_sgtl5000_audio_driver = {
+ .probe = mxs_evk_sgtl5000_probe,
+ .remove = mxs_evk_sgtl5000_remove,
+ .driver = {
+ .name = "mxs-sgtl5000",
+ },
+};
+
+static struct platform_device *mxs_evk_snd_device;
+
+static int __init mxs_evk_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&mxs_evk_sgtl5000_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ mxs_evk_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!mxs_evk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mxs_evk_snd_device, &mxs_evk_snd_devdata);
+ mxs_evk_snd_devdata.dev = &mxs_evk_snd_device->dev;
+ ret = platform_device_add(mxs_evk_snd_device);
+
+ if (ret)
+ platform_device_put(mxs_evk_snd_device);
+
+ return ret;
+}
+
+static void __exit mxs_evk_exit(void)
+{
+ platform_driver_unregister(&mxs_evk_sgtl5000_audio_driver);
+ platform_device_unregister(mxs_evk_snd_device);
+}
+
+module_init(mxs_evk_init);
+module_exit(mxs_evk_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SGTL5000 Driver for MXS EVK");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-evk-adc.c b/sound/soc/mxs/mxs-evk-adc.c
new file mode 100644
index 000000000000..16c1cb4fab96
--- /dev/null
+++ b/sound/soc/mxs/mxs-evk-adc.c
@@ -0,0 +1,198 @@
+/*
+ * ASoC driver for Freescale MXS EVK board
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+
+#include "../codecs/mxs-adc-codec.h"
+#include "mxs-adc.h"
+#include "mxs-pcm.h"
+
+/* mxs evk machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* HPR/HPL OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HPR"},
+ {"Headphone Jack", NULL, "HPL"},
+
+ /* SPEAKER OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "SPEAKER"},
+};
+
+static int mxs_evk_jack_func;
+static int mxs_evk_spk_func;
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+
+static const struct soc_enum mxs_evk_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static int mxs_evk_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = mxs_evk_jack_func;
+ return 0;
+}
+
+static int mxs_evk_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (mxs_evk_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ mxs_evk_jack_func = ucontrol->value.enumerated.item[0];
+ if (mxs_evk_jack_func)
+ snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+
+static int mxs_evk_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = mxs_evk_spk_func;
+ return 0;
+}
+
+static int mxs_evk_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (mxs_evk_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ mxs_evk_spk_func = ucontrol->value.enumerated.item[0];
+ if (mxs_evk_spk_func)
+ snd_soc_dapm_enable_pin(codec, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+ snd_soc_dapm_sync(codec);
+ return 1;
+}
+/* mxs evk card dapm widgets */
+static const struct snd_soc_dapm_widget mxs_evk_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new mxs_evk_controls[] = {
+ SOC_ENUM_EXT("HP Playback Switch", mxs_evk_enum[0], mxs_evk_get_jack,
+ mxs_evk_set_jack),
+ SOC_ENUM_EXT("Speaker Playback Switch", mxs_evk_enum[1],
+ mxs_evk_get_spk, mxs_evk_set_spk),
+};
+
+static int mxs_evk_codec_init(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ /* Add mxs evk specific controls */
+ snd_soc_add_controls(codec, mxs_evk_controls,
+ ARRAY_SIZE(mxs_evk_controls));
+
+ /* Add mxs evk specific widgets */
+ snd_soc_dapm_new_controls(codec, mxs_evk_dapm_widgets,
+ ARRAY_SIZE(mxs_evk_dapm_widgets));
+
+ /* Set up mxs evk specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_sync(codec);
+ /* default on */
+ mxs_evk_jack_func = 1;
+ mxs_evk_spk_func = 1;
+
+ return ret;
+}
+/* mxs evk dac/adc audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link mxs_evk_codec_dai = {
+ .name = "MXS ADC/DAC",
+ .stream_name = "MXS ADC/DAC",
+ .cpu_dai = &mxs_adc_dai,
+ .codec_dai = &mxs_codec_dai,
+ .init = mxs_evk_codec_init,
+};
+
+/* mxs evk audio machine driver */
+static struct snd_soc_card snd_soc_card_mxs_evk = {
+ .name = "MXS EVK",
+ .platform = &mxs_soc_platform,
+ .dai_link = &mxs_evk_codec_dai,
+ .num_links = 1,
+};
+
+/* mxs evk audio subsystem */
+static struct snd_soc_device mxs_evk_snd_devdata = {
+ .card = &snd_soc_card_mxs_evk,
+ .codec_dev = &soc_codec_dev_mxs,
+};
+
+static struct platform_device *mxs_evk_snd_device;
+
+static int __init mxs_evk_adc_init(void)
+{
+ int ret = 0;
+
+ mxs_evk_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!mxs_evk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mxs_evk_snd_device,
+ &mxs_evk_snd_devdata);
+ mxs_evk_snd_devdata.dev = &mxs_evk_snd_device->dev;
+ mxs_evk_snd_device->dev.platform_data =
+ &mxs_evk_snd_devdata;
+
+ ret = platform_device_add(mxs_evk_snd_device);
+ if (ret)
+ platform_device_put(mxs_evk_snd_device);
+
+ return ret;
+}
+
+static void __exit mxs_evk_adc_exit(void)
+{
+ platform_device_unregister(mxs_evk_snd_device);
+}
+
+module_init(mxs_evk_adc_init);
+module_exit(mxs_evk_adc_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("MXS EVK board ADC/DAC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c
new file mode 100644
index 000000000000..dac91e3eb8fb
--- /dev/null
+++ b/sound/soc/mxs/mxs-pcm.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/dmaengine.h>
+
+#include "mxs-pcm.h"
+static const struct snd_pcm_hardware mxs_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 255,
+ .buffer_bytes_max = 64 * 1024,
+ .fifo_size = 32,
+};
+
+/*
+ * Required to request DMA channels
+ */
+struct device *mxs_pcm_dev;
+
+struct mxs_runtime_data {
+ u32 dma_ch;
+ u32 dma_period;
+ u32 dma_totsize;
+ unsigned long appl_ptr_bytes;
+ int format;
+
+ struct mxs_pcm_dma_params *params;
+ struct mxs_dma_desc *dma_desc_array[255];
+};
+
+static irqreturn_t mxs_pcm_dma_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = substream->runtime->private_data;
+ struct mxs_dma_info dma_info;
+ void *pdma;
+ unsigned long prev_appl_offset, appl_count, cont, appl_ptr_bytes;
+
+ mxs_dma_get_info(prtd->dma_ch, &dma_info);
+
+ if (dma_info.status) {
+ printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n",
+ __func__, prtd->params->dma_ch, prtd->params->name);
+ mxs_dma_ack_irq(prtd->dma_ch);
+ } else {
+ if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) &&
+ (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
+ (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) &&
+ ((prtd->format == SNDRV_PCM_FORMAT_S24_LE)
+ || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) {
+
+ appl_ptr_bytes =
+ frames_to_bytes(runtime,
+ runtime->control->appl_ptr);
+
+ appl_count = appl_ptr_bytes - prtd->appl_ptr_bytes;
+ prev_appl_offset =
+ prtd->appl_ptr_bytes % prtd->dma_totsize;
+ cont = prtd->dma_totsize - prev_appl_offset;
+
+ if (appl_count > cont) {
+ pdma = runtime->dma_area + prev_appl_offset;
+ memmove(pdma + 1, pdma, cont - 1);
+ pdma = runtime->dma_area;
+ memmove(pdma + 1, pdma, appl_count - cont - 1);
+ } else {
+ pdma = runtime->dma_area + prev_appl_offset;
+ memmove(pdma + 1, pdma, appl_count - 1);
+ }
+ prtd->appl_ptr_bytes = appl_ptr_bytes;
+ }
+ mxs_dma_ack_irq(prtd->dma_ch);
+ snd_pcm_period_elapsed(substream);
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * Make a circular DMA descriptor list
+ */
+static int mxs_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ dma_addr_t dma_buffer_phys;
+ int periods_num, playback, i;
+
+ playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ periods_num = prtd->dma_totsize / prtd->dma_period;
+
+ dma_buffer_phys = runtime->dma_addr;
+
+ /* Reset DMA channel, enable interrupt */
+ mxs_dma_reset(prtd->dma_ch);
+
+ /* Set up a DMA chain to sent DMA buffer */
+ for (i = 0; i < periods_num; i++) {
+ int ret;
+ /* Link with previous command */
+ prtd->dma_desc_array[i]->cmd.cmd.bits.bytes = prtd->dma_period;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.irq = 1;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.dec_sem = 0;
+ prtd->dma_desc_array[i]->cmd.cmd.bits.chain = 1;
+ /* Set DMA direction */
+ if (playback)
+ prtd->dma_desc_array[i]->cmd.cmd.bits.command = \
+ DMA_READ;
+ else
+ prtd->dma_desc_array[i]->cmd.cmd.bits.command = \
+ DMA_WRITE;
+
+ prtd->dma_desc_array[i]->cmd.address = dma_buffer_phys;
+
+ ret = mxs_dma_desc_append(prtd->dma_ch, \
+ prtd->dma_desc_array[i]);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to append DMA descriptor\n",
+ __func__);
+ return ret;
+ }
+ /* Next data chunk */
+ dma_buffer_phys += prtd->dma_period;
+ }
+
+ return 0;
+}
+
+/*
+ * Stop circular DMA descriptor list
+ * We should not stop DMA in a middle of current transaction once we receive
+ * stop request from ALSA core. This function finds the next DMA descriptor
+ * and set it up to decrement DMA channel semaphore. So the current transaction
+ * is the last data transfer.
+ */
+static void mxs_pcm_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_dma_info dma_info;
+ int desc;
+
+ /* Freez DMA channel for a moment */
+ mxs_dma_freeze(prtd->dma_ch);
+ mxs_dma_get_info(prtd->dma_ch, &dma_info);
+
+ desc = (dma_info.buf_addr - runtime->dma_addr) / prtd->dma_period;
+
+ /* Set up the next descriptor to decrement DMA channel sempahore */
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.bytes = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.pio_words = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.dec_sem = 1;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.irq = 0;
+ prtd->dma_desc_array[(desc + 1)%8]->cmd.cmd.bits.command = NO_DMA_XFER;
+
+ mxs_dma_unfreeze(prtd->dma_ch);
+
+ mxs_dma_disable(prtd->dma_ch);
+}
+
+static int mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+ switch (cmd) {
+
+ case SNDRV_PCM_TRIGGER_START:
+ if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) &&
+ (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
+ (runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) &&
+ ((prtd->format == SNDRV_PCM_FORMAT_S24_LE)
+ || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) {
+ prtd->appl_ptr_bytes =
+ frames_to_bytes(runtime,
+ runtime->control->appl_ptr);
+ memmove(runtime->dma_area + 1, runtime->dma_area,
+ prtd->appl_ptr_bytes - 1);
+ }
+ mxs_dma_enable(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ mxs_pcm_stop(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ mxs_dma_unfreeze(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ mxs_dma_freeze(prtd->dma_ch);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+mxs_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_dma_info dma_info;
+ unsigned int offset;
+ dma_addr_t pos;
+
+ mxs_dma_get_info(prtd->params->dma_ch, &dma_info);
+ pos = dma_info.buf_addr;
+
+ offset = bytes_to_frames(runtime, pos - runtime->dma_addr);
+
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int mxs_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct mxs_runtime_data *prtd = substream->runtime->private_data;
+
+ prtd->dma_period = params_period_bytes(hw_params);
+ prtd->dma_totsize = params_buffer_bytes(hw_params);
+ prtd->format = params_format(hw_params);
+
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int mxs_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int mxs_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ struct mxs_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
+ int desc_num = mxs_pcm_hardware.periods_max;
+ int desc;
+ int ret;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ prtd->params = dma_data;
+ prtd->dma_ch = dma_data->dma_ch;
+
+ ret = mxs_dma_request(prtd->dma_ch, mxs_pcm_dev,
+ prtd->params->name);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n",
+ __func__, dma_data->dma_bus, dma_data->dma_ch);
+ return ret;
+ }
+
+ /* Allocate memory for data and pio DMA descriptors */
+ for (desc = 0; desc < desc_num; desc++) {
+ prtd->dma_desc_array[desc] = mxs_dma_alloc_desc();
+ if (prtd->dma_desc_array[desc] == NULL) {
+ printk(KERN_ERR"%s Unable to allocate DMA command %d\n",
+ __func__, desc);
+ goto err;
+ }
+ }
+
+ ret = request_irq(prtd->params->irq, mxs_pcm_dma_irq, 0,
+ "MXS PCM DMA", substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__,
+ prtd->params->irq);
+ goto err;
+ }
+ /* Enable completion interrupt */
+ mxs_dma_ack_irq(prtd->dma_ch);
+ mxs_dma_enable_irq(prtd->dma_ch, 1);
+
+ return 0;
+
+err:
+ while (--desc >= 0)
+ mxs_dma_free_desc(prtd->dma_desc_array[desc]);
+ mxs_dma_release(prtd->dma_ch, mxs_pcm_dev);
+
+ return ret;
+}
+
+static int mxs_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd;
+ int ret;
+
+ /* Ensure that buffer size is a multiple of the period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &mxs_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct mxs_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+
+ ret = mxs_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "mxs_pcm: Failed to request channels\n");
+ kfree(prtd);
+ return ret;
+ }
+ return 0;
+}
+
+static int mxs_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ int desc_num = mxs_pcm_hardware.periods_max;
+ int desc;
+
+ static LIST_HEAD(list);
+ mxs_dma_disable(prtd->dma_ch);
+ /* Free DMA irq */
+ free_irq(prtd->params->irq, substream);
+ mxs_dma_get_cooked(prtd->dma_ch, &list);
+ /* Free DMA channel*/
+ mxs_dma_reset(prtd->dma_ch);
+ for (desc = 0; desc < desc_num; desc++)
+ mxs_dma_free_desc(prtd->dma_desc_array[desc]);
+ mxs_dma_release(prtd->dma_ch, mxs_pcm_dev);
+
+ /* Free private runtime data */
+ kfree(prtd);
+ return 0;
+}
+
+static int mcs_pcm_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t hwoff, void __user *buf,
+ snd_pcm_uframes_t frames)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mxs_runtime_data *prtd = runtime->private_data;
+ char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+ unsigned long count = frames_to_bytes(runtime, frames);
+
+ /* For S/PDIF 24-bit playback, fix the buffer. Code taken from
+ snd_pcm_lib_write_transfer() and snd_pcm_lib_read_transfer()
+ in sound/core/pcm_lib.c */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if ((prtd->params->dma_ch == MXS_DMA_CHANNEL_AHB_APBX_SPDIF) &&
+ ((prtd->format == SNDRV_PCM_FORMAT_S24_LE)
+ || (prtd->format == SNDRV_PCM_FORMAT_S20_3LE))) {
+ if (copy_from_user(hwbuf + 1, buf, count - 1))
+ return -EFAULT;
+ } else {
+ if (copy_from_user(hwbuf, buf, count))
+ return -EFAULT;
+ }
+ } else {
+ if (copy_to_user(buf, hwbuf, count))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int mxs_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+}
+
+struct snd_pcm_ops mxs_pcm_ops = {
+ .open = mxs_pcm_open,
+ .close = mxs_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = mxs_pcm_hw_params,
+ .hw_free = mxs_pcm_hw_free,
+ .prepare = mxs_pcm_prepare,
+ .trigger = mxs_pcm_trigger,
+ .pointer = mxs_pcm_pointer,
+ .copy = mcs_pcm_copy,
+ .mmap = mxs_pcm_mmap,
+};
+
+static u64 mxs_pcm_dma_mask = DMA_BIT_MASK(32);
+
+static int mxs_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ size_t size = mxs_pcm_hardware.buffer_bytes_max;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &mxs_pcm_dma_mask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL,
+ size, size);
+
+ return 0;
+}
+
+static void mxs_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+/*
+ * We need probe/remove callbacks to setup mxs_pcm_dev
+ */
+static int mxs_pcm_probe(struct platform_device *pdev)
+{
+ mxs_pcm_dev = &pdev->dev;
+ return 0;
+}
+
+static int mxs_pcm_remove(struct platform_device *pdev)
+{
+ mxs_pcm_dev = NULL;
+ return 0;
+}
+
+struct snd_soc_platform mxs_soc_platform = {
+ .name = "MXS Audio",
+ .pcm_ops = &mxs_pcm_ops,
+ .probe = mxs_pcm_probe,
+ .remove = mxs_pcm_remove,
+ .pcm_new = mxs_pcm_new,
+ .pcm_free = mxs_pcm_free,
+};
+EXPORT_SYMBOL_GPL(mxs_soc_platform);
+
+static int __init mxs_pcm_init(void)
+{
+ return snd_soc_register_platform(&mxs_soc_platform);
+}
+
+static void __exit mxs_pcm_exit(void)
+{
+ snd_soc_unregister_platform(&mxs_soc_platform);
+}
+module_init(mxs_pcm_init);
+module_exit(mxs_pcm_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXS DMA Module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h
new file mode 100644
index 000000000000..08a9d92e2afe
--- /dev/null
+++ b/sound/soc/mxs/mxs-pcm.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MXS_PCM_H
+#define _MXS_PCM_H
+
+struct mxs_pcm_dma_params {
+ char *name;
+ int dma_bus; /* DMA bus */
+ int dma_ch; /* DMA channel number */
+ int irq; /* DMA interrupt number */
+};
+
+extern struct snd_soc_platform mxs_soc_platform;
+
+#endif
diff --git a/sound/soc/mxs/mxs-spdif-dai.c b/sound/soc/mxs/mxs-spdif-dai.c
new file mode 100644
index 000000000000..052c404c496d
--- /dev/null
+++ b/sound/soc/mxs/mxs-spdif-dai.c
@@ -0,0 +1,203 @@
+/*
+ * ALSA SoC SPDIF Audio Layer for MXS
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc.
+ *
+ * Based on stmp3xxx_spdif_dai.c
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <mach/hardware.h>
+
+#include "../codecs/mxs_spdif.h"
+#include "mxs-pcm.h"
+
+#define REGS_SPDIF_BASE IO_ADDRESS(SPDIF_PHYS_ADDR)
+
+struct mxs_pcm_dma_params mxs_spdif = {
+ .name = "mxs spdif",
+ .dma_ch = MXS_DMA_CHANNEL_AHB_APBX_SPDIF,
+ .irq = IRQ_SPDIF_DMA,
+};
+
+static irqreturn_t mxs_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg = 0;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = __raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL);
+ underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "underflow detected SPDIF\n");
+
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "overflow detected SPDIF\n");
+
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ } else
+ printk(KERN_WARNING "Unknown SPDIF error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int mxs_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_RUN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_RUN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mxs_spdif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int ret;
+
+ if (playback) {
+ irq = IRQ_SPDIF_ERROR;
+ cpu_dai->dma_data = &mxs_spdif;
+ }
+
+ ret = request_irq(irq, mxs_err_irq, 0, "Mxs SPDIF Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n",
+ __func__, IRQ_SPDIF_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ }
+
+ return 0;
+}
+
+static void mxs_spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ free_irq(IRQ_SPDIF_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int mxs_spdif_suspend(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int mxs_spdif_resume(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define mxs_spdif_suspend NULL
+#define mxs_spdif_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai_ops mxs_spdif_dai_ops = {
+ .startup = mxs_spdif_startup,
+ .shutdown = mxs_spdif_shutdown,
+ .trigger = mxs_spdif_trigger,
+};
+
+struct snd_soc_dai mxs_spdif_dai = {
+ .name = "mxs-spdif",
+ .id = 0,
+ .suspend = mxs_spdif_suspend,
+ .resume = mxs_spdif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SPDIF_RATES,
+ .formats = MXS_SPDIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = MXS_SPDIF_RATES,
+ .formats = MXS_SPDIF_FORMATS,
+ },
+ .ops = &mxs_spdif_dai_ops,
+};
+EXPORT_SYMBOL_GPL(mxs_spdif_dai);
+
+static int __init mxs_spdif_dai_init(void)
+{
+ return snd_soc_register_dai(&mxs_spdif_dai);
+}
+
+static void __exit mxs_spdif_dai_exit(void)
+{
+ snd_soc_unregister_dai(&mxs_spdif_dai);
+}
+module_init(mxs_spdif_dai_init);
+module_exit(mxs_spdif_dai_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("MXS SPDIF DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-spdif-dai.h b/sound/soc/mxs/mxs-spdif-dai.h
new file mode 100644
index 000000000000..035369af9676
--- /dev/null
+++ b/sound/soc/mxs/mxs-spdif-dai.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter
+ *
+ * Author: Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_SPDIF_H
+#define _STMP3XXX_SPDIF_H
+extern struct snd_soc_dai mxs_spdif_dai;
+#endif
diff --git a/sound/soc/stmp3xxx/Kconfig b/sound/soc/stmp3xxx/Kconfig
new file mode 100644
index 000000000000..5a3ee6067f90
--- /dev/null
+++ b/sound/soc/stmp3xxx/Kconfig
@@ -0,0 +1,31 @@
+config SND_STMP3XXX_SOC
+ tristate "SoC Audio for the SigmaTel STMP3XXX chips"
+ depends on ARCH_STMP3XXX && SND_SOC
+ select SND_PCM
+ help
+ Say Y or M if you want to add support for codecs embedded into
+ the STMP3XXX chips.
+
+config SND_STMP3XXX_SOC_DAI
+ tristate
+
+config SND_STMP3XXX_SOC_SPDIF_DAI
+ tristate
+
+config SND_STMP3XXX_SOC_STMP3780_DEVB
+ tristate "SoC Audio support for STMP3780 Development Board"
+ depends on SND_STMP3XXX_SOC && ARCH_STMP378X
+ select SND_STMP3XXX_SOC_DAI
+ select SND_SOC_STMP378X_CODEC
+ help
+ Say Y if you want to add support for SoC audio on stmp3780 development
+ board with the stmp378x codec.
+
+config SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF
+ tristate "SoC SPDIF support for STMP3780 Development Board"
+ depends on SND_STMP3XXX_SOC && ARCH_STMP378X
+ select SND_STMP3XXX_SOC_SPDIF_DAI
+ select SND_SOC_STMP3XXX_SPDIF
+ help
+ Say Y if you want to add support for SoC audio on stmp3780 development
+ board with the SPDIF transmitter.
diff --git a/sound/soc/stmp3xxx/Makefile b/sound/soc/stmp3xxx/Makefile
new file mode 100644
index 000000000000..d84ece16be33
--- /dev/null
+++ b/sound/soc/stmp3xxx/Makefile
@@ -0,0 +1,12 @@
+# STMP3XXX platfrom support
+snd-soc-stmp3xxx-objs := stmp3xxx_pcm.o
+snd-soc-stmp3xxx-dai-objs := stmp3xxx_dai.o
+snd-soc-stmp3xxx-spdif-dai-objs := stmp3xxx_spdif_dai.o
+snd-soc-stmp3780-devb-objs := stmp3780_devb.o
+snd-soc-stmp3780-devb-spdif-objs := stmp3780_devb_spdif.o
+
+obj-$(CONFIG_SND_STMP3XXX_SOC) += snd-soc-stmp3xxx.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_DAI) += snd-soc-stmp3xxx-dai.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_SPDIF_DAI) += snd-soc-stmp3xxx-spdif-dai.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB) += snd-soc-stmp3780-devb.o
+obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF) += snd-soc-stmp3780-devb-spdif.o
diff --git a/sound/soc/stmp3xxx/stmp3780_devb.c b/sound/soc/stmp3xxx/stmp3780_devb.c
new file mode 100644
index 000000000000..e36e4a3e3621
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3780_devb.c
@@ -0,0 +1,92 @@
+/*
+ * ASoC driver for Freescale STMP3780 development board
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/regs-apbx.h>
+
+#include "../codecs/stmp378x_codec.h"
+#include "stmp3xxx_dai.h"
+#include "stmp3xxx_pcm.h"
+
+/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link stmp3780_devb_dai = {
+ .name = "STMP378X ADC/DAC",
+ .stream_name = "STMP378X ADC/DAC",
+ .cpu_dai = &stmp3xxx_adc_dai,
+ .codec_dai = &stmp378x_codec_dai,
+};
+
+/* stmp3780 devb audio machine driver */
+static struct snd_soc_card snd_soc_card_stmp3780_devb = {
+ .name = "STMP3780 Devb",
+ .platform = &stmp3xxx_soc_platform,
+ .dai_link = &stmp3780_devb_dai,
+ .num_links = 1,
+};
+
+/* stmp3780 devb audio subsystem */
+static struct snd_soc_device stmp3780_devb_snd_devdata = {
+ .card = &snd_soc_card_stmp3780_devb,
+ .codec_dev = &soc_codec_dev_stmp378x,
+};
+
+static struct platform_device *stmp3780_devb_snd_device;
+
+static int __init stmp3780_devb_init(void)
+{
+ int ret = 0;
+
+ stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!stmp3780_devb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(stmp3780_devb_snd_device,
+ &stmp3780_devb_snd_devdata);
+ stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev;
+ stmp3780_devb_snd_device->dev.platform_data =
+ &stmp3780_devb_snd_devdata;
+
+ ret = platform_device_add(stmp3780_devb_snd_device);
+ if (ret)
+ platform_device_put(stmp3780_devb_snd_device);
+
+ return ret;
+}
+
+static void __exit stmp3780_devb_exit(void)
+{
+ platform_device_unregister(stmp3780_devb_snd_device);
+}
+
+module_init(stmp3780_devb_init);
+module_exit(stmp3780_devb_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("STMP3780 development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3780_devb_spdif.c b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c
new file mode 100644
index 000000000000..4e2377e23370
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c
@@ -0,0 +1,95 @@
+/*
+ * ASoC driver for STMP3780 development board
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/regs-apbx.h>
+
+#include <mach/stmp3xxx.h>
+
+#include "../codecs/stmp3xxx_spdif.h"
+#include "stmp3xxx_spdif_dai.h"
+#include "stmp3xxx_pcm.h"
+
+extern int spdif_pinmux(int);
+
+/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link stmp3780_devb_dai = {
+ .name = "STMP3XXX SPDIF",
+ .stream_name = "STMP3XXX SPDIF",
+ .cpu_dai = &stmp3xxx_spdif_dai,
+ .codec_dai = &stmp3xxx_spdif_codec_dai,
+};
+
+/* stmp3780 devb audio machine driver */
+static struct snd_soc_card snd_soc_machine_stmp3780_devb = {
+ .name = "STMP3780 Devb",
+ .platform = &stmp3xxx_soc_platform,
+ .dai_link = &stmp3780_devb_dai,
+ .num_links = 1,
+};
+
+/* stmp3780 devb audio subsystem */
+static struct snd_soc_device stmp3780_devb_snd_devdata = {
+ .card = &snd_soc_machine_stmp3780_devb,
+ .codec_dev = &soc_spdif_codec_dev_stmp3xxx,
+};
+
+static struct platform_device *stmp3780_devb_snd_device;
+
+static int __init stmp3780_devb_init(void)
+{
+ int ret = 0;
+
+ stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!stmp3780_devb_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(stmp3780_devb_snd_device,
+ &stmp3780_devb_snd_devdata);
+ stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev;
+ stmp3780_devb_snd_device->dev.platform_data =
+ &stmp3780_devb_snd_devdata;
+
+ ret = platform_device_add(stmp3780_devb_snd_device);
+ if (ret)
+ platform_device_put(stmp3780_devb_snd_device);
+
+ spdif_pinmux(1);
+
+ return ret;
+}
+
+static void __exit stmp3780_devb_exit(void)
+{
+ spdif_pinmux(0);
+ platform_device_unregister(stmp3780_devb_snd_device);
+}
+
+module_init(stmp3780_devb_init);
+module_exit(stmp3780_devb_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("STMP3780 development board ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.c b/sound/soc/stmp3xxx/stmp3xxx_dai.c
new file mode 100644
index 000000000000..ab367cc6a7fe
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_dai.c
@@ -0,0 +1,257 @@
+/*
+ * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/regs-apbx.h>
+#include <mach/regs-audioin.h>
+#include <mach/regs-audioout.h>
+#include "stmp3xxx_pcm.h"
+#include <mach/platform.h>
+
+#define STMP3XXX_ADC_RATES SNDRV_PCM_RATE_8000_192000
+#define STMP3XXX_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct stmp3xxx_pcm_dma_params stmp3xxx_audio_in = {
+ .name = "stmp3xxx adc",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 0,
+ .irq = IRQ_ADC_DMA,
+};
+
+struct stmp3xxx_pcm_dma_params stmp3xxx_audio_out = {
+ .name = "stmp3xxx dac",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 1,
+ .irq = IRQ_DAC_DMA,
+};
+
+static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL);
+ underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ;
+ } else {
+ ctrl_reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL);
+ underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "%s underflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ __raw_writel(
+ BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(
+ BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "%s overflow detected\n",
+ playback ? "DAC" : "ADC");
+
+ if (playback)
+ __raw_writel(
+ BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ } else
+ printk(KERN_WARNING "Unknown DAC error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int stmp3xxx_adc_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_RUN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (playback)
+ __raw_writel(BM_AUDIOOUT_CTRL_RUN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ else
+ __raw_writel(BM_AUDIOIN_CTRL_RUN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int stmp3xxx_adc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int ret;
+
+ if (playback) {
+ irq = IRQ_DAC_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_audio_out;
+ } else {
+ irq = IRQ_ADC_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_audio_in;
+ }
+
+ ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx DAC/ADC Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n",
+ __func__, IRQ_DAC_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET);
+ } else {
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET);
+ }
+
+ return 0;
+}
+
+static void stmp3xxx_adc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ __raw_writel(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR);
+ free_irq(IRQ_DAC_ERROR, substream);
+ } else {
+ __raw_writel(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR);
+ free_irq(IRQ_ADC_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_adc_suspend(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int stmp3xxx_adc_resume(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define stmp3xxx_adc_suspend NULL
+#define stmp3xxx_adc_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai_ops stmp3xxx_adc_dai_ops = {
+ .startup = stmp3xxx_adc_startup,
+ .shutdown = stmp3xxx_adc_shutdown,
+ .trigger = stmp3xxx_adc_trigger,
+};
+
+struct snd_soc_dai stmp3xxx_adc_dai = {
+ .name = "stmp3xxx adc/dac",
+ .id = 0,
+ .suspend = stmp3xxx_adc_suspend,
+ .resume = stmp3xxx_adc_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_ADC_RATES,
+ .formats = STMP3XXX_ADC_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_ADC_RATES,
+ .formats = STMP3XXX_ADC_FORMATS,
+ },
+ .ops = &stmp3xxx_adc_dai_ops,
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_adc_dai);
+
+static int __init stmp3xxx_dai_init(void)
+{
+ return snd_soc_register_dai(&stmp3xxx_adc_dai);
+}
+
+static void __exit stmp3xxx_dai_exit(void)
+{
+ snd_soc_unregister_dai(&stmp3xxx_adc_dai);
+}
+module_init(stmp3xxx_dai_init);
+module_exit(stmp3xxx_dai_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("stmp3xxx dac/adc DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.h b/sound/soc/stmp3xxx/stmp3xxx_dai.h
new file mode 100644
index 000000000000..409256a86d15
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_dai.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_DEV_H
+#define _STMP3XXX_DEV_H
+extern struct snd_soc_dai stmp3xxx_adc_dai;
+#endif
diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.c b/sound/soc/stmp3xxx/stmp3xxx_pcm.c
new file mode 100644
index 000000000000..cff13a5b4234
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.c
@@ -0,0 +1,461 @@
+/*
+ * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/platform.h>
+
+#include <mach/regs-apbx.h>
+
+#include "stmp3xxx_pcm.h"
+
+static const struct snd_pcm_hardware stmp3xxx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 32,
+ .period_bytes_max = 8192,
+ .periods_min = 1,
+ .periods_max = 255,
+ .buffer_bytes_max = 64 * 1024,
+ .fifo_size = 32,
+};
+
+/*
+ * Required to request DMA channels
+ */
+struct device *stmp3xxx_pcm_dev;
+
+struct stmp3xxx_runtime_data {
+ u32 dma_ch;
+ u32 dma_period;
+ u32 dma_totsize;
+
+ struct stmp3xxx_pcm_dma_params *params;
+ struct stmp3xxx_dma_descriptor *dma_desc_array;
+};
+
+static irqreturn_t stmp3xxx_pcm_dma_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+
+#ifdef CONFIG_ARCH_STMP37XX
+ u32 err_mask = 1 << (16 + prtd->params->dma_ch);
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ u32 err_mask = 1 << prtd->params->dma_ch;
+#endif
+ u32 irq_mask = 1 << prtd->params->dma_ch;
+
+#ifdef CONFIG_ARCH_STMP37XX
+ if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL1) & err_mask) {
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL2) & err_mask) {
+#endif
+ printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n",
+ __func__, prtd->params->dma_ch, prtd->params->name);
+#ifdef CONFIG_ARCH_STMP37XX
+ __raw_writel(err_mask, REGS_APBX_BASE + HW_APBX_CTRL1_CLR);
+#endif
+#ifdef CONFIG_ARCH_STMP378X
+ __raw_writel(err_mask, REGS_APBX_BASE + HW_APBX_CTRL2_CLR);
+#endif
+ } else if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL1) & irq_mask) {
+ stmp3xxx_dma_clear_interrupt(prtd->dma_ch);
+ snd_pcm_period_elapsed(substream);
+ } else
+ printk(KERN_WARNING "%s: Unknown interrupt\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Make a circular DMA descriptor list
+ */
+static int stmp3xxx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ dma_addr_t dma_buffer_phys;
+ int periods_num, playback, i;
+
+ playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ periods_num = prtd->dma_totsize / prtd->dma_period;
+ dma_buffer_phys = runtime->dma_addr;
+
+ /* Reset DMA channel, enable interrupt */
+ stmp3xxx_dma_reset_channel(prtd->dma_ch);
+
+ /* Set up a DMA chain to sent DMA buffer */
+ for (i = 0; i < periods_num; i++) {
+ int next = (i + 1) % periods_num;
+ u32 cmd = 0;
+
+ /* Link with previous command */
+ prtd->dma_desc_array[i].command->next =
+ prtd->dma_desc_array[next].handle;
+
+ prtd->dma_desc_array[i].next_descr =
+ &prtd->dma_desc_array[next];
+
+ cmd = BF(prtd->dma_period, APBX_CHn_CMD_XFER_COUNT) |
+ BM_APBX_CHn_CMD_IRQONCMPLT |
+ BM_APBX_CHn_CMD_CHAIN;
+
+ /* Set DMA direction */
+ if (playback)
+ cmd |= BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ,
+ APBX_CHn_CMD_COMMAND);
+ else
+ cmd |= BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE,
+ APBX_CHn_CMD_COMMAND);
+
+ prtd->dma_desc_array[i].command->cmd = cmd;
+ prtd->dma_desc_array[i].command->buf_ptr = dma_buffer_phys;
+
+ /* Next data chunk */
+ dma_buffer_phys += prtd->dma_period;
+ }
+
+ return 0;
+}
+
+/*
+ * Stop circular DMA descriptor list
+ * We should not stop DMA in a middle of current transaction once we receive
+ * stop request from ALSA core. This function finds the next DMA descriptor
+ * and set it up to decrement DMA channel semaphore. So the current transaction
+ * is the last data transfer.
+ */
+static void stmp3xxx_pcm_stop(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ dma_addr_t pos;
+ int desc;
+
+ /* Freez DMA channel for a moment */
+ stmp3xxx_dma_freeze(prtd->dma_ch);
+
+ /* Find current DMA descriptor */
+ pos = __raw_readl(REGS_APBX_BASE +
+ HW_APBX_CHn_BAR(prtd->params->dma_ch));
+ desc = (pos - runtime->dma_addr) / prtd->dma_period;
+
+ /* Set up the next descriptor to decrement DMA channel sempahore */
+ prtd->dma_desc_array[desc].next_descr->command->cmd
+ = BM_APBX_CHn_CMD_SEMAPHORE;
+
+ /* Let the current DMA transaction finish */
+ stmp3xxx_dma_unfreeze(prtd->dma_ch);
+}
+
+static int stmp3xxx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+
+ case SNDRV_PCM_TRIGGER_START:
+ stmp3xxx_dma_go(prtd->dma_ch, prtd->dma_desc_array, 1);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ stmp3xxx_pcm_stop(substream);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stmp3xxx_dma_unfreeze(prtd->dma_ch);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stmp3xxx_dma_freeze(prtd->dma_ch);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+stmp3xxx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ unsigned int offset;
+ dma_addr_t pos;
+
+ pos = __raw_readl(REGS_APBX_BASE +
+ HW_APBX_CHn_BAR(prtd->params->dma_ch));
+ offset = bytes_to_frames(runtime, pos - runtime->dma_addr);
+
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int stmp3xxx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data;
+
+ prtd->dma_period = params_period_bytes(hw_params);
+ prtd->dma_totsize = params_buffer_bytes(hw_params);
+
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+static int stmp3xxx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int stmp3xxx_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ struct stmp3xxx_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
+ int desc_num = stmp3xxx_pcm_hardware.periods_max;
+ int desc;
+ int ret;
+
+ if (!dma_data)
+ return -ENODEV;
+
+ prtd->params = dma_data;
+ prtd->dma_ch = STMP3XXX_DMA(dma_data->dma_ch, dma_data->dma_bus);
+
+ ret = stmp3xxx_dma_request(prtd->dma_ch, stmp3xxx_pcm_dev,
+ prtd->params->name);
+ if (ret) {
+ printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n",
+ __func__, dma_data->dma_bus, dma_data->dma_ch);
+ return ret;
+ }
+
+ /* Allocate memory for data and pio DMA descriptors */
+ prtd->dma_desc_array =
+ kzalloc(sizeof(struct stmp3xxx_dma_descriptor) * desc_num,
+ GFP_KERNEL);
+ if (prtd->dma_desc_array == NULL) {
+ printk(KERN_ERR "%s: Unable to allocate memory\n", __func__);
+ stmp3xxx_dma_release(prtd->dma_ch);
+ return -ENOMEM;
+ }
+
+ for (desc = 0; desc < desc_num; desc++) {
+ ret = stmp3xxx_dma_allocate_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ if (ret) {
+ printk(KERN_ERR"%s Unable to allocate DMA command %d\n",
+ __func__, desc);
+ goto err;
+ }
+ }
+
+ ret = request_irq(prtd->params->irq, stmp3xxx_pcm_dma_irq, 0,
+ "STMP3xxx PCM DMA", substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__,
+ prtd->params->irq);
+ goto err;
+ }
+
+
+ /* Enable completion interrupt */
+ stmp3xxx_dma_clear_interrupt(prtd->dma_ch);
+ stmp3xxx_dma_enable_interrupt(prtd->dma_ch);
+
+ return 0;
+
+err:
+ while (--desc >= 0)
+ stmp3xxx_dma_free_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ kfree(prtd->dma_desc_array);
+ stmp3xxx_dma_release(prtd->dma_ch);
+
+ return ret;
+}
+
+static int stmp3xxx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd;
+ int ret;
+
+ /* Ensure that buffer size is a multiple of the period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &stmp3xxx_pcm_hardware);
+
+ prtd = kzalloc(sizeof(struct stmp3xxx_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+
+ ret = stmp3xxx_pcm_dma_request(substream);
+ if (ret) {
+ printk(KERN_ERR "stmp3xxx_pcm: Failed to request channels\n");
+ kfree(prtd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int stmp3xxx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct stmp3xxx_runtime_data *prtd = runtime->private_data;
+ int desc_num = stmp3xxx_pcm_hardware.periods_max;
+ int desc;
+
+ /* Free DMA irq */
+ free_irq(prtd->params->irq, substream);
+
+ /* Free DMA channel */
+ for (desc = 0; desc < desc_num; desc++)
+ stmp3xxx_dma_free_command(prtd->dma_ch,
+ &prtd->dma_desc_array[desc]);
+ kfree(prtd->dma_desc_array);
+ stmp3xxx_dma_release(prtd->dma_ch);
+
+ /* Free private runtime data */
+ kfree(prtd);
+
+ return 0;
+}
+
+static int stmp3xxx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_coherent(NULL, vma, runtime->dma_area,
+ runtime->dma_addr, runtime->dma_bytes);
+}
+
+struct snd_pcm_ops stmp3xxx_pcm_ops = {
+ .open = stmp3xxx_pcm_open,
+ .close = stmp3xxx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = stmp3xxx_pcm_hw_params,
+ .hw_free = stmp3xxx_pcm_hw_free,
+ .prepare = stmp3xxx_pcm_prepare,
+ .trigger = stmp3xxx_pcm_trigger,
+ .pointer = stmp3xxx_pcm_pointer,
+ .mmap = stmp3xxx_pcm_mmap,
+};
+
+static u64 stmp3xxx_pcm_dma_mask = DMA_BIT_MASK(32);
+
+static int stmp3xxx_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ size_t size = stmp3xxx_pcm_hardware.buffer_bytes_max;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &stmp3xxx_pcm_dma_mask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL,
+ size, size);
+
+ return 0;
+}
+
+static void stmp3xxx_pcm_free(struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+/*
+ * We need probe/remove callbacks to setup stmp3xxx_pcm_dev
+ */
+static int stmp3xxx_pcm_probe(struct platform_device *pdev)
+{
+ stmp3xxx_pcm_dev = &pdev->dev;
+ return 0;
+}
+
+static int stmp3xxx_pcm_remove(struct platform_device *pdev)
+{
+ stmp3xxx_pcm_dev = NULL;
+ return 0;
+}
+
+struct snd_soc_platform stmp3xxx_soc_platform = {
+ .name = "STMP3xxx Audio",
+ .pcm_ops = &stmp3xxx_pcm_ops,
+ .probe = stmp3xxx_pcm_probe,
+ .remove = stmp3xxx_pcm_remove,
+ .pcm_new = stmp3xxx_pcm_new,
+ .pcm_free = stmp3xxx_pcm_free,
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_soc_platform);
+
+static int __init stmp3xxx_pcm_init(void)
+{
+ return snd_soc_register_platform(&stmp3xxx_soc_platform);
+}
+
+static void __exit stmp3xxx_pcm_exit(void)
+{
+ snd_soc_unregister_platform(&stmp3xxx_soc_platform);
+}
+module_init(stmp3xxx_pcm_init);
+module_exit(stmp3xxx_pcm_exit);
+
+MODULE_AUTHOR("Vladislav Buzov");
+MODULE_DESCRIPTION("STMP3xxx DMA Module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.h b/sound/soc/stmp3xxx/stmp3xxx_pcm.h
new file mode 100644
index 000000000000..78ac9487353f
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.h
@@ -0,0 +1,30 @@
+/*
+ * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC
+ *
+ * Author: Vladislav Buzov <vbuzov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_PCM_H
+#define _STMP3XXX_PCM_H
+
+struct stmp3xxx_pcm_dma_params {
+ char *name;
+ int dma_bus; /* DMA bus */
+ int dma_ch; /* DMA channel number */
+ int irq; /* DMA interrupt number */
+};
+
+extern struct snd_soc_platform stmp3xxx_soc_platform;
+
+#endif
diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c
new file mode 100644
index 000000000000..504192619c43
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c
@@ -0,0 +1,207 @@
+/*
+ * ALSA SoC SPDIF Audio Layer for STMP3xxx processor familiy
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ * Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/regs-spdif.h>
+#include "stmp3xxx_pcm.h"
+#include <mach/platform.h>
+
+#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct stmp3xxx_pcm_dma_params stmp3xxx_spdif = {
+ .name = "stmp3xxx spdif",
+ .dma_bus = STMP3XXX_BUS_APBX,
+ .dma_ch = 2,
+ .irq = IRQ_SPDIF_DMA,
+};
+
+static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ u32 ctrl_reg = 0;
+ u32 overflow_mask;
+ u32 underflow_mask;
+
+ if (playback) {
+ ctrl_reg = __raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL);
+ underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ;
+ overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ;
+ }
+
+ if (ctrl_reg & underflow_mask) {
+ printk(KERN_DEBUG "underflow detected SPDIF\n");
+
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ } else if (ctrl_reg & overflow_mask) {
+ printk(KERN_DEBUG "overflow detected SPDIF\n");
+
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ } else
+ printk(KERN_WARNING "Unknown SPDIF error interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+static int stmp3xxx_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_RUN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (playback)
+ __raw_writel(BM_SPDIF_CTRL_RUN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int stmp3xxx_spdif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+ int irq;
+ int ret;
+
+ if (playback) {
+ irq = IRQ_SPDIF_ERROR;
+ cpu_dai->dma_data = &stmp3xxx_spdif;
+ }
+
+ ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx SPDIF Error",
+ substream);
+ if (ret) {
+ printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n",
+ __func__, IRQ_SPDIF_ERROR);
+ return ret;
+ }
+
+ /* Enable error interrupt */
+ if (playback) {
+ __raw_writel(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ __raw_writel(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET);
+ }
+
+ return 0;
+}
+
+static void stmp3xxx_spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
+
+ /* Disable error interrupt */
+ if (playback) {
+ __raw_writel(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN,
+ REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR);
+ free_irq(IRQ_SPDIF_ERROR, substream);
+ }
+}
+
+#ifdef CONFIG_PM
+static int stmp3xxx_spdif_suspend(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+
+static int stmp3xxx_spdif_resume(struct snd_soc_dai *cpu_dai)
+{
+ return 0;
+}
+#else
+#define stmp3xxx_spdif_suspend NULL
+#define stmp3xxx_spdif_resume NULL
+#endif /* CONFIG_PM */
+
+struct snd_soc_dai_ops stmp3xxx_spdif_dai_ops = {
+ .startup = stmp3xxx_spdif_startup,
+ .shutdown = stmp3xxx_spdif_shutdown,
+ .trigger = stmp3xxx_spdif_trigger,
+};
+
+struct snd_soc_dai stmp3xxx_spdif_dai = {
+ .name = "stmp3xxx spdif",
+ .id = 0,
+ .suspend = stmp3xxx_spdif_suspend,
+ .resume = stmp3xxx_spdif_resume,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = STMP3XXX_SPDIF_RATES,
+ .formats = STMP3XXX_SPDIF_FORMATS,
+ },
+ .ops = &stmp3xxx_spdif_dai_ops,
+};
+EXPORT_SYMBOL_GPL(stmp3xxx_spdif_dai);
+
+static int __init stmp3xxx_spdif_dai_init(void)
+{
+ return snd_soc_register_dai(&stmp3xxx_spdif_dai);
+}
+
+static void __exit stmp3xxx_spdif_dai_exit(void)
+{
+ snd_soc_unregister_dai(&stmp3xxx_spdif_dai);
+}
+module_init(stmp3xxx_spdif_dai_init);
+module_exit(stmp3xxx_spdif_dai_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("stmp3xxx SPDIF DAI");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h
new file mode 100644
index 000000000000..bec3d6fad7c9
--- /dev/null
+++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h
@@ -0,0 +1,21 @@
+/*
+ * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter
+ *
+ * Author: Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#ifndef _STMP3XXX_SPDIF_H
+#define _STMP3XXX_SPDIF_H
+extern struct snd_soc_dai stmp3xxx_spdif_dai;
+#endif