diff options
author | Rob Herring <r.herring@freescale.com> | 2009-10-19 14:43:19 -0500 |
---|---|---|
committer | Alejandro Gonzalez <alex.gonzalez@digi.com> | 2010-02-12 17:19:16 +0100 |
commit | cdde68e3a7d4cbf4701005ab6032366e76009419 (patch) | |
tree | 5690552665f0b7843e6552e4d5fe7b63cbc78f51 /sound/arm | |
parent | 57d1417ea543b83760b3fd76a46b9d29deb2e444 (diff) |
ENGR00117389 Port 5.0.0 release to 2.6.31
This is i.MX BSP 5.0.0 release ported to 2.6.31
Signed-off-by: Rob Herring <r.herring@freescale.com>
Signed-off-by: Alan Tull <r80115@freescale.com>
Signed-off-by: Xinyu Chen <xinyu.chen@freescale.com>
Diffstat (limited to 'sound/arm')
-rw-r--r-- | sound/arm/Kconfig | 49 | ||||
-rw-r--r-- | sound/arm/Makefile | 11 | ||||
-rw-r--r-- | sound/arm/mxc-alsa-common.h | 68 | ||||
-rw-r--r-- | sound/arm/mxc-alsa-mixer.c | 410 | ||||
-rw-r--r-- | sound/arm/mxc-alsa-pmic.c | 3790 | ||||
-rw-r--r-- | sound/arm/mxc-alsa-pmic.h | 110 | ||||
-rw-r--r-- | sound/arm/mxc-alsa-spdif.c | 2266 |
7 files changed, 6704 insertions, 0 deletions
diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig index 885683a3b0bd..ae88981852b0 100644 --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@ -39,5 +39,54 @@ config SND_PXA2XX_AC97 Say Y or M if you want to support any AC97 codec attached to the PXA2xx AC97 interface. +config SND_MXC_SPDIF + tristate "MXC SPDIF sound card spport" + select SND_PCM + help + Say Y here to enable SPDIF sound card + +config SND_MXC_PMIC + tristate "MXC PMIC sound system" + depends on ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO || MXC_PMIC_SC55112_AUDIO) + default y + select SND_PCM + help + Say Y here to include support for soundcards based on the + MC13783 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-mc13783. + +config SND_MXC_PLAYBACK_MIXING + bool "Playback Stream Mixing" + depends on (!ARCH_MX27) && (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO) + default n + select SND_PCM + help + Say Y here to include support mixing for soundcards based on the + MC13783 chip. This supports audio stream mixing on VCODEC for mc13783 based platforms. + Analog mixng as well as Digital mixing can be tested on these platforms. + As of now , mixing of mono files only are supported in Digital Mixing since it is done on VCODEC. + SSI 2 channel mode is used to mix 2 streams on a single SSI. This is supported on all platforms except imx27ads(imx27ads - Analog mixing only). + +config HEADSET_DETECT_ENABLE + bool "Headset Detect Enable" + depends on (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO) + default n + select SND_PCM + help + Say Y here to enable Headset Detect Feature. + +config SND_MXC_PMIC_IRAM + bool "MXC PMIC sound system supports IRAM" + depends on SND_MXC_PMIC && SDMA_IRAM + default n + select SND_PCM + help + It will use IRAM as the DMA buffer of ALSA playback. + endif # SND_ARM diff --git a/sound/arm/Makefile b/sound/arm/Makefile index 5a549ed6c8aa..58569cc2db11 100644 --- a/sound/arm/Makefile +++ b/sound/arm/Makefile @@ -14,3 +14,14 @@ snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o snd-pxa2xx-ac97-objs := pxa2xx-ac97.o + +# +# Define the header file locations for PMIC drivers. +# +CFLAGS_mxc-alsa-pmic.o = -I$(srctree)/drivers/mxc +obj-$(CONFIG_SND_MXC_PMIC) += snd-mxc-alsa.o +snd-mxc-alsa-objs := mxc-alsa-pmic.o mxc-alsa-mixer.o + +CFLGS_mxc_alsa_spdif.o = -I$(TOPDIR)/drivers/mxc +obj-$(CONFIG_SND_MXC_SPDIF) += snd-spdif.o +snd-spdif-objs := mxc-alsa-spdif.o diff --git a/sound/arm/mxc-alsa-common.h b/sound/arm/mxc-alsa-common.h new file mode 100644 index 000000000000..863c0432510b --- /dev/null +++ b/sound/arm/mxc-alsa-common.h @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mxc-alsa-common.h + * @brief + * @ingroup SOUND_DRV + */ + +#ifndef __MXC_ALSA_COMMON_H__ +#define __MXC_ALSA_COMMON_H__ + +/* Enums typically used by the Mixer support APIs + * Emunerates IP, OP and mixer sources. + */ + +typedef enum { + CODEC_DIR_OUT, + MIXER_OUT +} OUTPUT_SOURCE; + +typedef enum { + OP_NODEV = -1, + OP_EARPIECE, + OP_HANDSFREE, + OP_HEADSET, + OP_LINEOUT, + OP_MAXDEV, + OP_MONO +} OUTPUT_DEVICES; + +typedef enum { + IP_NODEV = -1, + IP_HANDSET, + IP_HEADSET, + IP_LINEIN, + IP_MAXDEV +} INPUT_DEVICES; + +extern int mxc_alsa_create_ctl(struct snd_card *card, void *p_value); + +extern int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src, + OUTPUT_DEVICES dev, bool enable); +extern int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume, + OUTPUT_DEVICES dev); +extern int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev, + bool enable); +extern int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode); +extern int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val); +extern int set_mixer_output_balance(int bal); + +extern int get_mixer_output_device(void); +extern int get_mixer_output_volume(void); +extern int get_mixer_output_mono_adder(void); +extern int get_mixer_output_balance(void); +extern int get_mixer_input_gain(void); +extern int get_mixer_input_device(void); +#endif /* __MXC_ALSA_COMMON_H__ */ diff --git a/sound/arm/mxc-alsa-mixer.c b/sound/arm/mxc-alsa-mixer.c new file mode 100644 index 000000000000..c77120cf48eb --- /dev/null +++ b/sound/arm/mxc-alsa-mixer.c @@ -0,0 +1,410 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mxc-alsa-mixer.c + * @brief this file implements the mxc sound driver mixer interface for ALSA. + * The mxc sound driver supports mono/stereo recording (there are + * some limitations due to hardware), mono/stereo playback and + * audio mixing. This file implements output switching, volume/balance controls + * mono adder config, I/P dev switching and gain on the PCM streams. + * Recording supports 8000 khz and 16000 khz sample rate. + * Playback supports 8000, 11025, 16000, 22050, 24000, 32000, + * 44100 and 48000 khz for mono and stereo. + * + * @ingroup SOUND_DRV + */ + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <linux/soundcard.h> +#include <mach/pmic_audio.h> +#include "mxc-alsa-common.h" +/*! + * These are the functions implemented in the ALSA PCM driver that + * are used for mixer operations + * + */ + +/*! + * These are the callback functions for mixer controls + * + */ +/* Output device control*/ +static int pmic_mixer_output_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 15; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_mixer_output_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int dev, i; + dev = uvalue->value.integer.value[0]; + for (i = OP_EARPIECE; i < OP_MAXDEV; i++) { + if (dev & (1 << i)) { + set_mixer_output_device(NULL, MIXER_OUT, i, 1); + } else { + set_mixer_output_device(NULL, MIXER_OUT, i, 0); + } + } + return 0; +} +static int pmic_mixer_output_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val, ret = 0, i = 0; + for (i = OP_EARPIECE; i < OP_MAXDEV; i++) { + val = get_mixer_output_device(); + if (val & SOUND_MASK_PHONEOUT) + ret = ret | 1; + if (val & SOUND_MASK_SPEAKER) + ret = ret | 2; + if (val & SOUND_MASK_VOLUME) + ret = ret | 4; + if (val & SOUND_MASK_PCM) + ret = ret | 8; + uvalue->value.integer.value[0] = ret; + } + return 0; + +} + +/* Input gain control*/ +static int pmic_cap_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_cap_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val; + val = get_mixer_input_gain(); + val = val & 0xFF; + uvalue->value.integer.value[0] = val; + return 0; +} + +static int pmic_cap_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + + int vol; + vol = uvalue->value.integer.value[0]; + vol = vol | (vol << 8); + set_mixer_input_gain(NULL, vol); + return 0; +} + +/* Mono adder control*/ +static int pmic_pb_monoconfig_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_pb_monoconfig_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int mono; + mono = uvalue->value.integer.value[0]; + set_mixer_output_mono_adder(mono); + return 0; +} +static int pmic_pb_monoconfig_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = get_mixer_output_mono_adder(); + return 0; +} + +/*! + * These are the ALSA control structures with init values + * + */ + +/* Input device control*/ +static int pmic_cap_input_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 7; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_cap_input_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int dev, i; + dev = uvalue->value.integer.value[0]; + for (i = IP_HANDSET; i < IP_MAXDEV; i++) { + if (dev & (1 << i)) { + set_mixer_input_device(NULL, i, 1); + } else { + set_mixer_input_device(NULL, i, 0); + } + } + return 0; +} +static int pmic_cap_input_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val, ret = 0, i = 0; + for (i = IP_HANDSET; i < IP_MAXDEV; i++) { + val = get_mixer_input_device(); + if (val & SOUND_MASK_PHONEIN) + ret = ret | 1; + if (val & SOUND_MASK_MIC) + ret = ret | 2; + if (val & SOUND_MASK_LINE) + ret = ret | 4; + uvalue->value.integer.value[0] = ret; + } + return 0; +} + +/* Volume control*/ +static int pmic_pb_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int volume; + volume = uvalue->value.integer.value[0]; + volume = volume | (volume << 8); + set_mixer_output_volume(NULL, volume, OP_NODEV); + return 0; +} +static int pmic_pb_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + return 0; +} + +static int pmic_pb_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val; + val = get_mixer_output_volume(); + val = val & 0xFF; + uvalue->value.integer.value[0] = val; + return 0; +} + +/* Balance control start */ +static int pmic_pb_balance_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + return 0; +} + +static int pmic_pb_balance_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = get_mixer_output_balance(); + return 0; + +} +static int pmic_pb_balance_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int bal; + bal = uvalue->value.integer.value[0]; + set_mixer_output_balance(bal); + return 0; +} + +/* Balance control end */ + +/* loopback control start */ +static int pmic_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pmic_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = kcontrol->private_value; + return 0; + +} +static int pmic_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int changed; + long flag = uvalue->value.integer.value[0]; + changed = + (uvalue->value.integer.value[0] == kcontrol->private_value) ? 0 : 1; + kcontrol->private_value = uvalue->value.integer.value[0]; + if (flag) + pmic_audio_fm_output_enable(true); + else + pmic_audio_fm_output_enable(false); + + return changed; +} + +/* Loopback control end */ + +/* Kcontrol structure definitions */ +struct snd_kcontrol_new pmic_control_pb_vol __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .index = 0x00, + .info = pmic_pb_volume_info, + .get = pmic_pb_volume_get, + .put = pmic_pb_volume_put, + .private_value = 0xffab1, +}; + +struct snd_kcontrol_new pmic_control_pb_bal __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Balance Playback Volume", + .index = 0x00, + .info = pmic_pb_balance_info, + .get = pmic_pb_balance_get, + .put = pmic_pb_balance_put, + .private_value = 0xffab2, +}; +struct snd_kcontrol_new pmic_control_pb_monoconfig __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Monoconfig Playback Volume", + .index = 0x00, + .info = pmic_pb_monoconfig_info, + .get = pmic_pb_monoconfig_get, + .put = pmic_pb_monoconfig_put, + .private_value = 0xffab2, +}; +struct snd_kcontrol_new pmic_control_op_sw __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Output Playback Volume", + .index = 0x00, + .info = pmic_mixer_output_info, + .get = pmic_mixer_output_get, + .put = pmic_mixer_output_put, + .private_value = 0xffab4, +}; + +struct snd_kcontrol_new pmic_control_cap_vol __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Capture Volume", + .index = 0x00, + .info = pmic_cap_volume_info, + .get = pmic_cap_volume_get, + .put = pmic_cap_volume_put, + .private_value = 0xffab5, +}; +struct snd_kcontrol_new pmic_control_ip_sw __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Input Capture Volume", + .index = 0x00, + .info = pmic_cap_input_info, + .get = pmic_cap_input_get, + .put = pmic_cap_input_put, + .private_value = 0xffab5, +}; + +struct snd_kcontrol_new pmic_control_loop_out __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Line-in", + .index = 0x00, + .info = pmic_loopback_info, + .get = pmic_loopback_get, + .put = pmic_loopback_put, + .private_value = 0, +}; + +/*! + * This function registers the control components of ALSA Mixer + * It is called by ALSA PCM init. + * + * @param card pointer to the ALSA sound card structure. + * + * @return 0 on success, -ve otherwise. + */ +int __devinit mxc_alsa_create_ctl(struct snd_card *card, void *p_value) +{ + int err = 0; + + if ((err = + snd_ctl_add(card, snd_ctl_new1(&pmic_control_op_sw, p_value))) < 0) + return err; + + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_vol, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_monoconfig, + p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_bal, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_cap_vol, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, snd_ctl_new1(&pmic_control_ip_sw, p_value))) < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1(&pmic_control_loop_out, p_value)); + if (err < 0) + return err; + + return 0; +} + +EXPORT_SYMBOL(mxc_alsa_create_ctl); diff --git a/sound/arm/mxc-alsa-pmic.c b/sound/arm/mxc-alsa-pmic.c new file mode 100644 index 000000000000..2d24f458925a --- /dev/null +++ b/sound/arm/mxc-alsa-pmic.c @@ -0,0 +1,3790 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup SOUND_DRV MXC Sound Driver for ALSA + */ + + /*! + * @file mxc-alsa-pmic.c + * @brief this fle mxc-alsa-pmic.c + * @brief this file implements the mxc sound driver interface for ALSA. + * The mxc sound driver supports mono/stereo recording (there are + * some limitations due to hardware), mono/stereo playback and + * audio mixing. + * Recording supports 8000 khz and 16000 khz sample rate. + * Playback supports 8000, 11025, 16000, 22050, 24000, 32000, + * 44100, 48000 and 96000 Hz for mono and stereo. + * This file also handles the software mixer and abstraction APIs + * that control the volume,balance,mono-adder,input and output + * devices for PMIC. + * These mixer controls shall be accessible thru alsa as well as + * OSS emulation modes + * + * @ingroup SOUND_DRV + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/soundcard.h> +#include <linux/pmic_external.h> +#include <linux/pm.h> +#include <linux/fs.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> + +#include <mach/pmic_audio.h> +#include <mach/dma.h> +#include <asm/mach-types.h> + +#include <ssi/ssi.h> +#include <ssi/registers.h> +#include <dam/dam.h> + +#include "mxc-alsa-pmic.h" +#include "mxc-alsa-common.h" + +/* + * PMIC driver buffer policy. + * Customize here if the sound is not correct + */ +#define MAX_BUFFER_SIZE (32*1024) +#define DMA_BUF_SIZE (8*1024) +#define MIN_PERIOD_SIZE 64 +#define MIN_PERIOD 2 +#define MAX_PERIOD 255 + +#define AUD_MUX_CONF 0x0031010 +#define MASK_2_TS 0xfffffffc +#define MASK_1_TS 0xfffffffd +#define MASK_1_TS_MIX 0xfffffffc +#define MASK_1_TS_STDAC 0xfffffffe +#define MASK_1_TS_REC 0xfffffffe +#define SOUND_CARD_NAME "MXC" + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +#define MAX_IRAM_SIZE (IRAM_SIZE - CONFIG_SDMA_IRAM_SIZE) +#define DMA_IRAM_SIZE (4*1024) +#define ADMA_BASE_PADDR (IRAM_BASE_ADDR + CONFIG_SDMA_IRAM_SIZE) +#define ADMA_BASE_VADDR (IRAM_BASE_ADDR_VIRT + CONFIG_SDMA_IRAM_SIZE) + +#if (MAX_IRAM_SIZE + CONFIG_SDMA_IRAM_SIZE) > IRAM_SIZE +#error "The IRAM size required has beyond the limitation of IC spec" +#endif + +#if (MAX_IRAM_SIZE&(DMA_IRAM_SIZE-1)) +#error "The IRAM size for DMA ring buffer should be multiples of dma buffer size" +#endif + +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * These defines enable DMA chaining for playback + * and capture respectively. + */ +#define MXC_SOUND_PLAYBACK_CHAIN_DMA_EN 1 +#define MXC_SOUND_CAPTURE_CHAIN_DMA_EN 1 + +/*! + * ID for this card + */ +static char *id = NULL; + +#define MXC_ALSA_MAX_PCM_DEV 3 +#define MXC_ALSA_MAX_PLAYBACK 3 +#define MXC_ALSA_MAX_CAPTURE 1 + +struct mxc_audio_platform_data *audio_data; +/*! + * This structure is the global configuration of the soundcard + * that are accessed by the mixer as well as by the playback/recording + * stream. This contains various volume, balance, mono adder settings + * + */ +typedef struct audio_mixer_control { + + /*! + * This variable holds the current active output device(s) + */ + int output_device; + + /*! + * This variable holds the current active input device. + */ + int input_device; + + /* Used only for playback/recording on codec .. Use 1 for playback + * and 0 for recording*/ + int direction; + + /*! + * This variable holds the current source for active ouput device(s) + */ + OUTPUT_SOURCE source_for_output[OP_MAXDEV]; + + /*! + * This variable says if a given output device is part of an ongoing + * playback. This variable will be set and reset by the playback stream + * when stream is activated and when stream is closed. This shall also + * be set and reset my mixer functions for enabling/disabling output devs + */ + int output_active[OP_MAXDEV]; + + /*! + * This variable holds the current volume for active input device. + * This maps to the input gain of recording device + */ + int input_volume; + + /*! + * This variable holds the current volume for playback devices. + */ + //int output_volume[OP_MAXDEV]; + int master_volume_out; + + /*! + * This variable holds the balance setting for the mixer out. + * The range is 0 to 100. 50 means both L and R equal. + * < 50 attenuates left side and > 50 attenualtes right side + */ + int mixer_balance; + + /*! + * This variable holds the current mono adder config. + */ + PMIC_AUDIO_MONO_ADDER_MODE mixer_mono_adder; + + /*! + * Semaphore used to control the access to this structure. + */ + struct semaphore sem; + + /*! + * These variables are set by PCM stream and mixer when the voice codec's / ST dac's outputs are + * connected to the analog mixer of PMIC audio chip + */ + int codec_out_to_mixer; + int stdac_out_to_mixer; + + int codec_playback_active; + int codec_capture_active; + int stdac_playback_active; + int mixing_active; + + /*! + * This variable holds the configuration of the headset which was previously enabled. + */ + int old_prof; + + PMIC_AUDIO_HANDLE stdac_handle; + PMIC_AUDIO_HANDLE voice_codec_handle; + +} audio_mixer_control_t; + +/*! + * This structure stores current state of audio configuration + * soundcard wrt a specific stream (playback on different DACs, recording on the codec etc). + * It is used to set/get current values and are NOT accessed by the Mixer. This structure shall + * be retrieved thru pcm substream pointer and hence the mixer component will have no access + * to it. There will be as many structures as the number of streams. In our case it's 3. Codec playback + * STDAC playback and voice codec recording. + * This structure will be used at the beginning of activating a stream to configure audio chip. + * + */ +typedef struct pmic_audio_device { + + PMIC_AUDIO_HANDLE handle; + /*! + * This variable holds the sample rate currently being used. + */ + int sample_rate; + + /*! + * This variable holds the current protocol PMIC is using. + * PMIC can use one of three protocols at any given time: + * normal, network and I2S. + */ + int protocol; + + /*! + * This variables tells us whether PMIC runs in + * master mode (PMIC generates audio clocks)or slave mode (AP side + * generates audio clocks) + * + * Currently the default mode is master mode because PMIC clocks have + * higher precision. + */ + int mode; + + /* This variable holds the value representing the + * base clock PMIC will use to generate internal + * clocks (BCL clock and FrameSync clock) + */ + int pll; + + /*! + * This variable holds the SSI to which PMIC is currently connected. + */ + int ssi; + + /*! + * This variable tell us whether bit clock is inverted or not. + */ + int bcl_inverted; + + /*! + * This variable tell us whether frame clock is inverted or not. + */ + int fs_inverted; + + /*! + * This variable holds the pll used for PMIC audio operations. + */ + int pll_rate; + + /*! + * This variable holds the filter that PMIC is applying to + * CODEC operations. + */ + int codec_filter; + +} pmic_audio_device_t; + +/*! + * This structure represents an audio stream in term of + * channel DMA, HW configuration on PMIC and on AudioMux/SSI + */ +typedef struct audio_stream { + /*! + * identification string + */ + char *id; + + /*! + * numeric identification + */ + int stream_id; + + /*! + * SSI ID on the ARM side + */ + int ssi; + + /*! + * DAM port on the ARM side + */ + int dam_port; + + /*! + * device identifier for DMA + */ + int dma_wchannel; + + /*! + * we are using this stream for transfer now + */ + int active:1; + + /*! + * current transfer period + */ + int period; + + /*! + * current count of transfered periods + */ + int periods; + + /*! + * are we recording - flag used to do DMA trans. for sync + */ + int tx_spin; + + /*! + * Previous offset value for resume + */ + unsigned int old_offset; +#if 0 + /*! + * Path for this stream + */ + device_data_t stream_device; +#endif + + /*! + * pmic audio chip stream specific configuration + */ + pmic_audio_device_t pmic_audio_device; + + /*! + * for locking in DMA operations + */ + spinlock_t dma_lock; + + /*! + * Alsa substream pointer + */ + struct snd_pcm_substream *stream; +} audio_stream_t; + +/*! + * This structure represents the PMIC sound card with its + * 2 streams (StDac and Codecs) and its shared parameters + */ +typedef struct snd_card_mxc_pmic_audio { + /*! + * ALSA sound card handle + */ + struct snd_card *card; + + /*! + * ALSA pcm driver type handle + */ + struct snd_pcm *pcm[MXC_ALSA_MAX_PCM_DEV]; + + /*! + * playback & capture streams handle + * We can support a maximum of two playback streams (voice-codec + * and ST-DAC) and 1 recording stream + */ + audio_stream_t s[MXC_ALSA_MAX_CAPTURE + MXC_ALSA_MAX_PLAYBACK]; + +} mxc_pmic_audio_t; + +/*! + * pmic audio chip parameters for IP/OP and volume controls + */ +audio_mixer_control_t audio_mixer_control; + +/*! + * Global variable that represents the PMIC soundcard + * with its 2 availables stream devices: stdac and codec + */ +mxc_pmic_audio_t *mxc_audio = NULL; + +/*! + * Supported playback rates array + */ +static unsigned int playback_rates_stereo[] = { + 8000, + 11025, + 12000, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000, + 64000, + 96000, +}; + +static unsigned int playback_rates_mono[] = { + 8000, + 16000, +}; + +/*! + * Supported capture rates array + */ +static unsigned int capture_rates[] = { + 8000, + 16000, +}; + +/*! + * this structure represents the sample rates supported + * by PMIC for playback operations on StDac. + */ +static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = { + .count = ARRAY_SIZE(playback_rates_stereo), + .list = playback_rates_stereo, + .mask = 0, +}; + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +static spinlock_t g_audio_iram_lock = SPIN_LOCK_UNLOCKED; +static int g_audio_iram_en = 1; +static int g_device_opened = 0; +extern void flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); + +static inline int mxc_snd_enable_iram(int enable) +{ + int ret = -EBUSY; + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + if (!g_device_opened) { + g_audio_iram_en = (enable != 0); + ret = 0; + } + spin_unlock_irqrestore(&g_audio_iram_lock, flags); + return ret; +} + +static inline void mxc_snd_pcm_iram_get(void) +{ + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + g_audio_iram_en++; + spin_unlock_irqrestore(&g_audio_iram_lock, flags); +} + +static inline void mxc_snd_pcm_iram_put(void) +{ + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + g_audio_iram_en--; + spin_unlock_irqrestore(&g_audio_iram_lock, flags); +} + +struct snd_dma_buffer g_iram_dmab; + +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * this structure represents the sample rates supported + * by PMIC for playback operations on Voice codec. + */ +static struct snd_pcm_hw_constraint_list hw_playback_rates_mono = { + .count = ARRAY_SIZE(playback_rates_mono), + .list = playback_rates_mono, + .mask = 0, +}; + +/*! + * this structure represents the sample rates supported + * by PMIC for capture operations on Codec. + */ +static struct snd_pcm_hw_constraint_list hw_capture_rates = { + .count = ARRAY_SIZE(capture_rates), + .list = capture_rates, + .mask = 0, +}; + +#ifdef CONFIG_HEADSET_DETECT_ENABLE +static PMIC_HS_STATE hs_state; + +/*! + *This is used to maintain the state of the Headset*/ +static int headset_state = 0; + +/*Callback for headset event. */ +static void HSCallback(const PMIC_HS_STATE hs_st) +{ + + msleep(10); + + if (headset_state == 1) { + pmic_audio_output_disable_phantom_ground(); + headset_state = 0; + if (audio_mixer_control.stdac_playback_active) + pmic_audio_output_clear_port(audio_mixer_control. + stdac_handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + else if (audio_mixer_control.voice_codec_handle) + pmic_audio_output_clear_port(audio_mixer_control. + voice_codec_handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + + if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE, + 1); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE, + 1); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) { +#ifdef CONFIG_HEADSET_DETECT_ENABLE + /*This is a temporary workaround which should be removed later */ + set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1); +#else + set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 1); +#endif + } + if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 1); + } + + } else { + headset_state = 1; + + pmic_audio_output_enable_phantom_ground(); + + audio_mixer_control.old_prof = + audio_mixer_control.output_device; + + if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE, + 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE, + 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 0); + } + /*This is a temporary workaround which should be removed later */ + set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1); + + } +} + +#endif +/*! + * This function configures audio multiplexer to support + * audio data routing in PMIC master mode. + * + * @param ssi SSI of the ARM to connect to the DAM. + */ +void configure_dam_pmic_master(int ssi) +{ + int source_port; + int target_port; + + if (ssi == SSI1) { + pr_debug("DAM: port 1 -> port 4\n"); + source_port = audio_data->src_port; + + target_port = port_4; + } else { + pr_debug("DAM: port 2 -> port 5\n"); + source_port = port_2; + target_port = port_5; + } + + dam_reset_register(source_port); + dam_reset_register(target_port); + + dam_select_mode(source_port, normal_mode); + dam_select_mode(target_port, internal_network_mode); + + dam_set_synchronous(source_port, true); + dam_set_synchronous(target_port, true); + + dam_select_RxD_source(source_port, target_port); + dam_select_RxD_source(target_port, source_port); + + dam_select_TxFS_direction(source_port, signal_out); + dam_select_TxFS_source(source_port, false, target_port); + + dam_select_TxClk_direction(source_port, signal_out); + dam_select_TxClk_source(source_port, false, target_port); + + dam_select_RxFS_direction(source_port, signal_out); + dam_select_RxFS_source(source_port, false, target_port); + + dam_select_RxClk_direction(source_port, signal_out); + dam_select_RxClk_source(source_port, false, target_port); + + dam_set_internal_network_mode_mask(target_port, 0xfc); + + writel(AUD_MUX_CONF, IO_ADDRESS(AUDMUX_BASE_ADDR) + 0x38); +} + +/*! + * This function configures the SSI in order to receive audio + * from PMIC (recording). Configuration of SSI consists mainly in + * setting the following: + * + * 1) SSI to use (SSI1 or SSI2) + * 2) SSI mode (normal or network. We use always network mode) + * 3) SSI STCCR register settings, which control the sample rate (BCL and + * FS clocks) + * 4) Watermarks for SSI FIFOs as well as timeslots to be used. + * 5) Enable SSI. + * + * @param substream pointer to the structure of the current stream. + */ +void configure_ssi_rx(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + pr_debug("configure_ssi_rx: SSI %d\n", ssi + 1); + + ssi_enable(ssi, false); + ssi_synchronous_mode(ssi, true); + ssi_network_mode(ssi, true); + + if (machine_is_mx27ads()) { + ssi_tx_clock_divide_by_two(ssi, 0); + ssi_tx_clock_prescaler(ssi, 0); + ssi_tx_frame_rate(ssi, 2); + } + /* OJO */ + ssi_tx_frame_rate(ssi, 1); + + ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_tx_word_length(ssi, ssi_16_bits); + + ssi_rx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_rx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_rx_fifo_enable(ssi, ssi_fifo_0, true); + ssi_rx_bit0(ssi, true); + + ssi_rx_fifo_full_watermark(ssi, ssi_fifo_0, RX_WATERMARK); + + /* We never use the divider by 2 implemented in SSI */ + ssi_rx_clock_divide_by_two(ssi, 0); + + /* Set prescaler range (a fixed divide-by-eight prescaler + * in series with the variable prescaler) to 0 as we don't + * need it. + */ + ssi_rx_clock_prescaler(ssi, 0); + + /* Currently, only supported sample length is 16 bits */ + ssi_rx_word_length(ssi, ssi_16_bits); + + /* set direction of clocks ("externally" means that clocks come + * from PMIC to MCU) + */ + ssi_rx_frame_direction(ssi, ssi_tx_rx_externally); + ssi_rx_clock_direction(ssi, ssi_tx_rx_externally); + + /* Frame Rate Divider Control. + * In Normal mode, this ratio determines the word + * transfer rate. In Network mode, this ration sets + * the number of words per frame. + */ + ssi_tx_frame_rate(ssi, 4); + ssi_rx_frame_rate(ssi, 4); + + ssi_enable(ssi, true); +} + +/*! + * This function configures the SSI in order to + * send data to PMIC. Configuration of SSI consists + * mainly in setting the following: + * + * 1) SSI to use (SSI1 or SSI2) + * 2) SSI mode (normal for normal use e.g. playback, network for mixing) + * 3) SSI STCCR register settings, which control the sample rate (BCL and + * FS clocks) + * 4) Watermarks for SSI FIFOs as well as timeslots to be used. + * 5) Enable SSI. + * + * @param substream pointer to the structure of the current stream. + */ +void configure_ssi_tx(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + struct snd_pcm_runtime *runtime; + int ssi; + int device, stream_id = -1; + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + runtime = substream->runtime; + ssi = s->ssi; + + pr_debug("configure_ssi_tx: SSI %d\n", ssi + 1); + + ssi_enable(ssi, false); + ssi_synchronous_mode(ssi, true); + if (runtime->channels == 1) { + if (stream_id == 2) { + ssi_network_mode(ssi, true); + } else { +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_network_mode(ssi, false); +#endif + } + } else { + ssi_network_mode(ssi, true); + } + +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_two_channel_mode(ssi, true); + ssi_tx_fifo_enable(ssi, ssi_fifo_1, true); + ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_1, TX_WATERMARK); +#endif + + ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_tx_fifo_enable(ssi, ssi_fifo_0, true); + ssi_tx_bit0(ssi, true); + + ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_0, TX_WATERMARK); + + /* We never use the divider by 2 implemented in SSI */ + ssi_tx_clock_divide_by_two(ssi, 0); + + ssi_tx_clock_prescaler(ssi, 0); + + /*Currently, only supported sample length is 16 bits */ + ssi_tx_word_length(ssi, ssi_16_bits); + + /* clocks are being provided by PMIC */ + ssi_tx_frame_direction(ssi, ssi_tx_rx_externally); + ssi_tx_clock_direction(ssi, ssi_tx_rx_externally); + + if (runtime->channels == 1) { +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + if (stream_id == 2) { + ssi_tx_frame_rate(ssi, 4); + } else { + ssi_tx_frame_rate(ssi, 1); + } +#else + if (stream_id == 2) { + ssi_tx_frame_rate(ssi, 2); + } +#endif + + } else { + ssi_tx_frame_rate(ssi, 2); + } + + ssi_enable(ssi, true); +} + +/*! + * This function normalizes speed given by the user + * if speed is not supported, the function will + * calculate the nearest one. + * + * @param speed speed requested by the user. + * + * @return The normalized speed. + */ +int adapt_speed(int speed) +{ + + /* speeds from 8k to 96k */ + if (speed >= (64000 + 96000) / 2) { + speed = 96000; + } else if (speed >= (48000 + 64000) / 2) { + speed = 64000; + } else if (speed >= (44100 + 48000) / 2) { + speed = 48000; + } else if (speed >= (32000 + 44100) / 2) { + speed = 44100; + } else if (speed >= (24000 + 32000) / 2) { + speed = 32000; + } else if (speed >= (22050 + 24000) / 2) { + speed = 24000; + } else if (speed >= (16000 + 22050) / 2) { + speed = 22050; + } else if (speed >= (12000 + 16000) / 2) { + speed = 16000; + } else if (speed >= (11025 + 12000) / 2) { + speed = 12000; + } else if (speed >= (8000 + 11025) / 2) { + speed = 11025; + } else { + speed = 8000; + } + return speed; +} + +/*! + * This function get values to be put in PMIC registers. + * This values represents the sample rate that PMIC + * should use for current playback or recording. + * + * @param substream pointer to the structure of the current stream. + */ +void normalize_speed_for_pmic(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + pmic_audio_device_t *pmic_device; + struct snd_pcm_runtime *runtime; + int device, stream_id = -1; + device = substream->pcm->device; + if (device == 0) { + if ((audio_mixer_control.codec_capture_active == 1) + && (substream->stream == 1)) { + stream_id = 1; + } else + stream_id = 0; + } else { + stream_id = 2; + } + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + pmic_device = &s->pmic_audio_device; + runtime = substream->runtime; + + /* As the driver allows continuous sample rate, we must adapt the rate */ + runtime->rate = adapt_speed(runtime->rate); + + if (pmic_device->handle == audio_mixer_control.voice_codec_handle) { + switch (runtime->rate) { + case 8000: + pmic_device->sample_rate = VCODEC_RATE_8_KHZ; + break; + case 16000: + pmic_device->sample_rate = VCODEC_RATE_16_KHZ; + break; + default: + pmic_device->sample_rate = VCODEC_RATE_8_KHZ; + break; + } + + } else if (pmic_device->handle == audio_mixer_control.stdac_handle) { + switch (runtime->rate) { + case 8000: + pmic_device->sample_rate = STDAC_RATE_8_KHZ; + break; + + case 11025: + pmic_device->sample_rate = STDAC_RATE_11_025_KHZ; + break; + + case 12000: + pmic_device->sample_rate = STDAC_RATE_12_KHZ; + break; + + case 16000: + pmic_device->sample_rate = STDAC_RATE_16_KHZ; + break; + + case 22050: + pmic_device->sample_rate = STDAC_RATE_22_050_KHZ; + break; + + case 24000: + pmic_device->sample_rate = STDAC_RATE_24_KHZ; + break; + + case 32000: + pmic_device->sample_rate = STDAC_RATE_32_KHZ; + break; + + case 44100: + pmic_device->sample_rate = STDAC_RATE_44_1_KHZ; + break; + + case 48000: + pmic_device->sample_rate = STDAC_RATE_48_KHZ; + break; + + case 64000: + pmic_device->sample_rate = STDAC_RATE_64_KHZ; + break; + + case 96000: + pmic_device->sample_rate = STDAC_RATE_96_KHZ; + break; + + default: + pmic_device->sample_rate = STDAC_RATE_8_KHZ; + } + } + +} + +/*! + * This function configures number of channels for next audio operation + * (recording/playback) Number of channels define if sound is stereo + * or mono. + * + * @param substream pointer to the structure of the current stream. + * + */ +void set_pmic_channels(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + struct snd_pcm_runtime *runtime; + int device = -1, stream_id = -1; + + chip = snd_pcm_substream_chip(substream); + device = substream->pcm->device; + + if (device == 0) { + if (substream->pstr->stream == 1) { + stream_id = 1; + } else { + stream_id = 0; + } + } else { + stream_id = 2; + } + s = &chip->s[stream_id]; + runtime = substream->runtime; + + if (runtime->channels == 2) { + ssi_tx_mask_time_slot(s->ssi, MASK_2_TS); + ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC); + } else { + if (stream_id == 2) { +#ifdef CONFIG_MXC_PMIC_SC55112 + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_REC); +#else + + if (audio_mixer_control.mixing_active == 1) + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_MIX); + else + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS); + +#endif + } else { + + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_STDAC); + } +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC); +#endif + + } + +} + +/*! + * This function sets the input device in PMIC. It takes an + * ALSA value and modifies registers using pmic-specific values. + * + * @param handle Handle to the PMIC device opened + * @param val ALSA value. This value defines the input device that + * PMIC should activate to get audio signal (recording) + * @param enable Whether to enable or diable the input + */ +int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev, + bool enable) +{ + + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + if (handle != NULL) { + if (audio_mixer_control.input_device & SOUND_MASK_PHONEIN) { + pmic_audio_vcodec_set_mic(handle, MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS1); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS1); + } + if (audio_mixer_control.input_device & SOUND_MASK_MIC) { + pmic_audio_vcodec_set_mic(handle, NO_MIC, MIC2_AUX); + pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS2); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS2); + } + if (audio_mixer_control.input_device & SOUND_MASK_LINE) { + pmic_audio_vcodec_set_mic(handle, NO_MIC, TXIN_EXT); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC, + TXIN_EXT); + } + up(&audio_mixer_control.sem); + return 0; + + } + switch (dev) { + case IP_HANDSET: + pr_debug("Input: SOUND_MASK_PHONEIN \n"); + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_enable_micbias(handle, + MIC_BIAS1); + } + audio_mixer_control.input_device |= + SOUND_MASK_PHONEIN; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_disable_micbias + (handle, MIC_BIAS1); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_PHONEIN; + } + } + break; + + case IP_HEADSET: + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_enable_micbias(handle, + MIC_BIAS2); + } + audio_mixer_control.input_device |= + SOUND_MASK_MIC; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_disable_micbias + (handle, MIC_BIAS2); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_MIC; + } + // Enable Mic with MIC2_AUX + } + break; + + case IP_LINEIN: + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + NO_MIC, + TXIN_EXT); + } + audio_mixer_control.input_device |= + SOUND_MASK_LINE; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + NO_MIC, + TXIN_EXT); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_LINE; + } + } + break; + + default: + up(&audio_mixer_control.sem); + return -1; + break; + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_input_device); + +int get_mixer_input_device() +{ + int val; + val = audio_mixer_control.input_device; + return val; +} + +EXPORT_SYMBOL(get_mixer_input_device); + +/*! + * This function sets the PMIC input device's gain. + * Note that the gain is the input volume + * + * @param handle Handle to the opened PMIC device + * @param val gain to be applied. This value can go + * from 0 (mute) to 100 (max gain) + */ +int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val) +{ + int leftdb, rightdb; + int left, right; + + left = (val & 0x00ff); + right = ((val & 0xff00) >> 8); + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + leftdb = (left * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX; + rightdb = (right * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX; + audio_mixer_control.input_volume = val; + if (audio_mixer_control.voice_codec_handle == handle) { + pmic_audio_vcodec_set_record_gain(handle, VOLTAGE_TO_VOLTAGE, + leftdb, VOLTAGE_TO_VOLTAGE, + rightdb); + } else if ((handle == NULL) + && (audio_mixer_control.codec_capture_active)) { + pmic_audio_vcodec_set_record_gain(audio_mixer_control. + voice_codec_handle, + VOLTAGE_TO_VOLTAGE, leftdb, + VOLTAGE_TO_VOLTAGE, rightdb); + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_input_gain); + +int get_mixer_input_gain() +{ + int val; + val = audio_mixer_control.input_volume; + return val; +} + +EXPORT_SYMBOL(get_mixer_input_gain); + +/*! + * This function sets the PMIC output device's volume. + * + * @param handle Handle to the PMIC device opened + * @param volume ALSA value. This value defines the playback volume + * @param dev which output device gets affected by this volume + * + */ + +int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume, + OUTPUT_DEVICES dev) +{ + int leftdb, rightdb; + int right, left; + + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + left = (volume & 0x00ff); + right = ((volume & 0xff00) >> 8); + + leftdb = (left * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX; + rightdb = (right * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX; + if (handle == NULL) { + /* Invoked by mixer */ + audio_mixer_control.master_volume_out = volume; + if (audio_mixer_control.codec_playback_active) + pmic_audio_output_set_pgaGain(audio_mixer_control. + voice_codec_handle, + rightdb); + if (audio_mixer_control.stdac_playback_active) + pmic_audio_output_set_pgaGain(audio_mixer_control. + stdac_handle, rightdb); + + } else { + /* change the required volume */ + audio_mixer_control.master_volume_out = volume; + pmic_audio_output_set_pgaGain(handle, rightdb); + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_volume); + +int get_mixer_output_volume() +{ + int val; + val = audio_mixer_control.master_volume_out; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_volume); + +/*! + * This function sets the PMIC output device's balance. + * + * @param bal Balance to be applied. This value can go + * from 0 (Left atten) to 100 (Right atten) + * 50 is both equal + */ +int set_mixer_output_balance(int bal) +{ + int channel = 0; + PMIC_AUDIO_OUTPUT_BALANCE_GAIN b_gain; + PMIC_AUDIO_HANDLE handle; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + // Convert ALSA value to PMIC value i.e. atten and channel value + if (bal < 0) + bal = 0; + if (bal > 100) + bal = 100; + if (bal < 50) { + channel = 1; + } else { + bal = 100 - bal; + channel = 0; + } + + b_gain = bal / 8; + + audio_mixer_control.mixer_balance = bal; + if (audio_mixer_control.codec_playback_active) { + handle = audio_mixer_control.voice_codec_handle; + // Use codec's handle to set balance + } else if (audio_mixer_control.stdac_playback_active) { + handle = audio_mixer_control.stdac_handle; + // Use STDac's handle to set balance + } else { + up(&audio_mixer_control.sem); + return 0; + } + if (channel == 0) + pmic_audio_output_set_balance(handle, BAL_GAIN_0DB, b_gain); + else + pmic_audio_output_set_balance(handle, b_gain, BAL_GAIN_0DB); + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_balance); + +int get_mixer_output_balance() +{ + int val; + val = audio_mixer_control.mixer_balance; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_balance); + +/*! + * This function sets the PMIC output device's mono adder config. + * + * @param mode Mono adder mode to be set + */ +int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode) +{ + PMIC_AUDIO_HANDLE handle; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + audio_mixer_control.mixer_mono_adder = mode; + if (audio_mixer_control.codec_playback_active) { + handle = audio_mixer_control.voice_codec_handle; + // Use codec's handle to set balance + pmic_audio_output_enable_mono_adder(audio_mixer_control. + voice_codec_handle, mode); + } else if (audio_mixer_control.stdac_playback_active) { + handle = audio_mixer_control.stdac_handle; + pmic_audio_output_enable_mono_adder(audio_mixer_control. + stdac_handle, mode); + // Use STDac's handle to set balance + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_mono_adder); + +int get_mixer_output_mono_adder() +{ + int val; + val = audio_mixer_control.mixer_mono_adder; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_mono_adder); + +/*! + * This function sets the output device(s) in PMIC. It takes an + * ALSA value and modifies registers using PMIC-specific values. + * + * @param handle handle to the device already opened + * @param src Source connected to o/p device + * @param dev Output device to be enabled + * @param enable Enable or disable the device + * + */ +int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src, + OUTPUT_DEVICES dev, bool enable) +{ + PMIC_AUDIO_OUTPUT_PORT port; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + if (!((src == CODEC_DIR_OUT) || (src == MIXER_OUT))) { + up(&audio_mixer_control.sem); + return -1; + } + if (handle != (PMIC_AUDIO_HANDLE) NULL) { + /* Invoked by playback stream */ + if (audio_mixer_control.output_device & SOUND_MASK_PHONEOUT) { + audio_mixer_control.output_active[OP_EARPIECE] = 1; + pmic_audio_output_set_port(handle, MONO_SPEAKER); + } else { + audio_mixer_control.output_active[OP_EARPIECE] = 0; + pmic_audio_output_clear_port(handle, MONO_SPEAKER); + } + if (audio_mixer_control.output_device & SOUND_MASK_SPEAKER) { + audio_mixer_control.output_active[OP_HANDSFREE] = 1; + pmic_audio_output_set_port(handle, MONO_LOUDSPEAKER); + } else { + audio_mixer_control.output_active[OP_HANDSFREE] = 0; + pmic_audio_output_clear_port(handle, MONO_LOUDSPEAKER); + } + if (audio_mixer_control.output_device & SOUND_MASK_VOLUME) { + audio_mixer_control.output_active[OP_HEADSET] = 1; + if (dev != OP_MONO) { + pmic_audio_output_set_port(handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + } else { + pmic_audio_output_set_port(handle, + STEREO_HEADSET_LEFT); + /*This is a temporary workaround which should be removed later */ + } + + } else { + audio_mixer_control.output_active[OP_HEADSET] = 0; + pmic_audio_output_clear_port(handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + } + if (audio_mixer_control.output_device & SOUND_MASK_PCM) { + audio_mixer_control.output_active[OP_LINEOUT] = 1; + pmic_audio_output_set_port(handle, + STEREO_OUT_LEFT | + STEREO_OUT_RIGHT); + } else { + audio_mixer_control.output_active[OP_LINEOUT] = 0; + pmic_audio_output_clear_port(handle, + STEREO_OUT_LEFT | + STEREO_OUT_RIGHT); + } + } else { + switch (dev) { + case OP_EARPIECE: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_PHONEOUT; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_PHONEOUT; + } + port = MONO_SPEAKER; + break; + case OP_HANDSFREE: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_SPEAKER; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_SPEAKER; + } + port = MONO_LOUDSPEAKER; + break; + case OP_HEADSET: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_VOLUME; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_VOLUME; + } + port = STEREO_HEADSET_LEFT | STEREO_HEADSET_RIGHT; + break; + case OP_MONO: + /*This is a temporary workaround which should be removed later */ + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_VOLUME; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_VOLUME; + } + port = STEREO_HEADSET_LEFT; + break; + case OP_LINEOUT: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_PCM; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_PCM; + } + port = STEREO_OUT_LEFT | STEREO_OUT_RIGHT; + break; + default: + up(&audio_mixer_control.sem); + return -1; + break; + } + /* Invoked by mixer .. little tricky to handle over here */ + if (audio_mixer_control.codec_playback_active) { + if (enable) { + audio_mixer_control.output_active[dev] = 1; + pmic_audio_output_set_port(audio_mixer_control. + voice_codec_handle, + port); + } else { + audio_mixer_control.output_active[dev] = 0; + pmic_audio_output_clear_port + (audio_mixer_control.voice_codec_handle, + port); + } + } + if (audio_mixer_control.stdac_playback_active) { + if (enable) { + audio_mixer_control.output_active[dev] = 1; + pmic_audio_output_set_port(audio_mixer_control. + stdac_handle, port); + } else { + audio_mixer_control.output_active[dev] = 0; + pmic_audio_output_clear_port + (audio_mixer_control.stdac_handle, port); + } + } + + } + up(&audio_mixer_control.sem); + return 0; + // Set O/P device with handle and port + +} + +EXPORT_SYMBOL(set_mixer_output_device); + +int get_mixer_output_device() +{ + int val; + val = audio_mixer_control.output_device; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_device); + +/*! + * This function configures the CODEC for playback/recording. + * + * main configured elements are: + * - audio path on PMIC + * - external clock to generate BC and FS clocks + * - PMIC mode (master or slave) + * - protocol + * - sample rate + * + * @param substream pointer to the structure of the current stream. + * @param stream_id index into the audio_stream array. + */ +void configure_codec(struct snd_pcm_substream *substream, int stream_id) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + pmic_audio_device_t *pmic; + PMIC_AUDIO_HANDLE handle; + int ssi_bus; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + pmic = &s->pmic_audio_device; + handle = audio_mixer_control.voice_codec_handle; + + ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2; + + pmic_audio_vcodec_set_rxtx_timeslot(handle, USE_TS0); + pmic_audio_vcodec_enable_mixer(handle, USE_TS1, VCODEC_MIX_IN_0DB, + VCODEC_MIX_OUT_0DB); + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_4_TIMESLOTS); + + msleep(20); + pmic_audio_vcodec_set_clock(handle, pmic->pll, pmic->pll_rate, + pmic->sample_rate, NO_INVERT); + msleep(20); + pmic_audio_vcodec_set_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS); + pmic_audio_digital_filter_reset(handle); + msleep(15); + if (stream_id == 2) { + pmic_audio_output_enable_mixer(handle); + set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1); + set_mixer_output_volume(handle, + audio_mixer_control.master_volume_out, + OP_HEADSET); + } else { + set_mixer_input_device(handle, IP_NODEV, 1); + set_mixer_input_gain(handle, audio_mixer_control.input_volume); + } + pmic_audio_enable(handle); +} + +/*! + * This function configures the STEREODAC for playback/recording. + * + * main configured elements are: + * - audio path on PMIC + * - external clock to generate BC and FS clocks + * - PMIC mode (master or slave) + * - protocol + * - sample rate + * + * @param substream pointer to the structure of the current stream. + */ +void configure_stereodac(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + pmic_audio_device_t *pmic; + int ssi_bus; + PMIC_AUDIO_HANDLE handle; + struct snd_pcm_runtime *runtime; + + chip = snd_pcm_substream_chip(substream); + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + pmic = &s->pmic_audio_device; + handle = pmic->handle; + runtime = substream->runtime; + + if (runtime->channels == 1) { + audio_mixer_control.mixer_mono_adder = MONO_ADD_LEFT_RIGHT; + } else { + audio_mixer_control.mixer_mono_adder = MONO_ADDER_OFF; + } + + ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2; + pmic_audio_stdac_set_rxtx_timeslot(handle, USE_TS0_TS1); + pmic_audio_stdac_enable_mixer(handle, USE_TS2_TS3, STDAC_NO_MIX, + STDAC_MIX_OUT_0DB); +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_4_TIMESLOTS); +#else + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_2_TIMESLOTS); +#endif + pmic_audio_stdac_set_clock(handle, pmic->pll, pmic->pll_rate, + pmic->sample_rate, NO_INVERT); + + pmic_audio_stdac_set_config(handle, STDAC_MASTER_CLOCK_OUTPUTS); + pmic_audio_output_enable_mixer(handle); + audio_mixer_control.stdac_out_to_mixer = 1; + pmic_audio_digital_filter_reset(handle); + msleep(10); + pmic_audio_output_enable_phantom_ground(); + set_mixer_output_volume(handle, audio_mixer_control.master_volume_out, + OP_HEADSET); + pmic_audio_output_enable_mono_adder(handle, + audio_mixer_control. + mixer_mono_adder); +#ifdef CONFIG_HEADSET_DETECT_ENABLE + set_mixer_output_device(handle, MIXER_OUT, OP_MONO, 1); +#else + set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1); +#endif + pmic_audio_enable(handle); + +} + +/*! + * This function disables CODEC's amplifiers, volume and clock. + * @param handle Handle of voice codec + */ + +void disable_codec(PMIC_AUDIO_HANDLE handle) +{ + pmic_audio_disable(handle); + pmic_audio_vcodec_clear_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS); +} + +/*! + * This function disables STEREODAC's amplifiers, volume and clock. + * @param handle Handle of STdac + * @param + */ + +void disable_stereodac(void) +{ + + audio_mixer_control.stdac_out_to_mixer = 0; +} + +/*! + * This function configures PMIC for recording. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +int configure_pmic_recording(struct snd_pcm_substream *substream) +{ + + configure_codec(substream, 1); + return 0; +} + +/*! + * This function configures PMIC for playing back. + * + * @param substream pointer to the structure of the current stream. + * @param stream_id Index into the audio_stream array . + * + * @return 0 on success, -1 otherwise. + */ + +int configure_pmic_playback(struct snd_pcm_substream *substream, int stream_id) +{ + if (stream_id == 0) { + configure_stereodac(substream); + } else if (stream_id == 2 || stream_id == 3) { + configure_codec(substream, stream_id); + } + return 0; +} + +/*! + * This function shutsdown the PMIC soundcard. + * Nothing to be done here + * + * @param mxc_audio pointer to the sound card structure. + * + * @return + */ +/* +static void mxc_pmic_audio_shutdown(mxc_pmic_audio_t * mxc_audio) +{ + +} +*/ + +/*! + * This function configures the DMA channel used to transfer + * audio from MCU to PMIC + * + * @param substream pointer to the structure of the current stream. + * @param callback pointer to function that will be + * called when a SDMA TX transfer finishes. + * + * @return 0 on success, -1 otherwise. + */ +static int +configure_write_channel(audio_stream_t * s, mxc_dma_callback_t callback, + int stream_id) +{ + int ret = -1; + int channel = -1; +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + + int channel1 = -1; +#endif + + if (stream_id == 0) { + if (audio_data->ssi_num == 2) { + channel = + mxc_dma_request(MXC_DMA_SSI2_16BIT_TX0, + "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + + } else { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, + "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + } + + } + if (stream_id == 3) { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); + if (channel < 0) { + pr_debug("error requesting a write dma channel\n"); + return -1; + } + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + + return -1; + } + s->dma_wchannel = channel; + } +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + else if (stream_id == 2) { + channel1 = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX1, "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel1, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel1); + return -1; + } + s->dma_wchannel = channel1; + } +#else + + if (stream_id == 2) { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + } +#endif + /*if (channel < 0) { + pr_debug("error requesting a write dma channel\n"); + return -1; + } */ + + return 0; +} + +/*! + * This function configures the DMA channel used to transfer + * audio from PMIC to MCU + * + * @param substream pointer to the structure of the current stream. + * @param callback pointer to function that will be + * called when a SDMA RX transfer finishes. + * + * @return 0 on success, -1 otherwise. + */ +static int configure_read_channel(audio_stream_t * s, + mxc_dma_callback_t callback) +{ + int ret = -1; + int channel = -1; + + channel = mxc_dma_request(MXC_DMA_SSI1_16BIT_RX0, "ALSA RX DMA"); + if (channel < 0) { + pr_debug("error requesting a read dma channel\n"); + return -1; + } + + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + + return 0; +} + +/*! + * This function frees the stream structure + * + * @param s pointer to the structure of the current stream. + */ +static void audio_dma_free(audio_stream_t * s) +{ + /* + * There is nothing to be done here since the dma channel has been + * freed either in the callback or in the stop method + */ + +} + +/*! + * This function gets the dma pointer position during record. + * Our DMA implementation does not allow to retrieve this position + * when a transfert is active, so, it answers the middle of + * the current period beeing transfered + * + * @param s pointer to the structure of the current stream. + * + */ +static u_int audio_get_capture_dma_pos(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (s->tx_spin) { + offset = (runtime->period_size * (s->periods)) + 0; + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos offset %d\n", offset); + } else { + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset); + } + + return offset; +} + +/*! + * This function gets the dma pointer position during playback. + * Our DMA implementation does not allow to retrieve this position + * when a transfert is active, so, it answers the middle of + * the current period beeing transfered + * + * @param s pointer to the structure of the current stream. + * + */ +static u_int audio_get_playback_dma_pos(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (s->tx_spin) { + offset = (runtime->period_size * (s->periods)) + 0; + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos offset %d\n", offset); + } else { + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset); + } + + return offset; +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred to PMIC. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_playback_dma(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size = 0; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + unsigned int dma_size_mix = 0, offset_mix; + mxc_dma_requestbuf_t dma_request_mix; + int ret1 = 0; +#endif + int device; + int stream_id; + + substream = s->stream; + runtime = substream->runtime; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else { + stream_id = 3; + } + + pr_debug("\nDMA direction %d\(0 is playback 1 is capture)\n", + s->stream_id); + +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + +#else + if (stream_id == 2) { + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + } else if (stream_id == 3) { + memset(&dma_request_mix, 0, sizeof(mxc_dma_requestbuf_t)); + } +#endif + if (s->active) { + if (ssi_get_status(s->ssi) & (ssi_transmitter_underrun_0)) { + ssi_enable(s->ssi, false); + ssi_transmit_enable(s->ssi, false); + ssi_enable(s->ssi, true); + } +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + if (snd_BUG_ON(dma_size > DMA_BUF_SIZE)) + return; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + + if (g_audio_iram_en && stream_id == 0) { + dma_request.src_addr = ADMA_BASE_PADDR + offset; + } else +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + { + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + } + if (stream_id == 0) { + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + } else if (stream_id == 2) { + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + } + dma_request.num_of_bytes = dma_size; + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return; + } + s->period++; + s->period %= runtime->periods; +#else + + if (stream_id == 2) { + dma_size = + frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + if (snd_BUG_ON(dma_size > DMA_BUF_SIZE)) + return; + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + dma_request.num_of_bytes = dma_size; + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", + ret); + return; + } + s->period++; + s->period %= runtime->periods; + + } else if (stream_id == 3) { + + dma_size_mix = + frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset_mix = dma_size_mix * s->period; + if (snd_BUG_ON(dma_size_mix > DMA_BUF_SIZE)) + return; + dma_request_mix.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset_mix, + dma_size_mix, DMA_TO_DEVICE)); + dma_request_mix.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + dma_request_mix.num_of_bytes = dma_size_mix; + + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset_mix, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1, + MXC_DMA_MODE_WRITE); + ret1 = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret1) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", + ret1); + return; + } + s->period++; + s->period %= runtime->periods; + + } +#endif +#ifdef MXC_SOUND_PLAYBACK_CHAIN_DMA_EN + + if ((s->period > s->periods) && ((s->period - s->periods) > 1)) { + pr_debug + ("audio playback chain dma: already double buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug + ("audio playback chain dma: already double buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug + ("audio playback chain dma: s->period == s->periods\n"); + return; + } + + if (snd_pcm_playback_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug + ("audio playback chain dma: available data is not enough\n"); + return; + } + + pr_debug + ("audio playback chain dma:to set up the 2nd dma buffer\n"); + +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + offset = dma_size * s->period; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (g_audio_iram_en && stream_id == 0) { + dma_request.src_addr = ADMA_BASE_PADDR + offset; + } else +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + { + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + } + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); +#else + if (stream_id == 3) { + offset_mix = dma_size_mix * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, + runtime->dma_area + offset_mix, + dma_size, DMA_TO_DEVICE)); + + mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1, + MXC_DMA_MODE_WRITE); + } else { + offset = dma_size * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, + runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + } + +#endif + ret = mxc_dma_enable(s->dma_wchannel); + + s->period++; + s->period %= runtime->periods; +#endif /* MXC_SOUND_PLAYBACK_CHAIN_DMA_EN */ + } +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred from PMIC. The function receives the address and the size + * of the block that will store the audio samples and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_capture_dma(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; + + substream = s->stream; + runtime = substream->runtime; + + pr_debug("\nDMA direction %d\ + (0 is playback 1 is capture)\n", s->stream_id); + + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + snd_BUG_ON(dma_size > DMA_BUF_SIZE); + + dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL, + runtime-> + dma_area + + offset, + dma_size, + DMA_FROM_DEVICE)); + dma_request.src_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 0); + dma_request.num_of_bytes = dma_size; + + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", offset, + runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return; + } + s->period++; + s->period %= runtime->periods; + +#ifdef MXC_SOUND_CAPTURE_CHAIN_DMA_EN + if ((s->period > s->periods) && ((s->period - s->periods) > 1)) { + pr_debug + ("audio capture chain dma: already double buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug + ("audio capture chain dma: already double buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug + ("audio capture chain dma: s->period == s->periods\n"); + return; + } + + if (snd_pcm_capture_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug + ("audio capture chain dma: available data is not enough\n"); + return; + } + + pr_debug + ("audio capture chain dma:to set up the 2nd dma buffer\n"); + offset = dma_size * s->period; + dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL, + runtime-> + dma_area + + offset, + dma_size, + DMA_FROM_DEVICE)); + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + + s->period++; + s->period %= runtime->periods; +#endif /* MXC_SOUND_CAPTURE_CHAIN_DMA_EN */ + } +} + +/*! + * This is a callback which will be called + * when a TX transfer finishes. The call occurs + * in interrupt context. + * + * @param dat pointer to the structure of the current stream. + * + */ +static void audio_playback_dma_callback(void *data, int error, + unsigned int count) +{ + audio_stream_t *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + s->tx_spin = 0; + s->periods++; + s->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + spin_lock(&s->dma_lock); + + /* + * Trig next DMA transfer + */ + audio_playback_dma(s); + + spin_unlock(&s->dma_lock); + +} + +/*! + * This is a callback which will be called + * when a RX transfer finishes. The call occurs + * in interrupt context. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_capture_dma_callback(void *data, int error, + unsigned int count) +{ + audio_stream_t *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + s->tx_spin = 0; + s->periods++; + s->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + + /* + * Trig next DMA transfer + */ + audio_capture_dma(s); + + spin_unlock(&s->dma_lock); + +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for playback. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_audio_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + int err; + int device; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else { + stream_id = 3; + } + + chip = snd_pcm_substream_chip(substream); + //stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = 0; + + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->tx_spin = 0; + s->active = 1; + audio_playback_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + return err; +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for capture. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_audio_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + int err; + + chip = snd_pcm_substream_chip(substream); + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = 0; + + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->tx_spin = 0; + s->active = 1; + audio_capture_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + return err; +} + +/*! + * This function configures the hardware to allow audio + * playback operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_playback_prepare(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + int device = -1, stream_id = -1; + + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else if (device == 2) + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + ssi = s->ssi; + + normalize_speed_for_pmic(substream); + + configure_dam_pmic_master(ssi); + + configure_ssi_tx(substream); + + ssi_interrupt_enable(ssi, ssi_tx_dma_interrupt_enable); + ssi_interrupt_enable(ssi, ssi_tx_interrupt_enable); + + if (configure_pmic_playback(substream, stream_id) == -1) + pr_debug(KERN_ERR "MXC: PMIC Playback Config FAILED\n"); + ssi_interrupt_enable(ssi, ssi_tx_fifo_0_empty); + ssi_interrupt_enable(ssi, ssi_tx_fifo_1_empty); + /* + ssi_transmit_enable(ssi, true); + */ + msleep(20); + set_pmic_channels(substream); + s->period = 0; + s->periods = 0; + + msleep(100); + + return 0; +} + +/*! + * This function gets the current capture pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static +snd_pcm_uframes_t snd_mxc_audio_capture_pointer(struct snd_pcm_substream + *substream) +{ + mxc_pmic_audio_t *chip; + + chip = snd_pcm_substream_chip(substream); + return audio_get_capture_dma_pos(&chip->s[substream->pstr->stream]); +} + +/*! + * This function gets the current playback pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static snd_pcm_uframes_t +snd_mxc_audio_playback_pointer(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + int device; + int stream_id; + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else + stream_id = 3; + chip = snd_pcm_substream_chip(substream); + return audio_get_playback_dma_pos(&chip->s[stream_id]); +} + +/*! + * This structure reprensents the capabilities of the driver + * in capture mode. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This structure reprensents the capabilities of the driver + * in playback mode for ST-Dac. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_playback_stereo = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This structure reprensents the capabilities of the driver + * in playback mode for Voice-codec. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_playback_mono = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This function opens a PMIC audio device in playback mode + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_playback_open(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + struct snd_pcm_runtime *runtime; + int stream_id = -1; + int err; + audio_stream_t *s; + PMIC_AUDIO_HANDLE temp_handle; + int device = -1; + + device = substream->pcm->device; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else if (device == 2) { + stream_id = 3; + } + + s = &chip->s[stream_id]; + err = -1; + + if (stream_id == 0) { + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.codec_playback_active + || audio_mixer_control.codec_capture_active)) { + return -EBUSY; + } + + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, STEREO_DAC)) { + audio_mixer_control.stdac_handle = temp_handle; + audio_mixer_control.stdac_playback_active = 1; + chip->s[stream_id].pmic_audio_device.handle = + temp_handle; + } else { + return -EBUSY; + } + } else if (stream_id == 2) { + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.stdac_playback_active)) { + return -EBUSY; + } + + audio_mixer_control.codec_playback_active = 1; + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) { + audio_mixer_control.voice_codec_handle = temp_handle; + chip->s[stream_id].pmic_audio_device.handle = + temp_handle; + } else { + if (audio_mixer_control.codec_capture_active) { + temp_handle = + audio_mixer_control.voice_codec_handle; + } else { + return -EBUSY; + } + } + + } else if (stream_id == 3) { + audio_mixer_control.mixing_active = 1; + + } + + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + msleep(250); + + chip->s[stream_id].stream = substream; + + if (stream_id == 0) + runtime->hw = snd_mxc_pmic_playback_stereo; + else if (stream_id == 2) + runtime->hw = snd_mxc_pmic_playback_mono; + + else if (stream_id == 3) { + runtime->hw = snd_mxc_pmic_playback_mono; + + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_mono)) + < 0) + return err; + } +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (g_audio_iram_en && stream_id == 0) { + runtime->hw.buffer_bytes_max = MAX_IRAM_SIZE; + runtime->hw.period_bytes_max = DMA_IRAM_SIZE; + } +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < + 0) + goto exit_err; + if (stream_id == 0) { + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_stereo)) + < 0) + goto exit_err; + + } else if (stream_id == 2) { + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_mono)) + < 0) + goto exit_err; + } + msleep(10); + + /* setup DMA controller for playback */ + if ((err = + configure_write_channel(&mxc_audio->s[stream_id], + audio_playback_dma_callback, + stream_id)) < 0) + goto exit_err; + + /* enable ssi clock */ + clk_enable(audio_data->ssi_clk[s->ssi]); + + return 0; + exit_err: +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (stream_id == 0) + mxc_snd_pcm_iram_put(); +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + return err; + +} + +/*! + * This function closes an PMIC audio device for playback. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_playback_close(struct snd_pcm_substream + *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + PMIC_AUDIO_HANDLE handle; + int ssi; + int device, stream_id = -1; + handle = (PMIC_AUDIO_HANDLE) NULL; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + ssi = s->ssi; + + if (audio_mixer_control.mixing_active == 1) { + goto End; + + } else { + + if (stream_id == 0) { + disable_stereodac(); + audio_mixer_control.stdac_playback_active = 0; + handle = audio_mixer_control.stdac_handle; + audio_mixer_control.stdac_handle = NULL; + chip->s[stream_id].pmic_audio_device.handle = NULL; + } else if ((stream_id == 2) || (stream_id == 3)) { + + audio_mixer_control.codec_playback_active = 0; + handle = audio_mixer_control.voice_codec_handle; + disable_codec(handle); + audio_mixer_control.voice_codec_handle = NULL; + chip->s[stream_id].pmic_audio_device.handle = NULL; + } + + } + pmic_audio_close(handle); + + ssi_transmit_enable(ssi, false); + ssi_interrupt_disable(ssi, ssi_tx_dma_interrupt_enable); + ssi_tx_fifo_enable(ssi, ssi_fifo_0, false); + ssi_enable(ssi, false); + mxc_dma_free((mxc_audio->s[stream_id]).dma_wchannel); + + chip->s[stream_id].stream = NULL; + End: + audio_mixer_control.mixing_active = 0; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (stream_id == 0) + mxc_snd_pcm_iram_put(); +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + /* disable ssi clock */ + clk_disable(audio_data->ssi_clk[ssi]); + + return 0; +} + +/*! + * This function closes a PMIC audio device for capture. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_capture_close(struct snd_pcm_substream *substream) +{ + PMIC_AUDIO_HANDLE handle; + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + audio_mixer_control.codec_capture_active = 0; + handle = audio_mixer_control.voice_codec_handle; + disable_codec(handle); + audio_mixer_control.voice_codec_handle = NULL; + chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle = NULL; + + pmic_audio_close(handle); + + ssi_receive_enable(ssi, false); + ssi_interrupt_disable(ssi, ssi_rx_dma_interrupt_enable); + ssi_rx_fifo_enable(ssi, ssi_fifo_0, false); + ssi_enable(ssi, false); + mxc_dma_free((mxc_audio->s[1]).dma_wchannel); + + chip->s[substream->pstr->stream].stream = NULL; + + /* disable ssi clock */ + clk_disable(audio_data->ssi_clk[ssi]); + + return 0; +} + +/*! + * This function configure the Audio HW in terms of memory allocation. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime; + int ret = 0, size; + int device, stream_id = -1; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + struct snd_dma_buffer *dmab; +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + + runtime = substream->runtime; + ret = + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + size = params_buffer_bytes(hw_params); + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + + runtime->dma_addr = virt_to_phys(runtime->dma_area); + +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && g_audio_iram_en + && stream_id == 0) { + if (runtime->dma_buffer_p + && (runtime->dma_buffer_p != &g_iram_dmab)) { + snd_pcm_lib_free_pages(substream); + } + dmab = &g_iram_dmab; + dmab->dev = substream->dma_buffer.dev; + dmab->area = (char *)ADMA_BASE_VADDR; + dmab->addr = ADMA_BASE_PADDR; + dmab->bytes = size; + snd_pcm_set_runtime_buffer(substream, dmab); + runtime->dma_bytes = size; + } else +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + { + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + + runtime->dma_addr = virt_to_phys(runtime->dma_area); + } + + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_addr 0x(%x)\n", + (unsigned int)runtime->dma_addr); + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_area 0x(%x)\n", + (unsigned int)runtime->dma_area); + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_bytes 0x(%x)\n", + (unsigned int)runtime->dma_bytes); + + return ret; +} + +/*! + * This function frees the audio hardware at the end of playback/capture. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_hw_free(struct snd_pcm_substream *substream) +{ +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (substream->runtime->dma_buffer_p == &g_iram_dmab) { + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; + } else +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + { + return snd_pcm_lib_free_pages(substream); + } + return 0; +} + +/*! + * This function configures the hardware to allow audio + * capture operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_capture_prepare(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + normalize_speed_for_pmic(substream); + + pr_debug("substream->pstr->stream %d\n", substream->pstr->stream); + pr_debug("SSI %d\n", ssi + 1); + configure_dam_pmic_master(ssi); + + configure_ssi_rx(substream); + + ssi_interrupt_enable(ssi, ssi_rx_dma_interrupt_enable); + + if (configure_pmic_recording(substream) == -1) + pr_debug(KERN_ERR "MXC: PMIC Record Config FAILED\n"); + + ssi_interrupt_enable(ssi, ssi_rx_fifo_0_full); + ssi_receive_enable(ssi, true); + + msleep(20); + set_pmic_channels(substream); + + s->period = 0; + s->periods = 0; + + return 0; +} + +/*! + * This function opens an PMIC audio device in capture mode + * on Codec. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_capture_open(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + struct snd_pcm_runtime *runtime; + int stream_id; + int err; + PMIC_AUDIO_HANDLE temp_handle; + audio_stream_t *s; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = -1; + + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.stdac_playback_active)) { + return -EBUSY; + } + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) { + audio_mixer_control.voice_codec_handle = temp_handle; + audio_mixer_control.codec_capture_active = 1; + chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle = + temp_handle; + } else { + if (audio_mixer_control.codec_playback_active) { + temp_handle = audio_mixer_control.voice_codec_handle; + } else { + return -EBUSY; + } + } + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + + chip->s[stream_id].stream = substream; + + if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = snd_mxc_pmic_capture; + } else { + return err; + } + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < + 0) { + return err; + } + + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_capture_rates)) < 0) { + return err; + } + + /* setup DMA controller for Record */ + err = configure_read_channel(&mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE], + audio_capture_dma_callback); + if (err < 0) { + return err; + } + + /* enable ssi clock */ + clk_enable(audio_data->ssi_clk[s->ssi]); + + msleep(50); + + return 0; +} + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +static struct page *snd_mxc_audio_playback_nopage(struct vm_area_struct *area, + unsigned long address, + int *type) +{ + struct snd_pcm_substream *substream = area->vm_private_data; + struct snd_pcm_runtime *runtime; + unsigned long offset; + struct page *page; + void *vaddr; + size_t dma_bytes; + + if (substream == NULL) + return NOPAGE_OOM; + runtime = substream->runtime; + if (g_audio_iram_en) { + return NOPAGE_SIGBUS; + } + offset = area->vm_pgoff << PAGE_SHIFT; + offset += address - area->vm_start; + if (snd_BUG_ON(offset % PAGE_SIZE) != 0) + return NOPAGE_OOM; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) + return NOPAGE_SIGBUS; + if (substream->ops->page) { + page = substream->ops->page(substream, offset); + if (!page) + return NOPAGE_OOM; + } else { + vaddr = runtime->dma_area + offset; + page = virt_to_page(vaddr); + } + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, + .nopage = snd_mxc_audio_playback_nopage, +}; + +#ifdef CONFIG_ARCH_MX3 +static inline int snd_mxc_set_pte_attr(struct mm_struct *mm, + pmd_t * pmd, + unsigned long addr, unsigned long end) +{ + + pte_t *pte; + spinlock_t *ptl; + pte = pte_alloc_map_lock(mm, pmd, addr, &ptl); + + if (!pte) + return -ENOMEM; + do { + BUG_ON(pte_none(*pte)); //The mapping is created. It should not be none. + *(pte - 512) |= 0x83; //Directly modify to non-shared device + } while (pte++, addr += PAGE_SIZE, addr != end); + + pte_unmap_unlock(pte - 1, ptl); + + return 0; + +} + +static int snd_mxc_set_pmd_attr(struct mm_struct *mm, + pud_t * pud, + unsigned long addr, unsigned long end) +{ + + pmd_t *pmd; + unsigned long next; + pmd = pmd_alloc(mm, pud, addr); + + if (!pmd) + return -ENOMEM; + do { + + next = pmd_addr_end(addr, end); + if (snd_mxc_set_pte_attr(mm, pmd, addr, next)) + return -ENOMEM; + + } while (pmd++, addr = next, addr != end); + + return 0; + +} + +static int snd_mxc_set_pud_attr(struct mm_struct *mm, + pgd_t * pgd, + unsigned long addr, unsigned long end) +{ + + pud_t *pud; + unsigned long next; + pud = pud_alloc(mm, pgd, addr); + + if (!pud) + return -ENOMEM; + do { + + next = pud_addr_end(addr, end); + if (snd_mxc_set_pmd_attr(mm, pud, addr, next)) + return -ENOMEM; + + } while (pud++, addr = next, addr != end); + + return 0; + +} + +static inline int snd_mxc_set_pgd_attr(struct vm_area_struct *area) +{ + + int ret = 0; + pgd_t *pgd; + struct mm_struct *mm = current->mm; + unsigned long next, addr = area->vm_start; + + pgd = pgd_offset(mm, addr); + flush_cache_range(area, addr, area->vm_end); + + do { + if (!pgd_present(*pgd)) + return -1; + + next = pgd_addr_end(addr, area->vm_end); + if ((ret = snd_mxc_set_pud_attr(mm, pgd, addr, next))) + break; + + } while (pgd++, addr = next, addr != area->vm_end); + + return ret; + +} + +#else +#define snd_mxc_set_page_attr() (0) +#endif + +static int snd_mxc_audio_playback_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + int ret = 0; + area->vm_ops = &snd_mxc_audio_playback_vm_ops; + area->vm_private_data = substream; + if (g_audio_iram_en) { + unsigned long off = area->vm_pgoff << PAGE_SHIFT; + unsigned long phys = ADMA_BASE_PADDR + off; + unsigned long size = area->vm_end - area->vm_start; + if (off + size > MAX_IRAM_SIZE) { + return -EINVAL; + } + area->vm_page_prot = pgprot_nonshareddev(area->vm_page_prot); + area->vm_flags |= VM_IO; + ret = + remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT, + size, area->vm_page_prot); + if (ret == 0) { + ret = snd_mxc_set_pgd_attr(area); + } + + } else { + area->vm_flags |= VM_RESERVED; + } + if (ret == 0) + area->vm_ops->open(area); + return ret; +} + +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * This structure is the list of operation that the driver + * must provide for the capture interface + */ +static struct snd_pcm_ops snd_card_mxc_audio_capture_ops = { + .open = snd_card_mxc_audio_capture_open, + .close = snd_card_mxc_audio_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_audio_hw_params, + .hw_free = snd_mxc_audio_hw_free, + .prepare = snd_mxc_audio_capture_prepare, + .trigger = snd_mxc_audio_capture_trigger, + .pointer = snd_mxc_audio_capture_pointer, +}; + +/*! + * This structure is the list of operation that the driver + * must provide for the playback interface + */ +static struct snd_pcm_ops snd_card_mxc_audio_playback_ops = { + .open = snd_card_mxc_audio_playback_open, + .close = snd_card_mxc_audio_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_audio_hw_params, + .hw_free = snd_mxc_audio_hw_free, + .prepare = snd_mxc_audio_playback_prepare, + .trigger = snd_mxc_audio_playback_trigger, + .pointer = snd_mxc_audio_playback_pointer, +#ifdef CONFIG_SND_MXC_PMIC_IRAM + .mmap = snd_mxc_audio_playback_mmap, +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ +}; + +/*! + * This functions initializes the capture audio device supported by + * PMIC IC. + * + * @param mxc_audio pointer to the sound card structure + * + */ +void init_device_capture(mxc_pmic_audio_t * mxc_audio) +{ + audio_stream_t *audio_stream; + pmic_audio_device_t *pmic_device; + + audio_stream = &mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE]; + pmic_device = &audio_stream->pmic_audio_device; + + /* These parameters defines the identity of + * the device (codec or stereodac) + */ + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = VCODEC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = VCODEC_CLI_26MHZ; +#endif + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + +} + +/*! + * This functions initializes the playback audio device supported by + * PMIC IC. + * + * @param mxc_audio pointer to the sound card structure. + * @param device device ID of PCM instance. + * + */ +void init_device_playback(mxc_pmic_audio_t * mxc_audio, int device) +{ + audio_stream_t *audio_stream; + pmic_audio_device_t *pmic_device; + if (device == 0) + audio_stream = &mxc_audio->s[0]; + else + audio_stream = &mxc_audio->s[2]; + pmic_device = &audio_stream->pmic_audio_device; + + /* These parameters defines the identity of + * the device (codec or stereodac) + */ + if (device == 0) { + if (audio_data->ssi_num == 2) { + audio_stream->ssi = SSI2; + audio_stream->dam_port = DAM_PORT_5; + pmic_device->ssi = SSI2; + } else { + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + } + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = STDAC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = STDAC_CLI_26MHZ; +#endif + + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + + } else if ((device == 1) || (device == 2)) { + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = VCODEC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = VCODEC_CLI_26MHZ; +#endif + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + } + +} + +/*! + * This functions initializes the mixer related information + * + * @param mxc_audio pointer to the sound card structure. + * + */ +void mxc_pmic_mixer_controls_init(mxc_pmic_audio_t * mxc_audio) +{ + audio_mixer_control_t *audio_control; + int i = 0; + + audio_control = &audio_mixer_control; + + memset(audio_control, 0, sizeof(audio_mixer_control_t)); + sema_init(&audio_control->sem, 1); + + audio_control->input_device = SOUND_MASK_MIC; + audio_control->output_device = SOUND_MASK_VOLUME | SOUND_MASK_PCM; + + /* PMIC has to internal sources that can be routed to output + One is codec direct out and the other is mixer out + Initially we configure all outputs to have no source and + will be later configured either by PCM stream handler or mixer */ + for (i = 0; i < OP_MAXDEV && i != OP_HEADSET; i++) { + audio_control->source_for_output[i] = MIXER_OUT; + } + + /* These bits are initially reset and set when playback begins */ + audio_control->codec_out_to_mixer = 0; + audio_control->stdac_out_to_mixer = 0; + + audio_control->mixer_balance = 50; + if (machine_is_mx31ads()) + audio_control->mixer_mono_adder = STEREO_OPPOSITE_PHASE; + else + audio_control->mixer_mono_adder = MONO_ADDER_OFF; + /* Default values for input and output */ + audio_control->input_volume = ((40 << 8) & 0xff00) | (40 & 0x00ff); + audio_control->master_volume_out = ((50 << 8) & 0xff00) | (50 & 0x00ff); + + if (PMIC_SUCCESS != pmic_audio_set_autodetect(1)) + msleep(30); +} + +/*! + * This functions initializes the 2 audio devices supported by + * PMIC IC. The parameters define the type of device (CODEC or STEREODAC) + * + * @param mxc_audio pointer to the sound card structure. + * @param device device id of the PCM stream. + * + */ +void mxc_pmic_audio_init(mxc_pmic_audio_t * mxc_audio, int device) +{ + if (device == 0) { + mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Audio out"; + mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = + SNDRV_PCM_STREAM_PLAYBACK; + mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].id = "Audio in"; + mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = + SNDRV_PCM_STREAM_CAPTURE; + } else if (device == 1) { + mxc_audio->s[2].id = "Audio out"; + mxc_audio->s[2].stream_id = 2; + } else if (device == 2) { + mxc_audio->s[2].id = "Audio out"; + mxc_audio->s[2].stream_id = 3; + } + + init_device_playback(mxc_audio, device); + if (!device) { + init_device_capture(mxc_audio); + } +} + +/*! + * This function the soundcard structure. + * + * @param mxc_audio pointer to the sound card structure. + * @param device the device index (zero based) + * + * @return 0 on success, -1 otherwise. + */ +static int __devinit snd_card_mxc_audio_pcm(mxc_pmic_audio_t *mxc_audio, + int device) +{ + struct snd_pcm *pcm; + int err; + + /* + * Create a new PCM instance with 1 capture stream and 1 playback substream + */ + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 1, &pcm)) < 0) { + return err; + } + + /* + * this sets up our initial buffers and sets the dma_type to isa. + * isa works but I'm not sure why (or if) it's the right choice + * this may be too large, trying it for now + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_mxc_audio_capture_ops); + + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + mxc_pmic_audio_init(mxc_audio, device); + + /* Allocating a second device for PCM playback on voice codec */ + device = 1; + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) { + return err; + } + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + mxc_pmic_audio_init(mxc_audio, device); + + device = 2; + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) { + return err; + } + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + + /* End of allocation */ + /* FGA for record and not hard coded playback */ + mxc_pmic_mixer_controls_init(mxc_audio); + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function suspends all active streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_suspend(struct platform_device *dev, + pm_message_t state) +{ + struct snd_card *card = platform_get_drvdata(dev); + mxc_pmic_audio_t *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm[0]); + //mxc_alsa_audio_shutdown(chip); + + return 0; +} + +/*! + * This function resumes all suspended streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_resume(struct platform_device *dev) +{ + struct snd_card *card = platform_get_drvdata(dev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif /* COMFIG_PM */ + +/*! + * This function frees the sound card structure + * + * @param card pointer to the sound card structure. + * + * @return 0 on success, -1 otherwise. + */ +void snd_mxc_audio_free(struct snd_card *card) +{ + mxc_pmic_audio_t *chip; + + chip = card->private_data; + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); + mxc_audio = NULL; + card->private_data = NULL; + kfree(chip); + +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __devinit mxc_alsa_audio_probe(struct platform_device *pdev) +{ + int err; + struct snd_card *card; + + audio_data = (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!audio_data) { + dev_err(&pdev->dev, "Can't get the platform data for ALSA\n"); + return -EINVAL; + } + /* register the soundcard */ + err = snd_card_create(-1, id, THIS_MODULE, sizeof(mxc_pmic_audio_t), &card); + if (err < 0) { + return -ENOMEM; + } + + mxc_audio = kcalloc(1, sizeof(*mxc_audio), GFP_KERNEL); + if (mxc_audio == NULL) { + return -ENOMEM; + } + + card->private_data = (void *)mxc_audio; + card->private_free = snd_mxc_audio_free; + + mxc_audio->card = card; + card->dev = &pdev->dev; + if ((err = snd_card_mxc_audio_pcm(mxc_audio, 0)) < 0) { + goto nodev; + } + + if (0 == mxc_alsa_create_ctl(card, (void *)&audio_mixer_control)) + printk(KERN_INFO "Control ALSA component registered\n"); + +#ifdef CONFIG_HEADSET_DETECT_ENABLE + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + pmic_audio_set_callback((PMIC_AUDIO_CALLBACK) HSCallback, + HEADSET_DETECTED | HEADSET_REMOVED, &hs_state); +#endif + /* Set autodetect feature in order to allow audio operations */ + + spin_lock_init(&(mxc_audio->s[0].dma_lock)); + spin_lock_init(&(mxc_audio->s[1].dma_lock)); + spin_lock_init(&(mxc_audio->s[2].dma_lock)); + + strcpy(card->driver, "MXC"); + strcpy(card->shortname, "PMIC-audio"); + sprintf(card->longname, "MXC Freescale with PMIC"); + + if ((err = snd_card_register(card)) == 0) { + pr_debug(KERN_INFO "MXC audio support initialized\n"); + platform_set_drvdata(pdev, card); + return 0; + } + + nodev: + snd_card_free(card); + return err; +} + +static int __devexit mxc_alsa_audio_remove(struct platform_device *dev) +{ + snd_card_free(mxc_audio->card); + kfree(mxc_audio); + platform_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver mxc_alsa_audio_driver = { + .probe = mxc_alsa_audio_probe, + .remove = __devexit_p(mxc_alsa_audio_remove), +#ifdef CONFIG_PM + .suspend = snd_mxc_audio_suspend, + .resume = snd_mxc_audio_resume, +#endif + .driver = { + .name = "mxc_alsa", + }, +}; + +static int __init mxc_alsa_audio_init(void) +{ + return platform_driver_register(&mxc_alsa_audio_driver); +} + +/*! + * This function frees the sound driver structure. + * + */ + +static void __exit mxc_alsa_audio_exit(void) +{ + platform_driver_unregister(&mxc_alsa_audio_driver); +} + +module_init(mxc_alsa_audio_init); +module_exit(mxc_alsa_audio_exit); + +MODULE_AUTHOR("FREESCALE SEMICONDUCTOR"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MXC driver for ALSA"); +MODULE_SUPPORTED_DEVICE("{{PMIC}}"); + +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for MXC + PMIC soundcard."); diff --git a/sound/arm/mxc-alsa-pmic.h b/sound/arm/mxc-alsa-pmic.h new file mode 100644 index 000000000000..46c5ca4ae1ab --- /dev/null +++ b/sound/arm/mxc-alsa-pmic.h @@ -0,0 +1,110 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /*! + * @file mxc-alsa-pmic.h + * @brief + * @ingroup SOUND_DRV + */ + +#ifndef __MXC_ALSA_PMIC_H__ +#define __MXC_ALSA_PMIC_H__ + +#ifdef __KERNEL__ + + /**/ +#define PMIC_MASTER 0x1 +#define PMIC_SLAVE 0x2 + /**/ +#define DAM_PORT_4 port_4 +#define DAM_PORT_5 port_5 + /**/ +#define PMIC_STEREODAC 0x1 +#define PMIC_CODEC 0x2 + /**/ +#define PMIC_AUDIO_ADDER_STEREO 0x1 +#define PMIC_AUDIO_ADDER_STEREO_OPPOSITE 0x2 +#define PMIC_AUDIO_ADDER_MONO 0x4 +#define PMIC_AUDIO_ADDER_MONO_OPPOSITE 0x8 +#define TX_WATERMARK 0x4 +#define RX_WATERMARK 0x6 + /**/ +#define SSI_DEFAULT_FIFO 0x0 +#define DEFAULT_MASTER_CLOCK 0x1 + /**/ +#define SDMA_TXFIFO_WATERMARK 0x4 +#define SDMA_RXFIFO_WATERMARK 0x6 + /**/ +#define TIMESLOTS_2 0x3 +#define TIMESLOTS_4 0x2 +#define SAMPLE_RATE_MAX 0x9 + /**/ +#define CLK_SELECT_SLAVE_BCL 0x7 +#define CLK_SELECT_SLAVE_CLI 0x5 +#define CLK_SELECT_MASTER_CLI 0x4 +/* Volume to balance ratio */ +#define VOLUME_BALANCE_RATIO ((6 + 39) / (21 + 21)) +/* -21dB */ +#define PMIC_BALANCE_MIN 0x0 +/* 0dB*/ +#define PMIC_BALANCE_MAX 0x7 +/* -21dB left */ +#define BALANCE_MIN 0x0 +/* 0db, no balance */ +#define NO_BALANCE 0x32 +/* -21dB right*/ +#define BALANCE_MAX 0x64 +/* -8dB */ +#define PMIC_INPUT_VOLUME_MIN 0x0 +/* +23dB */ +#define PMIC_INPUT_VOLUME_MAX 0x1f +/* -39dB */ +#define PMIC_OUTPUT_VOLUME_MIN PMIC_INPUT_VOLUME_MIN +/* +6dB */ +#define PMIC_OUTPUT_VOLUME_MAX 0xd +/* -8dB */ +#define INPUT_VOLUME_MIN 0x0 +/* +23dB */ +#define INPUT_VOLUME_MAX 0x64 +/* -39dB */ +#define OUTPUT_VOLUME_MIN 0x0 +/* +6dB */ +#define OUTPUT_VOLUME_MAX 0x64 +/* 96 Khz */ +#define SAMPLE_FREQ_MAX 96000 +/* 8 Khz */ +#define SAMPLE_FREQ_MIN 8000 +/*! + * Define channels available on MC13783. This is mainly used + * in mixer interface to control different input/output + * devices + */ +#define MXC_STEREO_OUTPUT ( SOUND_MASK_VOLUME | SOUND_MASK_PCM ) +#define MXC_STEREO_INPUT ( SOUND_MASK_PHONEIN ) +#define MXC_MONO_OUTPUT ( SOUND_MASK_PHONEOUT | SOUND_MASK_SPEAKER ) +#define MXC_MONO_INPUT ( SOUND_MASK_LINE | SOUND_MASK_MIC ) +#define SNDCTL_CLK_SET_MASTER _SIOR('Z', 30, int) +#define SNDCTL_PMIC_READ_OUT_BALANCE _SIOR('Z', 8, int) +//#define SNDCTL_MC13783_WRITE_OUT_BALANCE _SIOWR('Z', 9, int) +#define SNDCTL_PMIC_WRITE_OUT_ADDER _SIOWR('Z', 10, int) +#define SNDCTL_PMIC_READ_OUT_ADDER _SIOR('Z', 11, int) +#define SNDCTL_PMIC_WRITE_CODEC_FILTER _SIOWR('Z', 12, int) +#define SNDCTL_PMIC_READ_CODEC_FILTER _SIOR('Z', 13, int) +#define SNDCTL_DAM_SET_OUT_PORT _SIOWR('Z', 14, int) +#endif /* __KERNEL__ */ +#endif /* __MXC_ALSA_PMIC_H__ */ diff --git a/sound/arm/mxc-alsa-spdif.c b/sound/arm/mxc-alsa-spdif.c new file mode 100644 index 000000000000..15d053f78a5a --- /dev/null +++ b/sound/arm/mxc-alsa-spdif.c @@ -0,0 +1,2266 @@ +/* + * 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); + clk_enable(spdif_data->spdif_audio_clk); + + runtime = substream->runtime; + chip->s[SNDRV_PCM_STREAM_PLAYBACK].stream = substream; + runtime->hw = snd_spdif_playback_hw; + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_stereo); + if (err < 0) + goto failed; + err = + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto failed; + + return 0; + failed: + clk_disable(spdif_data->spdif_clk); + return err; +} + +/*! + * This function closes an spdif device for playback. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_playback_close(struct snd_pcm_substream + *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + spdif_data = chip->card->dev->platform_data; + + pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS)); + + spdif_intr_status(); + spdif_intr_enable(INT_TXFIFO_RESYNC, 0); + spdif_tx_uninit(); + clk_disable(spdif_data->spdif_audio_clk); + clk_disable(spdif_data->spdif_clk); + mxc_dma_free(chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel); + chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel = 0; + + return 0; +} + +/*! TODO: update the dma start/stop callback routine + * This function stops the current dma transfert for capture + * and clears the dma pointers. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_stop_rx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->periods; + + s->active = 0; + s->period = 0; + s->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + mxc_dma_disable(s->dma_wchannel); + spdif_dma_enable(SCR_DMA_RX_EN, 0); + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); +} + +/*! + * This function is called whenever a new audio block needs to be + * received from SPDIF. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_start_rx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size = 0; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; + + substream = s->stream; + runtime = substream->runtime; + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + dma_request.dst_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_FROM_DEVICE)); + + dma_request.src_addr = + (dma_addr_t) (SPDIF_BASE_ADDR + SPDIF_REG_SRL); + dma_request.num_of_bytes = dma_size; + /* config and enable sdma for RX */ + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + /* enable SPDIF dma */ + spdif_dma_enable(SCR_DMA_RX_EN, 1); + + if (ret) { + pr_info("audio_process_dma: cannot queue DMA \ + buffer\n"); + return; + } + s->period++; + s->period %= runtime->periods; + + if ((s->period > s->periods) + && ((s->period - s->periods) > 1)) { + pr_debug("audio capture chain dma: already double \ + buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug("audio capture chain dma: already double \ + buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug("audio capture chain dma: s->period == \ + s->periods\n"); + return; + } + + if (snd_pcm_capture_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug("audio capture chain dma: available data \ + is not enough\n"); + return; + } + + pr_debug + ("audio playback chain dma:to set up the 2nd dma buffer\n"); + + offset = dma_size * s->period; + dma_request.dst_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_FROM_DEVICE)); + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + s->period++; + s->period %= runtime->periods; + + } + return; +} + +/*! + * This is a callback which will be called + * when a RX transfer finishes. The call occurs + * in interrupt context. + * + * @param data pointer to the structure of the current stream + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void spdif_rx_callback(void *data, int error, unsigned int count) +{ + struct mxc_spdif_stream *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + spin_lock(&s->dma_lock); + s->periods++; + s->periods %= runtime->periods; + + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + spin_unlock(&s->dma_lock); + + if (s->active) + snd_pcm_period_elapsed(s->stream); + spin_lock(&s->dma_lock); + spdif_start_rx(s); + spin_unlock(&s->dma_lock); +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for capture. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_spdif_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_stream *s; + int err = 0; + unsigned long flags; + chip = snd_pcm_substream_chip(substream); + s = &chip->s[SNDRV_PCM_STREAM_CAPTURE]; + + spin_lock_irqsave(&s->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->active = 1; + spdif_start_rx(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + spdif_stop_rx(s); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + s->active = 0; + s->periods = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + s->active = 1; + spdif_start_rx(s); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + spdif_start_rx(s); + break; + default: + err = -EINVAL; + break; + } + spin_unlock_irqrestore(&s->dma_lock, flags); + return err; +} + +/*! + * This function configures the hardware to allow audio + * capture operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_capture_prepare(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + struct snd_pcm_runtime *runtime; + int err; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + spdif_data = chip->card->dev->platform_data; + + spdif_rx_init(); + /* enable interrupts, include DPLL lock */ + spdif_intr_enable(INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL | + INT_URX_OV | INT_QRX_FUL | INT_QRX_OV | + INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC | + INT_LOSS_LOCK, 1); + + /* setup rx clock source */ + spdif_set_rx_clksrc(spdif_data->spdif_clkid, SPDIF_DEFAULT_GAINSEL, 1); + + /* setup DMA controller for spdif rx */ + err = spdif_configure_dma_channel(&chip-> + s[SNDRV_PCM_STREAM_CAPTURE], + spdif_rx_callback); + if (err < 0) { + pr_info("snd_mxc_spdif_playback_prepare - err < 0\n"); + return err; + } + + /* Debug: dump registers */ + pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR)); + pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE)); + pr_debug("SRPC: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRPC)); + pr_debug("FreqMeas: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRFM)); + + return 0; +} + +/*! + * This function gets the current capture pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static snd_pcm_uframes_t +snd_mxc_spdif_capture_pointer(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + chip = snd_pcm_substream_chip(substream); + return spdif_get_dma_pos(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); +} + +/*! + * This function opens a spdif device in capture mode + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_capture_open(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct snd_pcm_runtime *runtime; + int err = 0; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + + spdif_data = chip->card->dev->platform_data; + /* enable rx bus clock */ + clk_enable(spdif_data->spdif_clk); + + runtime = substream->runtime; + chip->s[SNDRV_PCM_STREAM_CAPTURE].stream = substream; + runtime->hw = snd_spdif_capture_hw; + + /* set hw param constraints */ + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_capture_rates_stereo); + if (err < 0) + goto failed; + err = + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto failed; + + /* enable spdif dpll lock interrupt */ + spdif_intr_enable(INT_DPLL_LOCKED, 1); + + return 0; + + failed: + clk_disable(spdif_data->spdif_clk); + return err; +} + +/*! + * This function closes an spdif device for capture. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_capture_close(struct snd_pcm_substream + *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + spdif_data = chip->card->dev->platform_data; + + pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS)); + pr_debug("SRPC: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRPC)); + pr_debug("FreqMeas: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRFM)); + + spdif_intr_enable(INT_DPLL_LOCKED | INT_SYM_ERR | INT_BIT_ERR | + INT_URX_FUL | INT_URX_OV | INT_QRX_FUL | INT_QRX_OV | + INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC | + INT_LOSS_LOCK, 0); + spdif_rx_uninit(); + clk_disable(spdif_data->spdif_clk); + mxc_dma_free(chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel); + chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel = 0; + return 0; +} + +/*! + * This function configure the Audio HW in terms of memory allocation. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * @param hw_params Pointer to hardware paramters structure + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_hw_params(struct snd_pcm_substream + *substream, struct snd_pcm_hw_params + *hw_params) +{ + struct snd_pcm_runtime *runtime; + int ret = 0; + runtime = substream->runtime; + ret = + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (ret < 0) { + pr_info("snd_mxc_spdif_hw_params - ret: %d\n", ret); + return ret; + } + runtime->dma_addr = virt_to_phys(runtime->dma_area); + return ret; +} + +/*! + * This function frees the spdif hardware at the end of playback. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/*! + * This structure is the list of operation that the driver + * must provide for the playback interface + */ +static struct snd_pcm_ops snd_card_mxc_spdif_playback_ops = { + .open = snd_card_mxc_spdif_playback_open, + .close = snd_card_mxc_spdif_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_spdif_hw_params, + .hw_free = snd_mxc_spdif_hw_free, + .prepare = snd_mxc_spdif_playback_prepare, + .trigger = snd_mxc_spdif_playback_trigger, + .pointer = snd_mxc_spdif_playback_pointer, +}; + +static struct snd_pcm_ops snd_card_mxc_spdif_capture_ops = { + .open = snd_card_mxc_spdif_capture_open, + .close = snd_card_mxc_spdif_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_spdif_hw_params, + .hw_free = snd_mxc_spdif_hw_free, + .prepare = snd_mxc_spdif_capture_prepare, + .trigger = snd_mxc_spdif_capture_trigger, + .pointer = snd_mxc_spdif_capture_pointer, +}; + +/*! + * This functions initializes the playback audio device supported by + * spdif + * + * @param mxc_spdif pointer to the sound card structure. + * + */ +void mxc_init_spdif_device(struct mxc_spdif_device *mxc_spdif) +{ + + /* initial spinlock for control data */ + spin_lock_init(&mxc_spdif_control.ctl_lock); + + if (mxc_spdif->mxc_spdif_tx) { + + mxc_spdif->s[SNDRV_PCM_STREAM_PLAYBACK].id = "spdif tx"; + /* init tx channel status default value */ + mxc_spdif_control.ch_status[0] = + IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS_5015; + mxc_spdif_control.ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; + mxc_spdif_control.ch_status[2] = 0x00; + mxc_spdif_control.ch_status[3] = + IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM; + } + if (mxc_spdif->mxc_spdif_rx) { + + /* TODO: Add code here if capture is available */ + mxc_spdif->s[SNDRV_PCM_STREAM_CAPTURE].id = "spdif rx"; + } + +} + +/*! + * MXC SPDIF IEC958 controller(mixer) functions + * + * Channel status get/put control + * User bit value get/put control + * Valid bit value get control + * DPLL lock status get control + * User bit sync mode selection control + * + */ +static int mxc_pb_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int mxc_pb_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.iec958.status[0] = mxc_spdif_control.ch_status[0]; + uvalue->value.iec958.status[1] = mxc_spdif_control.ch_status[1]; + uvalue->value.iec958.status[2] = mxc_spdif_control.ch_status[2]; + uvalue->value.iec958.status[3] = mxc_spdif_control.ch_status[3]; + return 0; +} + +static int mxc_pb_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + unsigned int ch_status; + mxc_spdif_control.ch_status[0] = uvalue->value.iec958.status[0]; + mxc_spdif_control.ch_status[1] = uvalue->value.iec958.status[1]; + mxc_spdif_control.ch_status[2] = uvalue->value.iec958.status[2]; + mxc_spdif_control.ch_status[3] = uvalue->value.iec958.status[3]; + ch_status = + ((mxc_spdif_control.ch_status[2] << 16) | (mxc_spdif_control. + ch_status[1] << 8) | + mxc_spdif_control.ch_status[0]); + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCH); + ch_status = mxc_spdif_control.ch_status[3]; + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCL); + return 0; +} + +static int snd_mxc_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +/*! + * Get channel status from SPDIF_RX_CCHAN register + */ +static int snd_mxc_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int cstatus; + + if (!(__raw_readl(spdif_base_addr + SPDIF_REG_SIS) & INT_CNEW)) + return -EAGAIN; + + cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLH); + ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[2] = cstatus & 0xFF; + cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLL); + ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[5] = cstatus & 0xFF; + + /* clear intr */ + __raw_writel(INT_CNEW, spdif_base_addr + SPDIF_REG_SIC); + + return 0; +} + +/*! + * Get User bits (subcode) from chip value which readed out + * in UChannel register. + */ +static int snd_mxc_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags); + if (mxc_spdif_control.ready_buf) { + memcpy(&ucontrol->value.iec958.subcode[0], + &mxc_spdif_control. + subcode[(mxc_spdif_control.ready_buf - + 1) * SPDIF_UBITS_SIZE], SPDIF_UBITS_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags); + + return ret; +} + +/*! + * Q-subcode infomation. + * the byte size is SPDIF_UBITS_SIZE/8 + */ +static int snd_mxc_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = SPDIF_QSUB_SIZE; + return 0; +} + +/*! + * Get Q subcode from chip value which readed out + * in QChannel register. + */ +static int snd_mxc_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags); + if (mxc_spdif_control.ready_buf) { + memcpy(&ucontrol->value.bytes.data[0], + &mxc_spdif_control. + qsub[(mxc_spdif_control.ready_buf - + 1) * SPDIF_QSUB_SIZE], SPDIF_QSUB_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags); + + return ret; +} + +/*! + * Valid bit infomation. + */ +static int snd_mxc_spdif_vbit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/*! + * Get valid good bit from interrupt status register. + */ +static int snd_mxc_spdif_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SIS); + ucontrol->value.integer.value[0] = (int_val & INT_VAL_NOGOOD) != 0; + __raw_writel(INT_VAL_NOGOOD, spdif_base_addr + SPDIF_REG_SIC); + + return 0; +} + +/*! + * DPLL lock infomation. + */ +static int snd_mxc_spdif_rxrate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 96000; + return 0; +} + +/*! + * Get DPLL lock or not info from stable interrupt status register. + * User application must use this control to get locked, + * then can do next PCM operation + */ +static int snd_mxc_spdif_rxrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mxc_spdif_device *chip = snd_kcontrol_chip(kcontrol); + struct mxc_spdif_platform_data *spdif_data; + + spdif_data = chip->card->dev->platform_data; + + if (atomic_read(&chip->dpll_locked)) { + ucontrol->value.integer.value[0] = + spdif_get_rxclk_rate(spdif_data->spdif_clk, + SPDIF_DEFAULT_GAINSEL); + } else { + ucontrol->value.integer.value[0] = 0; + } + return 0; +} + +/*! + * User bit sync mode info + */ +static int snd_mxc_spdif_usync_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/*! + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int snd_mxc_spdif_usync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SRCD); + ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0; + return 0; +} + +/*! + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int snd_mxc_spdif_usync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET; + __raw_writel(int_val, spdif_base_addr + SPDIF_REG_SRCD); + return 0; +} + +/*! + * MXC SPDIF IEC958 controller defines + */ +static struct snd_kcontrol_new snd_mxc_spdif_ctrls[] = { + /* status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mxc_pb_spdif_info, + .get = mxc_pb_spdif_get, + .put = mxc_pb_spdif_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_info, + .get = snd_mxc_spdif_capture_get, + }, + /* user bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_info, + .get = snd_mxc_spdif_subcode_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_qinfo, + .get = snd_mxc_spdif_qget, + }, + /* valid bit error controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_vbit_info, + .get = snd_mxc_spdif_vbit_get, + }, + /* DPLL lock info get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "RX Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_rxrate_info, + .get = snd_mxc_spdif_rxrate_get, + }, + /* User bit sync mode set/get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 USyncMode CDText", + .access = + SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_usync_info, + .get = snd_mxc_spdif_usync_get, + .put = snd_mxc_spdif_usync_put, + }, +}; + +/*! + * This function the soundcard structure. + * + * @param mxc_spdif pointer to the sound card structure. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_pcm(struct mxc_spdif_device *mxc_spdif) +{ + struct snd_pcm *pcm; + int err; + err = snd_pcm_new(mxc_spdif->card, MXC_SPDIF_NAME, 0, + mxc_spdif->mxc_spdif_tx, + mxc_spdif->mxc_spdif_rx, &pcm); + if (err < 0) + return err; + + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + SPDIF_MAX_BUF_SIZE * 2, + SPDIF_MAX_BUF_SIZE * 2); + if (mxc_spdif->mxc_spdif_tx) + snd_pcm_set_ops(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_spdif_playback_ops); + if (mxc_spdif->mxc_spdif_rx) + snd_pcm_set_ops(pcm, + SNDRV_PCM_STREAM_CAPTURE, + &snd_card_mxc_spdif_capture_ops); + pcm->private_data = mxc_spdif; + pcm->info_flags = 0; + strncpy(pcm->name, MXC_SPDIF_NAME, sizeof(pcm->name)); + mxc_spdif->pcm = pcm; + mxc_init_spdif_device(mxc_spdif); + return 0; +} + +extern void gpio_spdif_active(void); + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_alsa_spdif_probe(struct platform_device + *pdev) +{ + int err, idx; + static int dev; + struct snd_card *card; + struct mxc_spdif_device *chip; + struct resource *res; + struct snd_kcontrol *kctl; + struct mxc_spdif_platform_data *plat_data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* register the soundcard */ + err = snd_card_create(index[dev], id[dev], THIS_MODULE, + sizeof(struct mxc_spdif_device), &card); + if (err < 0) + return err; + chip = card->private_data; + chip->card = card; + card->dev = &pdev->dev; + chip->reg_base = ioremap(res->start, res->end - res->start + 1); + spdif_base_addr = (unsigned long)chip->reg_base; + plat_data = (struct mxc_spdif_platform_data *)pdev->dev.platform_data; + chip->mxc_spdif_tx = plat_data->spdif_tx; + chip->mxc_spdif_rx = plat_data->spdif_rx; + chip->spdif_txclk_44100 = plat_data->spdif_clk_44100; + chip->spdif_txclk_48000 = plat_data->spdif_clk_48000; + atomic_set(&chip->dpll_locked, 0); + + err = snd_card_mxc_spdif_pcm(chip); + if (err < 0) + goto nodev; + + /*! + * Add controls to the card + */ + for (idx = 0; idx < ARRAY_SIZE(snd_mxc_spdif_ctrls); idx++) { + + kctl = snd_ctl_new1(&snd_mxc_spdif_ctrls[idx], chip); + if (kctl == NULL) { + err = -ENOMEM; + goto nodev; + } + /* check to add control to corresponding substream */ + if (strstr(kctl->id.name, "Playback")) + kctl->id.device = 0; + else + kctl->id.device = 1; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto nodev; + } + + clk_enable(plat_data->spdif_core_clk); + /*! + * SPDIF interrupt initialization + * software reset to SPDIF + */ + spdif_softreset(); + /* disable all the interrupts */ + spdif_intr_enable(0xffffff, 0); + /* spdif interrupt register and disable */ + 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"); |