summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorRob Herring <r.herring@freescale.com>2009-01-25 19:43:26 -0500
committerRob Herring <r.herring@freescale.com>2009-02-17 11:32:54 -0600
commitaa2643ed8f7c03f8dc68b7f6f5b290f490385a43 (patch)
treede6fe3e0850e5195b3f33e694c6345faabd1754f /sound
parent484ecd8c05117a795d856e57e45be48ecea07eae (diff)
ENGR00107731-2: Port imx 3.3.0 release to 2.6.28
Port rel_imx_2.6.26_3.3.0 to 2.6.28. PMIC Regulator and ASoC drivers are removed and not yet ported. Updated asm/arch headers for move to plat-mxc/include/mach device_create parameters changed. sysdev attribute functions changed. Adopt mainline MX3 timer code and update clock init flow. Signed-off-by: Rob Herring <r.herring@freescale.com>
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.c3793
-rw-r--r--sound/arm/mxc-alsa-pmic.h110
-rw-r--r--sound/arm/mxc-alsa-spdif.c2264
7 files changed, 6705 insertions, 0 deletions
diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig
index f8e6de48d816..25b346110e49 100644
--- a/sound/arm/Kconfig
+++ b/sound/arm/Kconfig
@@ -50,5 +50,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 2054de11de8a..307e2db108db 100644
--- a/sound/arm/Makefile
+++ b/sound/arm/Makefile
@@ -17,3 +17,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$(TOPDIR)/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..07f9f0fe581f
--- /dev/null
+++ b/sound/arm/mxc-alsa-pmic.c
@@ -0,0 +1,3793 @@
+/*
+ * 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>
+
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif /* CONFIG_PM */
+
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+
+#include <ssi/ssi.h>
+#include <ssi/registers.h>
+#include <dam/dam.h>
+#include <mach/pmic_external.h>
+#include <mach/pmic_audio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include "mxc-alsa-pmic.h"
+#include "mxc-alsa-common.h"
+#include <linux/fs.h>
+#include <linux/clk.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 */
+ card = snd_card_new(-1, id, THIS_MODULE, sizeof(mxc_pmic_audio_t));
+ if (card == NULL) {
+ 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..b322cbc2f40b
--- /dev/null
+++ b/sound/arm/mxc-alsa-spdif.c
@@ -0,0 +1,2264 @@
+/*
+ * Copyright 2007-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-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/ioctl.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/soundcard.h>
+#include <linux/clk.h>
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif
+
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/control.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);
+
+ 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_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;
+ 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 */
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE,
+ sizeof(struct mxc_spdif_device));
+ if (card == NULL)
+ return -ENOMEM;
+ 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 */
+ if (request_irq(MXC_INT_SPDIF, 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(MXC_INT_SPDIF, 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");