[alsa-devel] i.MX audio support
Hi All,
This series adds support for AOSC on i.MX platforms. It uses DMA where available (i.MX27) and fiq otherwise. The driver works on all Phytec i.MX platforms, so the i.MX27, i.MX31 and the i.MX35 are supported including AC97 support.
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
AC97 support is a problem on i.MX. I found no way to tell the SSI controller which slots it should capture, so all slots the codec decides to send go into one single FIFO. The WM9712 I worked with sends data in slot 12 which we have to sort out manually from the FIFO, so I think there is no way to work around the fiq handler using the DMA engine with AC97.
The devdma code is actually a copy of sound/arm/devdma.[ch]. It should probably be selected somehow instead of duplicated.
I'm not very familiar with ALSA, so I expect some comments in this area. Let me know what has to be done to get this into mainline.
This series depends on the SSI resources and audmux support being present. I posted these patches on arm linux kernel recently and they are available here:
git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master
Any comments welcome.
Sascha
The following changes since commit 3f92a8bd5fb13e7e2505c65d1548910eaa843024: Uwe Kleine-König (1): imx: copy constants from mx3x.h to mx35.h using the appropriate namespace
are available in the git repository at:
git://git.pengutronix.de/git/imx/linux-2.6.git asoc
Sascha Hauer (12): mx3: Add SSI pins to iomux table mxc: iomux v3: remove resource handling add a mc13783 codec driver i.MX31 clock: rename SSI clocks to driver name mxc: mx1/mx2 DMA: add a possibility to create an endless DMA transfer imx-ssi sound driver add phycore sound support sound/soc/imx: Makefile/Kconfig changes for new driver pcm038: add sound support pcm043: add sound support pca100: add sound support pcm037: Add sound support
arch/arm/mach-mx2/pca100.c | 52 + arch/arm/mach-mx2/pcm038.c | 23 +- arch/arm/mach-mx3/clock.c | 4 +- arch/arm/mach-mx3/pcm037.c | 72 ++ arch/arm/mach-mx3/pcm043.c | 102 ++ arch/arm/plat-mxc/Makefile | 1 + arch/arm/plat-mxc/dma-mx1-mx2.c | 3 +- arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h | 8 + arch/arm/plat-mxc/include/mach/iomux-mx3.h | 8 + arch/arm/plat-mxc/include/mach/iomux-v3.h | 17 +- arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/iomux-v3.c | 36 +- arch/arm/plat-mxc/ssi-fiq.S | 134 +++ sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/mc13783.c | 739 ++++++++++++++ sound/soc/codecs/mc13783.h | 29 + sound/soc/imx/Kconfig | 15 +- sound/soc/imx/Makefile | 9 +- sound/soc/imx/devdma.c | 80 ++ sound/soc/imx/devdma.h | 3 + sound/soc/imx/imx-ssi.c | 1349 ++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 24 + sound/soc/imx/phycore.c | 181 ++++ 24 files changed, 2851 insertions(+), 60 deletions(-) create mode 100644 arch/arm/plat-mxc/include/mach/ssi.h create mode 100644 arch/arm/plat-mxc/ssi-fiq.S create mode 100644 sound/soc/codecs/mc13783.c create mode 100644 sound/soc/codecs/mc13783.h create mode 100644 sound/soc/imx/devdma.c create mode 100644 sound/soc/imx/devdma.h create mode 100644 sound/soc/imx/imx-ssi.c create mode 100644 sound/soc/imx/imx-ssi.h create mode 100644 sound/soc/imx/phycore.c
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/plat-mxc/include/mach/iomux-mx3.h | 8 ++++++++ 1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/arch/arm/plat-mxc/include/mach/iomux-mx3.h b/arch/arm/plat-mxc/include/mach/iomux-mx3.h index 0dcfb77..8a8fec1 100644 --- a/arch/arm/plat-mxc/include/mach/iomux-mx3.h +++ b/arch/arm/plat-mxc/include/mach/iomux-mx3.h @@ -700,6 +700,14 @@ enum iomux_pins { #define MX31_PIN_RTS1__GPIO2_6 IOMUX_MODE(MX31_PIN_RTS1, IOMUX_CONFIG_GPIO) #define MX31_PIN_CTS1__GPIO2_7 IOMUX_MODE(MX31_PIN_CTS1, IOMUX_CONFIG_GPIO) #define MX31_PIN_LCS0__GPIO3_23 IOMUX_MODE(MX31_PIN_LCS0, IOMUX_CONFIG_GPIO) +#define MX31_PIN_STXD4__STXD4 IOMUX_MODE(MX31_PIN_STXD4, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SRXD4__SRXD4 IOMUX_MODE(MX31_PIN_SRXD4, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SCK4__SCK4 IOMUX_MODE(MX31_PIN_SCK4, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SFS4__SFS4 IOMUX_MODE(MX31_PIN_SFS4, IOMUX_CONFIG_FUNC) +#define MX31_PIN_STXD5__STXD5 IOMUX_MODE(MX31_PIN_STXD5, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SRXD5__SRXD5 IOMUX_MODE(MX31_PIN_SRXD5, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SCK5__SCK5 IOMUX_MODE(MX31_PIN_SCK5, IOMUX_CONFIG_FUNC) +#define MX31_PIN_SFS5__SFS5 IOMUX_MODE(MX31_PIN_SFS5, IOMUX_CONFIG_FUNC)
/*XXX: The SS0, SS1, SS2, SS3 lines of spi3 are multiplexed by cspi2_ss0, cspi2_ss1, cspi1_ss0 * cspi1_ss1*/
The current model does not allow to put a pad into different modes once a pins is allocated. Remove the resource handling.
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/plat-mxc/include/mach/iomux-v3.h | 17 +------------- arch/arm/plat-mxc/iomux-v3.c | 36 +--------------------------- 2 files changed, 3 insertions(+), 50 deletions(-)
diff --git a/arch/arm/plat-mxc/include/mach/iomux-v3.h b/arch/arm/plat-mxc/include/mach/iomux-v3.h index a0fa402..1deda01 100644 --- a/arch/arm/plat-mxc/include/mach/iomux-v3.h +++ b/arch/arm/plat-mxc/include/mach/iomux-v3.h @@ -88,9 +88,7 @@ struct pad_desc { #define PAD_CTL_SRE_FAST (1 << 0)
/* - * setups a single pad: - * - reserves the pad so that it is not claimed by another driver - * - setups the iomux according to the configuration + * setups a single pad in the iomuxer */ int mxc_iomux_v3_setup_pad(struct pad_desc *pad);
@@ -101,19 +99,6 @@ int mxc_iomux_v3_setup_pad(struct pad_desc *pad); int mxc_iomux_v3_setup_multiple_pads(struct pad_desc *pad_list, unsigned count);
/* - * releases a single pad: - * - make it available for a future use by another driver - * - DOES NOT reconfigure the IOMUX in its reset state - */ -void mxc_iomux_v3_release_pad(struct pad_desc *pad); - -/* - * releases multiple pads - * convenvient way to call the above function with tables - */ -void mxc_iomux_v3_release_multiple_pads(struct pad_desc *pad_list, int count); - -/* * Initialise the iomux controller */ void mxc_iomux_v3_init(void __iomem *iomux_v3_base); diff --git a/arch/arm/plat-mxc/iomux-v3.c b/arch/arm/plat-mxc/iomux-v3.c index 851ca99..b318c6a 100644 --- a/arch/arm/plat-mxc/iomux-v3.c +++ b/arch/arm/plat-mxc/iomux-v3.c @@ -31,19 +31,11 @@
static void __iomem *base;
-static unsigned long iomux_v3_pad_alloc_map[0x200 / BITS_PER_LONG]; - /* - * setups a single pin: - * - reserves the pin so that it is not claimed by another driver - * - setups the iomux according to the configuration + * setups a single pad in the iomuxer */ int mxc_iomux_v3_setup_pad(struct pad_desc *pad) { - unsigned int pad_ofs = pad->pad_ctrl_ofs; - - if (test_and_set_bit(pad_ofs >> 2, iomux_v3_pad_alloc_map)) - return -EBUSY; if (pad->mux_ctrl_ofs) __raw_writel(pad->mux_mode, base + pad->mux_ctrl_ofs);
@@ -66,37 +58,13 @@ int mxc_iomux_v3_setup_multiple_pads(struct pad_desc *pad_list, unsigned count) for (i = 0; i < count; i++) { ret = mxc_iomux_v3_setup_pad(p); if (ret) - goto setup_error; + return ret; p++; } return 0; - -setup_error: - mxc_iomux_v3_release_multiple_pads(pad_list, i); - return ret; } EXPORT_SYMBOL(mxc_iomux_v3_setup_multiple_pads);
-void mxc_iomux_v3_release_pad(struct pad_desc *pad) -{ - unsigned int pad_ofs = pad->pad_ctrl_ofs; - - clear_bit(pad_ofs >> 2, iomux_v3_pad_alloc_map); -} -EXPORT_SYMBOL(mxc_iomux_v3_release_pad); - -void mxc_iomux_v3_release_multiple_pads(struct pad_desc *pad_list, int count) -{ - struct pad_desc *p = pad_list; - int i; - - for (i = 0; i < count; i++) { - mxc_iomux_v3_release_pad(p); - p++; - } -} -EXPORT_SYMBOL(mxc_iomux_v3_release_multiple_pads); - void mxc_iomux_v3_init(void __iomem *iomux_v3_base) { base = iomux_v3_base;
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/mc13783.c | 739 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/mc13783.h | 29 ++ 4 files changed, 773 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/mc13783.c create mode 100644 sound/soc/codecs/mc13783.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0edca93..bed402e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -114,6 +114,9 @@ config SND_SOC_CX20442 config SND_SOC_L3 tristate
+config SND_SOC_MC13783 + tristate + config SND_SOC_PCM3008 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fb4af28..d524e90 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-l3-objs := l3.o +snd-soc-mc13783-objs := mc13783.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o @@ -59,6 +60,7 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c new file mode 100644 index 0000000..278ee65 --- /dev/null +++ b/sound/soc/codecs/mc13783.c @@ -0,0 +1,739 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * Copyright 2009 Sascha Hauer, s.hauer@pengutronix.de + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include <linux/device.h> +#include <linux/mfd/mc13783-private.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> + +#include "mc13783.h" + +#define PMIC_AUDIO_RX_0 36 +#define PMIC_AUDIO_RX_1 37 +#define PMIC_AUDIO_TX 38 +#define PMIC_SSI_NETWORK 39 +#define PMIC_AUDIO_CODEC 40 +#define PMIC_AUDIO_DAC 41 + +#define SSI_NETWORK_CDCTXRXSLOT(x) (((x) & 0x3) << 2) +#define SSI_NETWORK_CDCTXSECSLOT(x) (((x) & 0x3) << 4) +#define SSI_NETWORK_CDCRXSECSLOT(x) (((x) & 0x3) << 6) +#define SSI_NETWORK_CDCRXSECGAIN(x) (((x) & 0x3) << 8) +#define SSI_NETWORK_CDCSUMGAIN(x) (1 << 10) +#define SSI_NETWORK_CDCFSDLY(x) (1 << 11) +#define SSI_NETWORK_STDCSLOT(x) (((x) & 0x3) << 12) +#define SSI_NETWORK_STDCRXSLOT(x) (((x) & 0x3) << 14) +#define SSI_NETWORK_STDCRXSECSLOT(x) (((x) & 0x3) << 16) +#define SSI_NETWORK_STDCRXSECGAIN(x) (((x) & 0x3) << 18) +#define SSI_NETWORK_STDCSUMGAIN (1 << 20) + +#define STEREO_DAC_STD_SSI_SEL (1 << 0) +#define STEREO_DAC_STD_CLK_SEL (1 << 1) +#define STEREO_DAC_STD_CSM (1 << 2) +#define STEREO_DAC_STD_BCL_INV (1 << 3) +#define STEREO_DAC_STD_CFS_INV (1 << 4) +#define STEREO_DAC_STD_CFS(x) (((x) & 0x3) << 5) +#define STEREO_DAC_STD_CLK(x) (((x) & 0x7) << 7) +#define STEREO_DAC_STD_CFS_DLY_B (1 << 10) +#define STEREO_DAC_STD_C_EN (1 << 11) +#define STEREO_DAC_STD_C_CLK_EN (1 << 12) +#define STEREO_DAC_STD_C_RESET (1 << 15) +#define STEREO_DAC_SPDIF (1 << 16) +#define STEREO_DAC_SR(x) (((x) & 0xf) << 17) + +static struct mc13783 *mc13783; + +struct mc13783_priv { + struct snd_soc_codec codec; + + u32 reg_cache[42]; +}; + +static unsigned int mc13783_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct mc13783_priv *priv = codec->private_data; + + return priv->reg_cache[reg]; +} + +static int mc13783_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct mc13783_priv *priv = codec->private_data; + + priv->reg_cache[reg] = value; + + mc13783_reg_write(mc13783, reg, value); + + return 0; +} + +/* sample rates supported by PMIC for stereo playback operations on StDac. */ +static unsigned int mc13783_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000,44100, + 48000, 64000, 96000 +}; + +static int mc13783_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + unsigned int rate = params_rate(params); + int i; + unsigned int reg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < ARRAY_SIZE(mc13783_rates); i++) { + if (rate == mc13783_rates[i]) { + reg = mc13783_read(codec, PMIC_AUDIO_DAC); + /* setup the clock speed */ + reg &= ~(0xf << 17); + reg |= i << 17; + mc13783_write(codec, PMIC_AUDIO_DAC, reg); + return 0; + } + } + } else { + reg = mc13783_read(codec, PMIC_AUDIO_CODEC); + + if (rate == 8000) + reg &= ~(1 << 10); + else + reg |= (1 << 10); + + mc13783_write(codec, PMIC_AUDIO_CODEC, reg); + + return 0; + } + + return -EINVAL; +} + +static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int reg; + + if (dai->id == 1) + reg = mc13783_read(codec, PMIC_AUDIO_DAC); + else + reg = mc13783_read(codec, PMIC_AUDIO_CODEC); + + reg &= ~STEREO_DAC_STD_CFS(3); + reg &= ~STEREO_DAC_STD_BCL_INV; + reg &= ~STEREO_DAC_STD_CFS_INV; + reg &= ~STEREO_DAC_STD_CSM; + reg &= ~STEREO_DAC_STD_C_CLK_EN; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg |= STEREO_DAC_STD_CFS(2); + break; + case SND_SOC_DAIFMT_DSP_A: + reg |= STEREO_DAC_STD_CFS(1); + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + reg |= STEREO_DAC_STD_BCL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + reg |= STEREO_DAC_STD_BCL_INV; + reg |= STEREO_DAC_STD_CFS_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + reg |= STEREO_DAC_STD_CFS_INV; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + reg |= STEREO_DAC_STD_C_CLK_EN; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg |= STEREO_DAC_STD_CSM; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + return -EINVAL; + } + + reg |= STEREO_DAC_STD_C_EN; /* DAC power up */ + reg |= STEREO_DAC_STD_C_RESET; /* reset filter */ + + if (dai->id == 1) + mc13783_write(codec, PMIC_AUDIO_DAC, reg); + else + mc13783_write(codec, PMIC_AUDIO_CODEC, reg); + + return 0; +} + +static int mc13783_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + int clk; + unsigned int reg; + + if (dai->id == 1) + reg = mc13783_read(codec, PMIC_AUDIO_DAC); + else + reg = mc13783_read(codec, PMIC_AUDIO_CODEC); + + reg &= ~STEREO_DAC_STD_CLK(0x7); + reg &= ~STEREO_DAC_STD_CLK_SEL; + + switch (freq) { + case 13000000: + clk = 0; + break; + case 15360000: + clk = 1; + break; + case 16800000: + clk = 2; + break; + case 26000000: + clk = 4; + break; + case 12000000: + clk = 5; + break; + case 3686400: + clk = 6; + break; + case 33600000: + clk = 7; + break; + default: + return -EINVAL; + } + + if (clk_id == MC13783_CLK_CLIB) + reg |= STEREO_DAC_STD_CLK_SEL; + + reg |= STEREO_DAC_STD_CLK(clk); + + if (dai->id == 1) + mc13783_write(codec, PMIC_AUDIO_DAC, reg); + else + mc13783_write(codec, PMIC_AUDIO_CODEC, reg); + + return 0; +} + +static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int reg; + + if (slots != 4) + return -EINVAL; + + reg = mc13783_read(codec, PMIC_SSI_NETWORK); + + reg &= ~(0xfff << 0); + reg |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */ + reg |= (0x01 << 4); /* secondary timeslot TX is 1 */ + reg |= (0x01 << 6); /* secondary timeslot RX is 1 */ + + mc13783_write(codec, PMIC_SSI_NETWORK, reg); + + return 0; +} + +static int mc13783_asp_val; + +static int mc13783_get_asp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mc13783_asp_val; + return 0; +} + +static int mc13783_put_asp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg; + + mc13783_asp_val = ucontrol->value.integer.value[0]; + + reg = mc13783_read(codec, PMIC_AUDIO_RX_0); + + reg &= ~(0x3 << 3); + reg |= mc13783_asp_val << 3; + + mc13783_write(codec, PMIC_AUDIO_RX_0, reg); + + return 0; +} + +static int mc13783_alsp_val; + +static int mc13783_get_alsp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mc13783_alsp_val; + return 0; +} + +static int mc13783_put_alsp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg; + mc13783_alsp_val = ucontrol->value.integer.value[0]; + + reg = mc13783_read(codec, PMIC_AUDIO_RX_0); + + reg &= ~((1 << 5) | (1 << 7)); + + if (mc13783_alsp_val) + reg |= 1 << 5; + + if (mc13783_alsp_val == 2) + reg |= 1 << 7; + + mc13783_write(codec, PMIC_AUDIO_RX_0, reg); + + return 0; +} + +static int mc13783_pcm_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int val; + + val = mc13783_read(codec, PMIC_AUDIO_RX_0); + ucontrol->value.enumerated.item[0] = (val >> 22) & 1; + + return 0; +} + +static int mc13783_pcm_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r36, r37; + + r36 = mc13783_read(codec, PMIC_AUDIO_RX_0); + r37 = mc13783_read(codec, PMIC_AUDIO_RX_1); + + r36 &= ~(1 << 22); + r37 &= ~(1 << 5); + + if (ucontrol->value.enumerated.item[0]) { + r36 |= (1 << 22); + r37 |= (1 << 5); + } else { + r36 &= ~(1 << 22); + r37 &= ~(1 << 5); + } + + mc13783_write(codec, PMIC_AUDIO_RX_0, r36); + mc13783_write(codec, PMIC_AUDIO_RX_1, r37); + + return 0; +} + +static int mc13783_linein_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int val; + + val = mc13783_read(codec, PMIC_AUDIO_RX_0); + ucontrol->value.enumerated.item[0] = (val >> 23) & 1; + + return 0; +} + +static int mc13783_linein_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r36, r37; + + r36 = mc13783_read(codec, PMIC_AUDIO_RX_0); + r37 = mc13783_read(codec, PMIC_AUDIO_RX_1); + + r36 &= ~(1 << 23); + r37 &= ~(1 << 10); + + if (ucontrol->value.enumerated.item[0]) { + r36 |= (1 << 23); + r37 |= (1 << 10); + } else { + r36 &= ~(1 << 23); + r37 &= ~(1 << 10); + } + + mc13783_write(codec, PMIC_AUDIO_RX_0, r36); + mc13783_write(codec, PMIC_AUDIO_RX_1, r37); + + return 0; +} + +static int mc13783_capure_cache; + +static int mc13783_get_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mc13783_capure_cache; + return 0; +} + +#define AMC1REN (1 << 5) +#define AMC2EN (1 << 9) +#define ATXINEN (1 << 11) +#define RXINREC (1 << 13) +#define AMC1LEN (1 << 7) + +static int mc13783_put_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r38, change; + + r38 = mc13783_read(codec, PMIC_AUDIO_TX); + + change = (mc13783_capure_cache != ucontrol->value.enumerated.item[0]); + mc13783_capure_cache = ucontrol->value.enumerated.item[0]; + r38 &= ~(AMC1REN | AMC2EN | ATXINEN | RXINREC | AMC1LEN); + + switch (mc13783_capure_cache) { + case 0: + break; + case 1: + r38 |= RXINREC; + break; + case 2: + r38 |= AMC1REN | AMC1LEN; + break; + case 3: + r38 |= AMC1REN; + break; + case 4: + r38 |= AMC2EN; + break; + case 5: + r38 |= AMC1LEN | AMC2EN; + break; + case 6: + r38 |= ATXINEN; + break; + case 7: + r38 |= AMC1LEN | ATXINEN; + break; + case 8: + r38 |= AMC1LEN | RXINREC; + break; + case 9: + r38 |= AMC1LEN; + break; + default: + break; + } + + mc13783_write(codec, PMIC_AUDIO_TX, r38); + + return change; +} + +static const char *mc13783_asp[] = {"Off", "Codec", "Right"}; +static const char *mc13783_alsp[] = {"Off", "Codec", "Right"}; + +static const char *mc13783_ahs[] = {"Codec", "Mixer"}; + +static const char *mc13783_arxout[] = {"Codec", "Mixer"}; + +static const char *mc13783_capture[] = {"off/off", "rxinl/rxinr", + "mc1lin/mc1rin", "off/mc1rin", "off/mc2in", "mc1lin/mc2in", + "off/txin", "mc1lin/txin", "mc1lin/rxinr", "mc1lin/off"}; + +static const char *mc13783_3d_mixer[] = {"Stereo", "Phase Mix", "Mono", "Mono Mix"}; + +static const struct soc_enum mc13783_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_asp), mc13783_asp), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_alsp), mc13783_alsp), + + SOC_ENUM_SINGLE(PMIC_AUDIO_RX_0, 11, ARRAY_SIZE(mc13783_ahs), mc13783_ahs), + SOC_ENUM_SINGLE(PMIC_AUDIO_RX_0, 17, ARRAY_SIZE(mc13783_arxout), mc13783_arxout), + + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_capture), mc13783_capture), + SOC_ENUM_SINGLE(PMIC_AUDIO_RX_1, 16, ARRAY_SIZE(mc13783_3d_mixer), mc13783_3d_mixer), +}; + +static struct snd_kcontrol_new mc13783_control_list[] = { + /* Output Routing */ + SOC_ENUM_EXT("Asp Source", mc13783_enum[0], mc13783_get_asp, mc13783_put_asp), + SOC_ENUM_EXT("Alsp Source", mc13783_enum[1], mc13783_get_alsp, mc13783_put_alsp), + SOC_ENUM("Ahs Source", mc13783_enum[2]), + SOC_SINGLE("Ahsr enable", PMIC_AUDIO_RX_0, 9, 1, 0), + SOC_SINGLE("Ahsl enable", PMIC_AUDIO_RX_0, 10, 1, 0), + SOC_ENUM("Arxout Source", mc13783_enum[3]), + SOC_SINGLE("ArxoutR enable", PMIC_AUDIO_RX_0, 16, 1, 0), + SOC_SINGLE("ArxoutL enable", PMIC_AUDIO_RX_0, 15, 1, 0), + SOC_SINGLE_EXT("PCM Playback Switch", 0, 0, 1, 0, mc13783_pcm_get, mc13783_pcm_put), + SOC_SINGLE("PCM Playback Volume", PMIC_AUDIO_RX_1, 6, 15, 0), + SOC_SINGLE_EXT("Line in Switch", 0, 0, 1, 0, mc13783_linein_get, mc13783_linein_put), + SOC_SINGLE("Line in Volume", PMIC_AUDIO_RX_1, 12, 15, 0), + SOC_ENUM_EXT("Capture Source", mc13783_enum[4], mc13783_get_capture, mc13783_put_capture), + SOC_DOUBLE("PCM Capture Volume", PMIC_AUDIO_TX, 19, 14, 31, 0), + SOC_ENUM("3D Control - Switch", mc13783_enum[5]), +}; + +static struct snd_soc_codec *mc13783_codec; + +static int mc13783_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = mc13783_codec; + struct mc13783_priv *priv = codec->private_data; + int i, ret = 0; + + if (mc13783_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + /* these are the reset values */ + priv->reg_cache[PMIC_AUDIO_RX_0] = 0x001000; + priv->reg_cache[PMIC_AUDIO_RX_1] = 0x00d35A; + priv->reg_cache[PMIC_AUDIO_TX] = 0x420000; + priv->reg_cache[PMIC_SSI_NETWORK] = 0x013060; + priv->reg_cache[PMIC_AUDIO_CODEC] = 0x180027; + priv->reg_cache[PMIC_AUDIO_DAC] = 0x0e0004; + + /* VAUDIOON -> supply audio part, BIAS enable */ + priv->reg_cache[PMIC_AUDIO_RX_0] |= 0x3; + + for (i = 36; i < 42; i++) + mc13783_write(codec, i, priv->reg_cache[i]); + + socdev->card->codec = mc13783_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, mc13783_control_list, + ARRAY_SIZE(mc13783_control_list)); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int mc13783_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = mc13783_codec; + unsigned int reg; + + reg = mc13783_read(codec, PMIC_AUDIO_RX_0); + + /* VAUDIOON -> switch off audio part, BIAS disable */ + reg &= ~0x3; + + mc13783_write(codec, PMIC_AUDIO_RX_0, reg); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#define MC13783_RATES_PLAYBACK (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define MC13783_RATES_RECORD (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops mc13783_ops_dac = { + .hw_params = mc13783_pcm_hw_params, + .set_fmt = mc13783_set_fmt, + .set_sysclk = mc13783_set_sysclk, +}; + +static struct snd_soc_dai_ops mc13783_ops_voice_codec = { + .hw_params = mc13783_pcm_hw_params, + .set_fmt = mc13783_set_fmt, + .set_sysclk = mc13783_set_sysclk, + .set_tdm_slot = mc13783_set_tdm_slot_codec, +}; + +struct snd_soc_dai mc13783_dai[] = { + { + .name = "MC13783 Playback", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_PLAYBACK, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_dac, + }, { + .name = "MC13783 Capture", + .id = 2, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_voice_codec, + }, +}; +EXPORT_SYMBOL_GPL(mc13783_dai); + +struct snd_soc_codec_device soc_codec_dev_mc13783 = { + .probe = mc13783_probe, + .remove = mc13783_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_mc13783); + +/* + * OK, this stinks. We currently only can support one MC13783. + * Lets take it as an intermediate to turn this stuff into SoC + * Audio. + */ +static int mc13783_codec_probe(struct platform_device *pdev) +{ + struct mc13783_priv *priv; + struct snd_soc_codec *codec; + int ret; + + printk(KERN_INFO "MC13783 Audio Codec\n"); + + if (mc13783) + return -EBUSY; + + mc13783 = dev_get_drvdata(pdev->dev.parent); + + priv = kzalloc(sizeof(struct mc13783_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + codec = &priv->codec; + codec->private_data = priv; + mc13783_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "mc13783"; + codec->owner = THIS_MODULE; + codec->read = mc13783_read; + codec->write = mc13783_write; + codec->dai = mc13783_dai; + codec->num_dai = ARRAY_SIZE(mc13783_dai); + codec->control_data = priv; + codec->dev = &pdev->dev; + + mc13783_dai[0].dev = codec->dev; + mc13783_dai[1].dev = codec->dev; + + ret = snd_soc_register_dais(mc13783_dai, ARRAY_SIZE(mc13783_dai)); + if (ret) + goto out; + + ret = snd_soc_register_codec(codec); + if (ret) + goto out; + + return 0; +out: + printk("register codec failed with %d\n", ret); + kfree(priv); + return ret; +} + +static int mc13783_codec_remove(struct platform_device *pdev) +{ + mc13783 = NULL; + + return 0; +} + +static struct platform_driver mc13783_codec_driver = { + .driver = { + .name = "mc13783-codec", + .owner = THIS_MODULE, + }, + .probe = mc13783_codec_probe, + .remove = __devexit_p(mc13783_codec_remove), +}; + +static __init int mc13783_init(void) +{ + return platform_driver_register(&mc13783_codec_driver); +} + +static __exit void mc13783_exit(void) +{ + platform_driver_unregister(&mc13783_codec_driver); +} + +module_init(mc13783_init); +module_exit(mc13783_exit); + +MODULE_DESCRIPTION("ASoC MC13783 driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix s.hauer@pengutronix.de"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mc13783.h b/sound/soc/codecs/mc13783.h new file mode 100644 index 0000000..f0da0ee --- /dev/null +++ b/sound/soc/codecs/mc13783.h @@ -0,0 +1,29 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MC13783_MIXER_H +#define MC13783_MIXER_H + +extern int mc13783_add_ctl(struct snd_card*, void *); + +extern struct snd_soc_dai mc13783_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_mc13783; + +#define MC13783_CLK_CLIA 1 +#define MC13783_CLK_CLIB 2 + +#endif /* MC13783_MIXER_H */
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/mach-mx3/clock.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-mx3/clock.c b/arch/arm/mach-mx3/clock.c index b2a3bcf..3089786 100644 --- a/arch/arm/mach-mx3/clock.c +++ b/arch/arm/mach-mx3/clock.c @@ -558,8 +558,8 @@ static struct clk_lookup lookups[] = { _REGISTER_CLOCK("mxc_w1.0", NULL, owire_clk) _REGISTER_CLOCK("mxc-mmc.0", NULL, sdhc1_clk) _REGISTER_CLOCK("mxc-mmc.1", NULL, sdhc2_clk) - _REGISTER_CLOCK(NULL, "ssi", ssi1_clk) - _REGISTER_CLOCK(NULL, "ssi", ssi2_clk) + _REGISTER_CLOCK("imx-ssi.0", NULL, ssi1_clk) + _REGISTER_CLOCK("imx-ssi.1", NULL, ssi2_clk) _REGISTER_CLOCK(NULL, "firi", firi_clk) _REGISTER_CLOCK(NULL, "ata", ata_clk) _REGISTER_CLOCK(NULL, "rtic", rtic_clk)
This is useful for audio where we do not want to setup a new scatterlist after playing 4GB of audio data. This would cause skips in the playback.
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/plat-mxc/dma-mx1-mx2.c | 3 ++- arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletions(-)
diff --git a/arch/arm/plat-mxc/dma-mx1-mx2.c b/arch/arm/plat-mxc/dma-mx1-mx2.c index 7764643..9c1b3f9 100644 --- a/arch/arm/plat-mxc/dma-mx1-mx2.c +++ b/arch/arm/plat-mxc/dma-mx1-mx2.c @@ -156,7 +156,8 @@ static inline int imx_dma_sg_next(int channel, struct scatterlist *sg) }
now = min(imxdma->resbytes, sg->length); - imxdma->resbytes -= now; + if (imxdma->resbytes != IMX_DMA_LENGTH_LOOP) + imxdma->resbytes -= now;
if ((imxdma->dma_mode & DMA_MODE_MASK) == DMA_MODE_READ) __raw_writel(sg->dma_address, DMA_BASE + DMA_DAR(channel)); diff --git a/arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h b/arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h index b3876cc..07be8ad 100644 --- a/arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h +++ b/arch/arm/plat-mxc/include/mach/dma-mx1-mx2.h @@ -58,6 +58,14 @@ imx_dma_setup_single(int channel, dma_addr_t dma_address, unsigned int dma_length, unsigned int dev_addr, unsigned int dmamode);
+ +/* + * Use this flag as the dma_length argument to imx_dma_setup_sg() + * to create an endless running dma loop. The end of the scatterlist + * must be linked to the beginning for this to work. + */ +#define IMX_DMA_LENGTH_LOOP ((unsigned int)-1) + int imx_dma_setup_sg(int channel, struct scatterlist *sg, unsigned int sgcount, unsigned int dma_length,
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/plat-mxc/Makefile | 1 + arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/ssi-fiq.S | 134 ++++ sound/soc/imx/devdma.c | 80 ++ sound/soc/imx/devdma.h | 3 + sound/soc/imx/imx-ssi.c | 1349 ++++++++++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 24 + 7 files changed, 1608 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-mxc/include/mach/ssi.h create mode 100644 arch/arm/plat-mxc/ssi-fiq.S create mode 100644 sound/soc/imx/devdma.c create mode 100644 sound/soc/imx/devdma.h create mode 100644 sound/soc/imx/imx-ssi.c create mode 100644 sound/soc/imx/imx-ssi.h
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index 4cbca9d..1f013ea 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_MXC_PWM) += pwm.o obj-$(CONFIG_MXC_ULPI) += ulpi.o obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.o +obj-$(CONFIG_SND_IMX_SOC) += ssi-fiq.o diff --git a/arch/arm/plat-mxc/include/mach/ssi.h b/arch/arm/plat-mxc/include/mach/ssi.h new file mode 100644 index 0000000..144a2ac --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/ssi.h @@ -0,0 +1,17 @@ +#ifndef __MACH_SSI_H +#define __MACH_SSI_H + +struct snd_ac97; + +extern unsigned char imx_ssi_fiq_start, imx_ssi_fiq_end; +extern unsigned long imx_ssi_fiq_base, imx_ssi_fiq_tx_buffer, imx_ssi_fiq_rx_buffer; + +struct imx_ssi_platform_data { + unsigned int flags; +#define IMX_SSI_DMA (1 << 0) +#define IMX_SSI_USE_AC97 (1 << 1) + void (*ac97_reset) (struct snd_ac97 *ac97); + void (*ac97_warm_reset)(struct snd_ac97 *ac97); +}; + +#endif /* __MACH_SSI_H */ diff --git a/arch/arm/plat-mxc/ssi-fiq.S b/arch/arm/plat-mxc/ssi-fiq.S new file mode 100644 index 0000000..4ddce56 --- /dev/null +++ b/arch/arm/plat-mxc/ssi-fiq.S @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009 Sascha Hauer s.hauer@pengutronix.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> + +/* + * r8 = bit 0-15: tx offset, bit 16-31: tx buffer size + * r9 = bit 0-15: rx offset, bit 16-31: rx buffer size + */ + +#define SSI_STX0 0x00 +#define SSI_SRX0 0x08 +#define SSI_SISR 0x14 +#define SSI_SIER 0x18 +#define SSI_SACNT 0x38 + +#define SSI_SACNT_AC97EN (1 << 0) + +#define SSI_SIER_TFE0_EN (1 << 0) +#define SSI_SISR_TFE0 (1 << 0) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SIER_RFF0_EN (1 << 2) + + .text + .global imx_ssi_fiq_start + .global imx_ssi_fiq_end + .global imx_ssi_fiq_base + .global imx_ssi_fiq_rx_buffer + .global imx_ssi_fiq_tx_buffer + +imx_ssi_fiq_start: + ldr r12, imx_ssi_fiq_base + + /* TX */ + ldr r11, imx_ssi_fiq_tx_buffer + + /* shall we send? */ + ldr r13, [r12, #SSI_SIER] + tst r13, #SSI_SIER_TFE0_EN + beq 1f + + /* TX FIFO empty? */ + ldr r13, [r12, #SSI_SISR] + tst r13, #SSI_SISR_TFE0 + beq 1f + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r8 /* r10: current buffer offset */ + + add r11, r11, r10 + + ldrh r13, [r11] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #2] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #4] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #6] + strh r13, [r12, #SSI_STX0] + + add r10, #8 + lsr r13, r8, #16 /* r13: buffer size */ + cmp r10, r13 + lslgt r8, r13, #16 + addle r8, #8 +1: + /* RX */ + + /* shall we receive? */ + ldr r13, [r12, #SSI_SIER] + tst r13, #SSI_SIER_RFF0_EN + beq 1f + + /* RX FIFO full? */ + ldr r13, [r12, #SSI_SISR] + tst r13, #SSI_SISR_RFF0 + beq 1f + + ldr r11, imx_ssi_fiq_rx_buffer + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r9 /* r10: current buffer offset */ + + add r11, r11, r10 + + ldr r13, [r12, #SSI_SACNT] + tst r13, #SSI_SACNT_AC97EN + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #2] + + /* dummy read to skip slot 12 */ + ldrne r13, [r12, #SSI_SRX0] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #4] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #6] + + /* dummy read to skip slot 12 */ + ldrne r13, [r12, #SSI_SRX0] + + add r10, #8 + lsr r13, r9, #16 /* r13: buffer size */ + cmp r10, r13 + lslgt r9, r13, #16 + addle r9, #8 + +1: + @ return from FIQ + subs pc, lr, #4 +imx_ssi_fiq_base: + .word 0x0 +imx_ssi_fiq_rx_buffer: + .word 0x0 +imx_ssi_fiq_tx_buffer: + .word 0x0 +imx_ssi_fiq_end: + diff --git a/sound/soc/imx/devdma.c b/sound/soc/imx/devdma.c new file mode 100644 index 0000000..9d1e666 --- /dev/null +++ b/sound/soc/imx/devdma.c @@ -0,0 +1,80 @@ +/* + * linux/sound/arm/devdma.c + * + * Copyright (C) 2003-2004 Russell King, All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ARM DMA shim for ALSA. + */ +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> + +#include "devdma.h" + +void devdma_hw_free(struct device *dev, struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + + if (runtime->dma_area == NULL) + return; + + if (buf != &substream->dma_buffer) { + dma_free_coherent(buf->dev.dev, buf->bytes, buf->area, buf->addr); + kfree(runtime->dma_buffer_p); + } + + snd_pcm_set_runtime_buffer(substream, NULL); +} + +int devdma_hw_alloc(struct device *dev, struct snd_pcm_substream *substream, size_t size) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *buf = runtime->dma_buffer_p; + int ret = 0; + + if (buf) { + if (buf->bytes >= size) + goto out; + devdma_hw_free(dev, substream); + } + + if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) { + buf = &substream->dma_buffer; + } else { + buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL); + if (!buf) + goto nomem; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = dev; + buf->area = dma_alloc_coherent(dev, size, &buf->addr, GFP_KERNEL); + buf->bytes = size; + buf->private_data = NULL; + + if (!buf->area) + goto free; + } + snd_pcm_set_runtime_buffer(substream, buf); + ret = 1; + out: + runtime->dma_bytes = size; + return ret; + + free: + kfree(buf); + nomem: + return -ENOMEM; +} + +int devdma_mmap(struct device *dev, struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_coherent(dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} diff --git a/sound/soc/imx/devdma.h b/sound/soc/imx/devdma.h new file mode 100644 index 0000000..d025329 --- /dev/null +++ b/sound/soc/imx/devdma.h @@ -0,0 +1,3 @@ +void devdma_hw_free(struct device *dev, struct snd_pcm_substream *substream); +int devdma_hw_alloc(struct device *dev, struct snd_pcm_substream *substream, size_t size); +int devdma_mmap(struct device *dev, struct snd_pcm_substream *substream, struct vm_area_struct *vma); diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c new file mode 100644 index 0000000..225379d --- /dev/null +++ b/sound/soc/imx/imx-ssi.c @@ -0,0 +1,1349 @@ +/* + * imx-ssi.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer s.hauer@pengutronix.de + * + * 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. + * + * + * The i.MX SSI core has some nasty limitations in AC97 mode. While most + * sane processor vendors have a FIFO per AC97 slot, the i.MX has only + * one FIFO which combines all valid receive slots. We cannot even select + * which slots we want to receive. The WM9712 with which this driver + * was developped with always sends GPIO status data in slot 12 which + * we receive in our (PCM-) data stream. The only chance we have is to + * manually skip this data in the FIQ handler. With sampling rates different + * from 48000Hz not every frame has valid receive data, so the ratio + * between pcm data and GPIO status data changes. Our FIQ handler is not + * able to handle this, hence this driver only works with 48000Hz sampling + * rate. + * Reading and writing AC97 registers is another challange. The core + * provides us status bits when the read register is updated with *another* + * value. When we read the same register two times (and the register still + * contains the same value) these status bits are not set. We work + * around this by not polling these bits but only wait a fixed delay. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/fiq.h> + +#include <mach/dma-mx1-mx2.h> +#include <mach/ssi.h> + +#include "devdma.h" +#include "imx-ssi.h" +#include "../codecs/ac97.h" + +#define DRV_NAME "imx-ssi" + +#define DMA_RXFIFO_BURST 0x4 +#define DMA_TXFIFO_BURST 0x6 + +#ifdef CONFIG_MACH_MX27 +#define IMX_SSI_MX1_MX2_DMA +#define use_dma(ssi) (ssi->flags & IMX_SSI_DMA) +#else +#define use_dma(ssi) 0 +#endif + +#define SSI_STX0 0x00 +#define SSI_STX1 0x04 +#define SSI_SRX0 0x08 +#define SSI_SRX1 0x0c + +#define SSI_SCR 0x10 +#define SSI_SCR_CLK_IST (1 << 9) +#define SSI_SCR_CLK_IST_SHIFT 9 +#define SSI_SCR_TCH_EN (1 << 8) +#define SSI_SCR_SYS_CLK_EN (1 << 7) +#define SSI_SCR_I2S_MODE_NORM (0 << 5) +#define SSI_SCR_I2S_MODE_MSTR (1 << 5) +#define SSI_SCR_I2S_MODE_SLAVE (2 << 5) +#define SSI_I2S_MODE_MASK (3 << 5) +#define SSI_SCR_SYN (1 << 4) +#define SSI_SCR_NET (1 << 3) +#define SSI_SCR_RE (1 << 2) +#define SSI_SCR_TE (1 << 1) +#define SSI_SCR_SSIEN (1 << 0) + +#define SSI_SISR 0x14 +#define SSI_SISR_MASK ((1 << 19) - 1) +#define SSI_SISR_CMDAU (1 << 18) +#define SSI_SISR_CMDDU (1 << 17) +#define SSI_SISR_RXT (1 << 16) +#define SSI_SISR_RDR1 (1 << 15) +#define SSI_SISR_RDR0 (1 << 14) +#define SSI_SISR_TDE1 (1 << 13) +#define SSI_SISR_TDE0 (1 << 12) +#define SSI_SISR_ROE1 (1 << 11) +#define SSI_SISR_ROE0 (1 << 10) +#define SSI_SISR_TUE1 (1 << 9) +#define SSI_SISR_TUE0 (1 << 8) +#define SSI_SISR_TFS (1 << 7) +#define SSI_SISR_RFS (1 << 6) +#define SSI_SISR_TLS (1 << 5) +#define SSI_SISR_RLS (1 << 4) +#define SSI_SISR_RFF1 (1 << 3) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SISR_TFE1 (1 << 1) +#define SSI_SISR_TFE0 (1 << 0) + +#define SSI_SIER 0x18 +#define SSI_SIER_RDMAE (1 << 22) +#define SSI_SIER_RIE (1 << 21) +#define SSI_SIER_TDMAE (1 << 20) +#define SSI_SIER_TIE (1 << 19) +#define SSI_SIER_CMDAU_EN (1 << 18) +#define SSI_SIER_CMDDU_EN (1 << 17) +#define SSI_SIER_RXT_EN (1 << 16) +#define SSI_SIER_RDR1_EN (1 << 15) +#define SSI_SIER_RDR0_EN (1 << 14) +#define SSI_SIER_TDE1_EN (1 << 13) +#define SSI_SIER_TDE0_EN (1 << 12) +#define SSI_SIER_ROE1_EN (1 << 11) +#define SSI_SIER_ROE0_EN (1 << 10) +#define SSI_SIER_TUE1_EN (1 << 9) +#define SSI_SIER_TUE0_EN (1 << 8) +#define SSI_SIER_TFS_EN (1 << 7) +#define SSI_SIER_RFS_EN (1 << 6) +#define SSI_SIER_TLS_EN (1 << 5) +#define SSI_SIER_RLS_EN (1 << 4) +#define SSI_SIER_RFF1_EN (1 << 3) +#define SSI_SIER_RFF0_EN (1 << 2) +#define SSI_SIER_TFE1_EN (1 << 1) +#define SSI_SIER_TFE0_EN (1 << 0) + +#define SSI_STCR 0x1c +#define SSI_STCR_TXBIT0 (1 << 9) +#define SSI_STCR_TFEN1 (1 << 8) +#define SSI_STCR_TFEN0 (1 << 7) +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_STCR_TFDIR (1 << 6) +#define SSI_STCR_TXDIR (1 << 5) +#define SSI_STCR_TSHFD (1 << 4) +#define SSI_STCR_TSCKP (1 << 3) +#define SSI_STCR_TFSI (1 << 2) +#define SSI_STCR_TFSL (1 << 1) +#define SSI_STCR_TEFS (1 << 0) + +#define SSI_SRCR 0x20 +#define SSI_SRCR_RXBIT0 (1 << 9) +#define SSI_SRCR_RFEN1 (1 << 8) +#define SSI_SRCR_RFEN0 (1 << 7) +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_SRCR_RFDIR (1 << 6) +#define SSI_SRCR_RXDIR (1 << 5) +#define SSI_SRCR_RSHFD (1 << 4) +#define SSI_SRCR_RSCKP (1 << 3) +#define SSI_SRCR_RFSI (1 << 2) +#define SSI_SRCR_RFSL (1 << 1) +#define SSI_SRCR_REFS (1 << 0) + +#define SSI_SRCCR 0x28 +#define SSI_SRCCR_DIV2 (1 << 18) +#define SSI_SRCCR_PSR (1 << 17) +#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_SRCCR_WL_MASK (0xf << 13) +#define SSI_SRCCR_DC_MASK (0x1f << 8) +#define SSI_SRCCR_PM_MASK (0xff << 0) + +#define SSI_STCCR 0x24 +#define SSI_STCCR_DIV2 (1 << 18) +#define SSI_STCCR_PSR (1 << 17) +#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_STCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_STCCR_WL_MASK (0xf << 13) +#define SSI_STCCR_DC_MASK (0x1f << 8) +#define SSI_STCCR_PM_MASK (0xff << 0) + +#define SSI_SFCSR 0x2c +#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) +#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) +#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) +#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) +#define SSI_SFCSR_RFWM0_MASK (0xf << 4) +#define SSI_SFCSR_TFWM0_MASK (0xf << 0) + +#define SSI_STR 0x30 +#define SSI_STR_TEST (1 << 15) +#define SSI_STR_RCK2TCK (1 << 14) +#define SSI_STR_RFS2TFS (1 << 13) +#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) +#define SSI_STR_TXD2RXD (1 << 7) +#define SSI_STR_TCK2RCK (1 << 6) +#define SSI_STR_TFS2RFS (1 << 5) +#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) + +#define SSI_SOR 0x34 +#define SSI_SOR_CLKOFF (1 << 6) +#define SSI_SOR_RX_CLR (1 << 5) +#define SSI_SOR_TX_CLR (1 << 4) +#define SSI_SOR_INIT (1 << 3) +#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) +#define SSI_SOR_WAIT_MASK (0x3 << 1) +#define SSI_SOR_SYNRST (1 << 0) + +#define SSI_SACNT 0x38 +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR (1 << 4) +#define SSI_SACNT_RD (1 << 3) +#define SSI_SACNT_TIF (1 << 2) +#define SSI_SACNT_FV (1 << 1) +#define SSI_SACNT_AC97EN (1 << 0) + +#define SSI_SACADD 0x3c +#define SSI_SACDAT 0x40 +#define SSI_SATAG 0x44 +#define SSI_STMSK 0x48 +#define SSI_SRMSK 0x4c +#define SSI_SACCST 0x50 +#define SSI_SACCEN 0x54 +#define SSI_SACCDIS 0x58 + +#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) + +struct imx_channel { + int sg_count; + struct scatterlist *sg_list; + int period; + int periods; + unsigned long dma_addr; + int dma; + struct snd_pcm_substream *substream; + unsigned long offset; + unsigned long size; + unsigned long period_cnt; + void *buf; + struct timer_list timer; + int period_time; +}; + +struct imx_ssi { + struct snd_soc_dai dai; + struct platform_device *ac97_dev; + + struct snd_soc_device imx_ac97; + struct clk *clk; + void __iomem *base; + int irq; + int fiq_enable; + unsigned int offset; + + struct imx_channel rxc, txc; + unsigned int flags; + + void (*ac97_reset) (struct snd_ac97 *ac97); + void (*ac97_warm_reset)(struct snd_ac97 *ac97); +}; + +/* + * SSI Network Mode or TDM slots configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + u32 sccr; + + sccr = readl(ssi->base + SSI_STCCR); + sccr &= ~SSI_STCCR_DC_MASK; + sccr |= SSI_STCCR_DC(slots - 1); + writel(sccr, ssi->base + SSI_STCCR); + + sccr = readl(ssi->base + SSI_SRCCR); + sccr &= ~SSI_STCCR_DC_MASK; + sccr |= SSI_STCCR_DC(slots - 1); + writel(sccr, ssi->base + SSI_SRCCR); + + writel(tx_mask, ssi->base + SSI_STMSK); + writel(rx_mask, ssi->base + SSI_SRMSK); + + return 0; +} + +/* + * SSI DAI format configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + * Note: We don't use the I2S modes but instead manually configure the + * SSI for I2S. + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + u32 strcr = 0, scr; + + scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; + scr |= SSI_SCR_NET; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data on rising edge of bclk, frame high with data */ + strcr |= SSI_STCR_TXBIT0; + break; + case SND_SOC_DAIFMT_DSP_B: + /* data on rising edge of bclk, frame high with data */ + strcr |= SSI_STCR_TFSL; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + strcr |= SSI_STCR_TFSI; + strcr &= ~SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_IB_NF: + strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI); + break; + case SND_SOC_DAIFMT_NB_IF: + strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_NB_NF: + strcr &= ~SSI_STCR_TFSI; + strcr |= SSI_STCR_TSCKP; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + strcr |= SSI_STCR_TFDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + strcr |= SSI_STCR_TXDIR; + break; + } + + strcr |= SSI_STCR_TFEN0; + + writel(strcr, ssi->base + SSI_STCR); + writel(strcr, ssi->base + SSI_SRCR); + writel(scr, ssi->base + SSI_SCR); + + return 0; +} + +/* + * SSI system clock configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + u32 scr; + + scr = readl(ssi->base + SSI_SCR); + + switch (clk_id) { + case IMX_SSP_SYS_CLK: + if (dir == SND_SOC_CLOCK_OUT) + scr |= SSI_SCR_SYS_CLK_EN; + else + scr &= ~SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + writel(scr, ssi->base + SSI_SCR); + + return 0; +} + +/* + * SSI Clock dividers + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + u32 stccr, srccr; + + stccr = readl(ssi->base + SSI_STCCR); + srccr = readl(ssi->base + SSI_SRCCR); + + switch (div_id) { + case IMX_SSI_TX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + case IMX_SSI_RX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + default: + return -EINVAL; + } + + writel(stccr, ssi->base + SSI_STCCR); + writel(srccr, ssi->base + SSI_SRCCR); + + return 0; +} + +/* + * Should only be called when port is inactive (i.e. SSIEN = 0), + * although can be called multiple times by upper layers. + */ +static int imx_ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + u32 reg, sccr; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = SSI_STCCR; + else + reg = SSI_SRCCR; + + sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; + + /* DAI data (word) size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + sccr |= SSI_SRCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + sccr |= SSI_SRCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + sccr |= SSI_SRCCR_WL(24); + break; + } + + writel(sccr, ssi->base + reg); + + return 0; +} + +static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { + .hw_params = imx_ssi_hw_params, + .set_fmt = imx_ssi_set_dai_fmt, + .set_clkdiv = imx_ssi_set_dai_clkdiv, + .set_sysclk = imx_ssi_set_dai_sysclk, + .set_tdm_slot = imx_ssi_set_dai_tdm_slot, +}; + +#define IMX_SSI_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai imx_ssi_dai = { + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = IMX_SSI_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = IMX_SSI_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = dma_mmap_coherent(NULL, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); + + pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); + return ret; +} + +static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + struct imx_channel *ch; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ch = &ssi->txc; + else + ch = &ssi->rxc; + + return bytes_to_frames(substream->runtime, ch->offset); +} + +static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + int i, err = 0; + struct imx_channel *ch; + unsigned long dma_addr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ch = &ssi->txc; + else + ch = &ssi->rxc; + + ch->size = params_buffer_bytes(params); + ch->periods = params_periods(params); + ch->period = params_period_bytes(params); + ch->offset = 0; + ch->period_time = HZ / (params_rate(params) / params_period_size(params)); + + err = devdma_hw_alloc(NULL, substream, ch->size); + if (err < 0) + return err; + + if (!use_dma(ssi)) + return 0; + + if (ch->sg_count != ch->periods) { + if (ch->sg_list) + kfree(ch->sg_list); + /* using sg_alloc_table() instead? */ + ch->sg_list = kcalloc(ch->periods + 1, + sizeof(struct scatterlist), GFP_KERNEL); + if (!ch->sg_list) + return -ENOMEM; + ch->sg_count = ch->periods + 1; + } + + sg_init_table(ch->sg_list, ch->sg_count); + dma_addr = runtime->dma_addr; + + for (i = 0; i < ch->periods; i++) { + ch->sg_list[i].page_link = 0; + ch->sg_list[i].offset = 0; /* FIXME */ + ch->sg_list[i].dma_address = dma_addr; + ch->sg_list[i].length = ch->period; + dma_addr += ch->period; + } + + /* close the loop */ + ch->sg_list[ch->sg_count - 1].offset = 0; + ch->sg_list[ch->sg_count - 1].length = 0; + ch->sg_list[ch->sg_count - 1].page_link = + ((unsigned long) ch->sg_list | 0x01) & ~0x02; + return 0; +} + +static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + devdma_hw_free(NULL, substream); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + devdma_hw_free(NULL, substream); + return 0; +} + +static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + struct imx_channel *ch; + u32 val, mode; + int err; + struct pt_regs regs; + + val = readl(ssi->base + SSI_SCR); + val |= SSI_SCR_SSIEN; + writel(val, ssi->base + SSI_SCR); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mode = DMA_MODE_WRITE; + ch = &ssi->txc; + } else { + mode = DMA_MODE_READ; + ch = &ssi->rxc; + } + + ch->substream = substream; + ch->buf = (unsigned int *)substream->dma_buffer.area; + ch->period_cnt = 0; + + pr_debug("%s: buf: %p period: %d periods: %d\n", + __func__, ch->buf, ch->period, ch->periods); + + if (use_dma(ssi)) { + err = imx_dma_setup_sg(ch->dma, ch->sg_list, + ch->sg_count, + IMX_DMA_LENGTH_LOOP, + ch->dma_addr, + mode); + if (err) + return err; + } else { + get_fiq_regs(®s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regs.ARM_r8 = (ch->period * ch->periods - 1) << 16; + else + regs.ARM_r9 = (ch->period * ch->periods - 1) << 16; + + /* FIXME: need to disable fiqs here? */ + set_fiq_regs(®s); + } + + return 0; +} + +static int snd_imx_pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * Do not change this as the FIQ handler depends on this size + */ +#define IMX_SSI_DMABUF_SIZE (64 * 1024) + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 16 * 1024, + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static int snd_imx_open(struct snd_pcm_substream *substream) +{ + int ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + return 0; +} + +static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, dai); + struct imx_channel *ch; + unsigned int sier, sier_bits, scr; + + sier = readl(ssi->base + SSI_SIER); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (use_dma(ssi)) + sier_bits = SSI_SIER_TDMAE; + else + sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; + ch = &ssi->txc; + } else { + if (use_dma(ssi)) + sier_bits = SSI_SIER_RDMAE; + else + sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; + ch = &ssi->rxc; + } + + if (ssi->flags & IMX_SSI_USE_AC97) { + if (use_dma(ssi)) + sier_bits = SSI_SIER_TDMAE | SSI_SIER_RDMAE; + else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; + else + sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; + } + } + + scr = readl(ssi->base + SSI_SCR); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sier |= sier_bits; + if (use_dma(ssi)) { + writel(sier, ssi->base + SSI_SIER); + imx_dma_enable(ch->dma); + } else { + writel(sier, ssi->base + SSI_SIER); + ch->timer.expires = jiffies + ch->period_time; + add_timer(&ch->timer); + if (ssi->flags & IMX_SSI_USE_AC97) { + while(!(readl(ssi->base + SSI_SISR) & (1<<7))) + readl(ssi->base + SSI_SRX0); + writel((1<<5) | SSI_SOR_WAIT(3), ssi->base + SSI_SOR); + } + if (++ssi->fiq_enable == 1) + enable_fiq(ssi->irq); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr |= SSI_SCR_TE; + else + scr |= SSI_SCR_RE; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sier &= ~sier_bits; + if (use_dma(ssi)) { + imx_dma_disable(ch->dma); + } else { + writel(sier, ssi->base + SSI_SIER); + del_timer(&ch->timer); + if (--ssi->fiq_enable == 0) + disable_fiq(ssi->irq); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr &= ~SSI_SCR_TE; + else + scr &= ~SSI_SCR_RE; + + break; + default: + return -EINVAL; + } + + if (!(ssi->flags & IMX_SSI_USE_AC97)) + /* rx/tx are always enabled to read/write ac97 registers */ + writel(scr, ssi->base + SSI_SCR); + + return 0; +} + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = IMX_SSI_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; + else + imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; + + return 0; +} + +static u64 imx_pcm_dmamask = DMA_BIT_MASK(32); + +static int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ +#ifdef IMX_SSI_MX1_MX2_DMA + struct imx_ssi *ssi = container_of(dai, struct imx_ssi, dai); +#endif + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &imx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + if (dai->playback.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + + if (!use_dma(ssi)) { + set_fiq_handler(&imx_ssi_fiq_start, + &imx_ssi_fiq_end - &imx_ssi_fiq_start); + } +out: + return ret; +} + +static void imx_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static struct snd_pcm_ops imx_pcm_ops = { + .open = snd_imx_open, + .close = snd_imx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_imx_pcm_hw_params, + .hw_free = snd_imx_pcm_hw_free, + .prepare = snd_imx_pcm_prepare, + .trigger = snd_imx_pcm_trigger, + .pointer = snd_imx_pcm_pointer, + .mmap = snd_imx_pcm_mmap +}; + +struct snd_soc_platform imx_soc_platform = { + .name = "imx-audio", + .pcm_ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, + .pcm_free = imx_pcm_free, +}; +EXPORT_SYMBOL_GPL(imx_soc_platform); + +#ifdef IMX_SSI_MX1_MX2_DMA +/* Called by the DMA framework when a period has elapsed */ +static void imx_ssi_dma_progression(int channel, void *data, + struct scatterlist *sg) +{ + struct imx_ssi *ssi = data; + struct imx_channel *ch; + struct snd_pcm_runtime *runtime; + + if (!sg) + return; + + if (ssi->txc.dma == channel) + ch = &ssi->txc; + else + ch = &ssi->rxc; + + runtime = ch->substream->runtime; + + ch->offset = sg->dma_address - runtime->dma_addr; + + snd_pcm_period_elapsed(ch->substream); +} + +static void imx_ssi_dma_callback(int channel, void *data) +{ + pr_err("%s shouldn't be called\n", __func__); +} + +static void snd_imx_dma_err_callback(int channel, void *data, int err) +{ + pr_err("DMA error callback called\n"); + + pr_err("DMA timeout on channel %d -%s%s%s%s\n", + channel, + err & IMX_DMA_ERR_BURST ? " burst":"", + err & IMX_DMA_ERR_REQUEST ? " request":"", + err & IMX_DMA_ERR_TRANSFER ? " transfer":"", + err & IMX_DMA_ERR_BUFFER ? " buffer":""); +} + +static int imx_ssi_dma_alloc(struct platform_device *pdev, struct imx_ssi *ssi, int tx) +{ + struct imx_channel *ch; + struct resource *res; + int ret; + + ch = tx ? &ssi->txc : &ssi->rxc; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, + tx ? "tx0" : "rx0"); + if (!res) { + dev_err(&pdev->dev, "platform_get_resource_byname tx0 failed\n"); + return -ENODEV; + } + + ch->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH); + if (ch->dma < 0) { + pr_err("Failed to claim the audio DMA\n"); + return -ENODEV; + } + + ret = imx_dma_setup_handlers(ch->dma, + imx_ssi_dma_callback, + snd_imx_dma_err_callback, ssi); + if (ret) { + dev_err(&pdev->dev, "imx_dma_setup_handlers tx0 failed: %d\n", + ret); + goto out; + } + + ret = imx_dma_setup_progression_handler(ch->dma, + imx_ssi_dma_progression); + if (ret) { + pr_err("Failed to setup the DMA handler\n"); + goto out; + } + + ret = imx_dma_config_channel(ch->dma, + IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, + IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, + res->start, 1); + if (ret < 0) { + pr_err("Cannot configure DMA channel: %d\n", ret); + goto out; + } + + imx_dma_config_burstlen(ch->dma, + (tx ? DMA_TXFIFO_BURST : DMA_RXFIFO_BURST) * 2); + + return 0; +out: + imx_dma_free(ch->dma); + return ret; +} +#endif + +static struct snd_soc_dai imx_ac97_dai = { + .name = "AC97", + .ac97_control = 1, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static void setup_channel_to_ac97(struct imx_ssi *imx_ssi) +{ + void __iomem *base = imx_ssi->base; + + writel(0x0, base + SSI_SCR); + writel(0x0, base + SSI_SIER); + writel(0x0, base + SSI_STCR); + writel(0x0, base + SSI_SRCR); + + writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR); + + writel(SSI_SFCSR_RFWM0(8) | + SSI_SFCSR_TFWM0(8) | + SSI_SFCSR_RFWM1(8) | + SSI_SFCSR_TFWM1(8), base + SSI_SFCSR); + + writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR); + writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR); + + writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR); + writel(SSI_SOR_WAIT(3), base + SSI_SOR); + + writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN | + SSI_SCR_TE | SSI_SCR_RE, + base + SSI_SCR); + + writel(SSI_SACNT_DEFAULT, base + SSI_SACNT); + writel(0xff, base + SSI_SACCDIS); + writel(0x300, base + SSI_SACCEN); +} + +static void imx_ssi_timer_callback(unsigned long data) +{ + struct imx_channel *ch = (void *)data; + struct pt_regs regs; + + get_fiq_regs(®s); + + if (ch->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ch->offset = regs.ARM_r8 & 0xffff; + else + ch->offset = regs.ARM_r9 & 0xffff; + + ch->timer.expires = jiffies + ch->period_time; + add_timer(&ch->timer); + snd_pcm_period_elapsed(ch->substream); +} + +static struct imx_ssi *ac97_ssi; + +static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + void __iomem *base = imx_ssi->base; + unsigned int lreg; + unsigned int lval; + + if (reg > 0x7f) + return; + + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + lreg = reg << 12; + writel(lreg, base + SSI_SACADD); + + lval = val << 4; + writel(lval , base + SSI_SACDAT); + + writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT); + udelay(100); +} + +static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + void __iomem *base = imx_ssi->base; + + unsigned short val = -1; + unsigned int lreg; + + lreg = (reg & 0x7f) << 12 ; + writel(lreg, base + SSI_SACADD); + writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT); + + udelay(100); + + val = (readl(base + SSI_SACDAT) >> 4) & 0xffff; + + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + return val; +} + +static void imx_ssi_ac97_reset(struct snd_ac97 *ac97) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + + if (imx_ssi->ac97_reset) + imx_ssi->ac97_reset(ac97); +} + +static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + + if (imx_ssi->ac97_warm_reset) + imx_ssi->ac97_warm_reset(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = imx_ssi_ac97_read, + .write = imx_ssi_ac97_write, + .reset = imx_ssi_ac97_reset, + .warm_reset = imx_ssi_ac97_warm_reset +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static struct fiq_handler fh = { + .name = DRV_NAME, +}; + +struct snd_soc_dai *imx_ssi_pcm_dai[2]; + +static int imx_ssi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_ssi *ssi; + struct imx_ssi_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + unsigned int val; + + ssi = kzalloc(sizeof (*ssi), GFP_KERNEL); + if (!ssi) + return -ENOMEM; + + if (pdata) { + ssi->ac97_reset = pdata->ac97_reset; + ssi->ac97_warm_reset = pdata->ac97_warm_reset; + ssi->flags = pdata->flags; + } + + imx_ssi_pcm_dai[pdev->id] = &ssi->dai; + + ssi->irq = platform_get_irq(pdev, 0); + + if (!use_dma(ssi)) { + ret = claim_fiq(&fh); + if (ret) { + dev_err(&pdev->dev, "failed to claim fiq: %d", ret); + goto failed_fiq; + } + + mxc_set_irq_fiq(ssi->irq, 1); + + init_timer(&ssi->txc.timer); + ssi->txc.timer.data = (unsigned long)&ssi->txc; + ssi->txc.timer.function = imx_ssi_timer_callback; + + init_timer(&ssi->rxc.timer); + ssi->rxc.timer.data = (unsigned long)&ssi->rxc; + ssi->rxc.timer.function = imx_ssi_timer_callback; + } + + ssi->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(ssi->clk)) { + ret = PTR_ERR(ssi->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + goto failed_clk; + } + clk_enable(ssi->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto failed_get_resource; + } + + ssi->txc.dma_addr = res->start + SSI_STX0; + ssi->rxc.dma_addr = res->start + SSI_SRX0; + + if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto failed_get_resource; + } + + ssi->base = ioremap(res->start, resource_size(res)); + if (!ssi->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto failed_ioremap; + } + + imx_ssi_fiq_base = (unsigned long)ssi->base; + +#ifdef IMX_SSI_MX1_MX2_DMA + if (use_dma(ssi)) { + ret = imx_ssi_dma_alloc(pdev, ssi, 1); + if (ret) + goto failed_dma1; + + ret = imx_ssi_dma_alloc(pdev, ssi, 0); + if (ret) + goto failed_dma2; + } +#endif + + if (ssi->flags & IMX_SSI_USE_AC97) { + if (ac97_ssi) { + ret = -EBUSY; + goto failed_dma2; + } + ac97_ssi = ssi; + setup_channel_to_ac97(ssi); + memcpy(&ssi->dai, &imx_ac97_dai, sizeof(imx_ac97_dai)); + } else + memcpy(&ssi->dai, &imx_ssi_dai, sizeof(imx_ssi_dai)); + ssi->dai.id = pdev->id; + ssi->dai.dev = &pdev->dev; + ssi->dai.name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id); + + if (use_dma(ssi)) { + val = SSI_SFCSR_RFWM0(DMA_RXFIFO_BURST) | + SSI_SFCSR_TFWM0(DMA_TXFIFO_BURST); + } else { + val = SSI_SFCSR_TFWM0(4) | SSI_SFCSR_RFWM0(6); + } + + writel(val, ssi->base + SSI_SFCSR); + + ret = snd_soc_register_dai(&ssi->dai); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + return 0; + +failed_register: +#ifdef IMX_SSI_MX1_MX2_DMA + if (use_dma(ssi)) + imx_dma_free(ssi->rxc.dma); +#endif +failed_dma2: +#ifdef IMX_SSI_MX1_MX2_DMA + if (use_dma(ssi)) + imx_dma_free(ssi->txc.dma); +failed_dma1: +#endif + + iounmap(ssi->base); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_get_resource: + clk_disable(ssi->clk); + clk_put(ssi->clk); +failed_clk: + if (!use_dma(ssi)) { + mxc_set_irq_fiq(ssi->irq, 0); + release_fiq(&fh); + } +failed_fiq: + kfree(ssi); + + return ret; +} + +static int __devexit imx_ssi_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct imx_ssi *ssi = platform_get_drvdata(pdev); + + snd_soc_unregister_dai(&ssi->dai); + + if (ssi->flags & IMX_SSI_USE_AC97) + ac97_ssi = NULL; + +#ifdef IMX_SSI_MX1_MX2_DMA + if (use_dma(ssi)) { + imx_dma_free(ssi->rxc.dma); + imx_dma_free(ssi->txc.dma); + } +#endif + iounmap(ssi->base); + release_mem_region(res->start, resource_size(res)); + clk_disable(ssi->clk); + clk_put(ssi->clk); + kfree(ssi); + + return 0; +} + +static struct platform_driver imx_ssi_driver = { + .probe = imx_ssi_probe, + .remove = __devexit_p(imx_ssi_remove), + + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init imx_ssi_init(void) +{ + int ret; + + ret = snd_soc_register_platform(&imx_soc_platform); + if (ret) { + pr_err("failed to register soc platform: %d\n", ret); + return ret; + } + + ret = platform_driver_register(&imx_ssi_driver); + if (ret) { + snd_soc_unregister_platform(&imx_soc_platform); + return ret; + } + + return 0; +} + +static void __exit imx_ssi_exit(void) +{ + platform_driver_unregister(&imx_ssi_driver); + snd_soc_unregister_platform(&imx_soc_platform); +} + +device_initcall(imx_ssi_init); +module_exit(imx_ssi_exit); + +/* Module information */ +MODULE_AUTHOR("Sascha Hauer, s.hauer@pengutronix.de"); +MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h new file mode 100644 index 0000000..d836fd9 --- /dev/null +++ b/sound/soc/imx/imx-ssi.h @@ -0,0 +1,24 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _IMX_SSI_H +#define _IMX_SSI_H + +/* SSI clock sources */ +#define IMX_SSP_SYS_CLK 0 + +/* SSI audio dividers */ +#define IMX_SSI_TX_DIV_2 0 +#define IMX_SSI_TX_DIV_PSR 1 +#define IMX_SSI_TX_DIV_PM 2 +#define IMX_SSI_RX_DIV_2 3 +#define IMX_SSI_RX_DIV_PSR 4 +#define IMX_SSI_RX_DIV_PM 5 + +extern struct snd_soc_dai *imx_ssi_pcm_dai[2]; +extern struct snd_soc_platform imx_soc_platform; + +#endif /* _IMX_SSI_H */
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- sound/soc/imx/phycore.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 181 insertions(+), 0 deletions(-) create mode 100644 sound/soc/imx/phycore.c
diff --git a/sound/soc/imx/phycore.c b/sound/soc/imx/phycore.c new file mode 100644 index 0000000..35e5875 --- /dev/null +++ b/sound/soc/imx/phycore.c @@ -0,0 +1,181 @@ +/* + * phycore.c -- SoC audio for imx_phycore + * + * Copyright 2009 Sascha Hauer, Pengutronix s.hauer@pengutronix.de + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/mc13783.h" +#include "../codecs/wm9712.h" +#include "imx-ssi.h" + +static struct snd_soc_card imx_phycore; + +static int imx_phycore_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + int ret = 0; + + /* set cpu DAI configuration */ + if (machine_is_pcm038() || machine_is_pcm037()) { + snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + } else { + snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, 0); + } + snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0); + + /* Put DC field of STCCR to 1 (not zero) */ + ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0x0, 0xfffffffc, 4, 0); + } + if (ret < 0) { + printk(KERN_ERR "Error from cpu DAI configuration\n"); + return ret; + } + + return ret; +} + +static int imx_phycore_hifi_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_soc_ops imx_phycore_hifi_ops = { + .hw_params = imx_phycore_hifi_hw_params, + .hw_free = imx_phycore_hifi_hw_free, +}; + +static int imx_phycore_probe(struct platform_device *pdev) +{ + return 0; +} + +static int imx_phycore_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_SND_SOC_MC13783 +static struct snd_soc_dai_link imx_phycore_dai_mc13783[] = { + { + .name = "MC13783 Playback", + .stream_name = "Playback", + .codec_dai = &mc13783_dai[0], + .ops = &imx_phycore_hifi_ops, + }, { + .name = "MC13783 Capture", + .stream_name = "Capture", + .codec_dai = &mc13783_dai[1], + .ops = &imx_phycore_hifi_ops, + }, +}; +#endif + +#ifdef CONFIG_SND_SOC_WM9712 +static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .ops = &imx_phycore_hifi_ops, + }, +}; +#endif + +static struct snd_soc_card imx_phycore = { + .name = "PhyCORE-audio", + .platform = &imx_soc_platform, + .probe = imx_phycore_probe, + .remove = imx_phycore_remove, + .dai_link = NULL, /* runtime */ + .num_links = 0, /* runtime */ +}; + +static struct snd_soc_device imx_phycore_snd_devdata = { + .card = &imx_phycore, + .codec_dev = NULL, /* runtime */ +}; + +static struct platform_device *imx_phycore_snd_device; + +static int __init imx_phycore_init(void) +{ + int ret; + + if (machine_is_pcm038() || machine_is_pcm037()) { +#ifdef CONFIG_SND_SOC_MC13783 + imx_phycore_snd_devdata.codec_dev = &soc_codec_dev_mc13783; + imx_phycore.dai_link = imx_phycore_dai_mc13783; + imx_phycore.num_links = ARRAY_SIZE(imx_phycore_dai_mc13783); +#endif + } else if (machine_is_pcm043() || machine_is_pca100()) { +#ifdef CONFIG_SND_SOC_WM9712 + imx_phycore_snd_devdata.codec_dev = &soc_codec_dev_wm9712; + imx_phycore.dai_link = imx_phycore_dai_ac97; + imx_phycore.num_links = ARRAY_SIZE(imx_phycore_dai_ac97); +#endif + } else + /* return happy. We might run on a totally different machine */ + return 0; + + imx_phycore_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_phycore_snd_device) + return -ENOMEM; + +#ifdef CONFIG_SND_SOC_MC13783 + imx_phycore_dai_mc13783[0].cpu_dai = imx_ssi_pcm_dai[0]; + imx_phycore_dai_mc13783[1].cpu_dai = imx_ssi_pcm_dai[0]; +#endif +#ifdef CONFIG_SND_SOC_WM9712 + imx_phycore_dai_ac97[0].cpu_dai = imx_ssi_pcm_dai[0]; +#endif + platform_set_drvdata(imx_phycore_snd_device, &imx_phycore_snd_devdata); + imx_phycore_snd_devdata.dev = &imx_phycore_snd_device->dev; + ret = platform_device_add(imx_phycore_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(imx_phycore_snd_device); + } + + return ret; +} + +static void __exit imx_phycore_exit(void) +{ + platform_device_unregister(imx_phycore_snd_device); +} + +late_initcall(imx_phycore_init); +module_exit(imx_phycore_exit); + +MODULE_AUTHOR("Sascha Hauer s.hauer@pengutronix.de"); +MODULE_DESCRIPTION("PhyCORE ALSA SoC driver"); +MODULE_LICENSE("GPL");
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- sound/soc/imx/Kconfig | 15 +++++++++++++-- sound/soc/imx/Makefile | 9 +++++---- 2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index a700562..83b6252 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -1,7 +1,9 @@ -config SND_MX1_MX2_SOC +config SND_IMX_SOC tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs" - depends on ARCH_MX2 || ARCH_MX1 + depends on ARCH_MXC select SND_PCM + select FIQ + select SND_SOC_AC97_BUS help Say Y or M if you want to add support for codecs attached to the MX1 or MX2 SSI interface. @@ -18,4 +20,13 @@ config SND_SOC_MX27VIS_WM8974 Say Y if you want to add support for SoC audio on Visstrim SM10 board with WM8974.
+config SND_SOC_PHYCORE + tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" + depends on MACH_PCM038 || MACH_PCM043 || MACH_PCA100 || MACH_PCM037 + select SND_MXC_SOC_SSI + select SND_SOC_MC13783 if MACH_PCM038 || MACH_PCM037 + select SND_SOC_WM9712 if MACH_PCM043 || MACH_PCA100 + help + Say Y if you want to add support for SoC audio on Phytec phyCORE + and phyCARD baords
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index c2ffd2c..f8c3be1 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -1,10 +1,11 @@ # i.MX Platform Support -snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o -snd-soc-mxc-ssi-objs := mxc-ssi.o +snd-soc-imx-objs := imx-ssi.o devdma.o
-obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o -obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o +obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
# i.MX Machine Support snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o + +snd-soc-phycore-objs := phycore.o +obj-$(CONFIG_SND_SOC_PHYCORE) += snd-soc-phycore.o
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/mach-mx2/pcm038.c | 23 ++++++++++++++++++++++- 1 files changed, 22 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-mx2/pcm038.c b/arch/arm/mach-mx2/pcm038.c index 906d59b..7d11da1 100644 --- a/arch/arm/mach-mx2/pcm038.c +++ b/arch/arm/mach-mx2/pcm038.c @@ -39,7 +39,9 @@ #include <mach/iomux.h> #include <mach/imx-uart.h> #include <mach/mxc_nand.h> +#include <mach/audmux.h> #include <mach/spi.h> +#include <mach/ssi.h>
#include "devices.h"
@@ -264,7 +266,7 @@ static struct mc13783_platform_data pcm038_pmic = { .regulators = pcm038_regulators, .num_regulators = ARRAY_SIZE(pcm038_regulators), .flags = MC13783_USE_ADC | MC13783_USE_REGULATOR | - MC13783_USE_TOUCHSCREEN, + MC13783_USE_TOUCHSCREEN | MC13783_USE_CODEC, };
static struct spi_board_info pcm038_spi_board_info[] __initdata = { @@ -279,16 +281,35 @@ static struct spi_board_info pcm038_spi_board_info[] __initdata = { } };
+struct imx_ssi_platform_data pcm038_ssi_pdata = { +// .flags = IMX_SSI_DMA, +}; + static void __init pcm038_init(void) { mxc_gpio_setup_multiple_pins(pcm038_pins, ARRAY_SIZE(pcm038_pins), "PCM038");
+ mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + MXC_AUDMUX_V1_PCR_TFSDIR | + MXC_AUDMUX_V1_PCR_TCLKDIR | + MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | + MXC_AUDMUX_V1_PCR_RFSDIR | + MXC_AUDMUX_V1_PCR_RCLKDIR | + MXC_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4)); + mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4, + MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)); + mxc_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1, + MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR1_SSI0) | + MXC_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR1_SSI0)); + pcm038_init_sram();
mxc_register_device(&mxc_uart_device0, &uart_pdata[0]); mxc_register_device(&mxc_uart_device1, &uart_pdata[1]); mxc_register_device(&mxc_uart_device2, &uart_pdata[2]); + mxc_register_device(&imx_ssi_device0, &pcm038_ssi_pdata);
mxc_gpio_mode(PE16_AF_OWIRE); mxc_register_device(&mxc_nand_device, &pcm038_nand_board_info);
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/mach-mx3/pcm043.c | 102 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 102 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-mx3/pcm043.c b/arch/arm/mach-mx3/pcm043.c index e3aa829..072f9b4 100644 --- a/arch/arm/mach-mx3/pcm043.c +++ b/arch/arm/mach-mx3/pcm043.c @@ -28,6 +28,7 @@ #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/i2c/at24.h> +#include <linux/delay.h>
#include <asm/mach-types.h> #include <asm/mach/arch.h> @@ -44,6 +45,8 @@ #include <mach/ipu.h> #include <mach/mx3fb.h> #include <mach/mxc_nand.h> +#include <mach/audmux.h> +#include <mach/ssi.h>
#include "devices.h"
@@ -205,6 +208,91 @@ static struct pad_desc pcm043_pads[] = { MX35_PAD_D3_CLS__IPU_DISPB_D3_CLS, /* gpio */ MX35_PAD_ATA_CS0__GPIO2_6, + /* SSI */ + MX35_PAD_STXFS4__AUDMUX_AUD4_TXFS, + MX35_PAD_STXD4__AUDMUX_AUD4_TXD, + MX35_PAD_SRXD4__AUDMUX_AUD4_RXD, + MX35_PAD_SCK4__AUDMUX_AUD4_TXC, +}; + +#define AC97_GPIO_TXFS (1 * 32 + 31) +#define AC97_GPIO_TXD (1 * 32 + 28) +#define AC97_GPIO_RESET (1 * 32 + 0) + +static void pcm043_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct pad_desc txfs_gpio = MX35_PAD_STXFS4__GPIO2_31; + struct pad_desc txfs = MX35_PAD_STXFS4__AUDMUX_AUD4_TXFS; + int ret; + + ret = gpio_request(AC97_GPIO_TXFS, "SSI"); + if (ret) { + printk("failed to get GPIO_TXFS: %d\n", ret); + return; + } + + mxc_iomux_v3_setup_pad(&txfs_gpio); + + /* warm reset */ + gpio_direction_output(AC97_GPIO_TXFS, 1); + udelay(2); + gpio_set_value(AC97_GPIO_TXFS, 0); + + gpio_free(AC97_GPIO_TXFS); + mxc_iomux_v3_setup_pad(&txfs); +} + +static void pcm043_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct pad_desc txfs_gpio = MX35_PAD_STXFS4__GPIO2_31; + struct pad_desc txfs = MX35_PAD_STXFS4__AUDMUX_AUD4_TXFS; + struct pad_desc txd_gpio = MX35_PAD_STXD4__GPIO2_28; + struct pad_desc txd = MX35_PAD_STXD4__AUDMUX_AUD4_TXD; + struct pad_desc reset_gpio = MX35_PAD_SD2_CMD__GPIO2_0; + int ret; + + ret = gpio_request(AC97_GPIO_TXFS, "SSI"); + if (ret) + goto err1; + + ret = gpio_request(AC97_GPIO_TXD, "SSI"); + if (ret) + goto err2; + + ret = gpio_request(AC97_GPIO_RESET, "SSI"); + if (ret) + goto err3; + + mxc_iomux_v3_setup_pad(&txfs_gpio); + mxc_iomux_v3_setup_pad(&txd_gpio); + mxc_iomux_v3_setup_pad(&reset_gpio); + + gpio_direction_output(AC97_GPIO_TXFS, 0); + gpio_direction_output(AC97_GPIO_TXD, 0); + + /* cold reset */ + gpio_direction_output(AC97_GPIO_RESET, 0); + udelay(10); + gpio_direction_output(AC97_GPIO_RESET, 1); + + mxc_iomux_v3_setup_pad(&txd); + mxc_iomux_v3_setup_pad(&txfs); + + gpio_free(AC97_GPIO_RESET); +err3: + gpio_free(AC97_GPIO_TXD); +err2: + gpio_free(AC97_GPIO_TXFS); +err1: + if (ret) + printk("%s failed with %d\n", __func__, ret); + mdelay(1); +} + +static struct imx_ssi_platform_data pcm043_ssi_pdata = { + .ac97_reset = pcm043_ac97_cold_reset, + .ac97_warm_reset = pcm043_ac97_warm_reset, + .flags = IMX_SSI_USE_AC97, };
static struct mxc_nand_platform_data pcm037_nand_board_info = { @@ -219,12 +307,25 @@ static void __init mxc_board_init(void) { mxc_iomux_v3_setup_multiple_pads(pcm043_pads, ARRAY_SIZE(pcm043_pads));
+ mxc_audmux_v2_configure_port(3, + MXC_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V2_PTCR_TFSEL(0) | + MXC_AUDMUX_V2_PTCR_TFSDIR, + MXC_AUDMUX_V2_PDCR_RXDSEL(0)); + + mxc_audmux_v2_configure_port(0, + MXC_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V2_PTCR_TCSEL(3) | + MXC_AUDMUX_V2_PTCR_TCLKDIR, /* clock is output */ + MXC_AUDMUX_V2_PDCR_RXDSEL(3)); + platform_add_devices(devices, ARRAY_SIZE(devices));
mxc_register_device(&mxc_uart_device0, &uart_pdata); mxc_register_device(&mxc_nand_device, &pcm037_nand_board_info);
mxc_register_device(&mxc_uart_device1, &uart_pdata); + mxc_register_device(&imx_ssi_device0, &pcm043_ssi_pdata);
#if defined CONFIG_I2C_IMX || defined CONFIG_I2C_IMX_MODULE i2c_register_board_info(0, pcm043_i2c_devices, @@ -239,6 +340,7 @@ static void __init mxc_board_init(void)
static void __init pcm043_timer_init(void) { + printk("mx35_clocks_init()\n"); mx35_clocks_init(); }
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/mach-mx2/pca100.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 52 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-mx2/pca100.c b/arch/arm/mach-mx2/pca100.c index aea3d34..e0eb21f 100644 --- a/arch/arm/mach-mx2/pca100.c +++ b/arch/arm/mach-mx2/pca100.c @@ -26,6 +26,7 @@ #include <linux/spi/eeprom.h> #include <linux/irq.h> #include <linux/gpio.h> +#include <linux/delay.h>
#include <asm/mach/arch.h> #include <asm/mach-types.h> @@ -41,6 +42,8 @@ #include <mach/mxc_nand.h> #include <mach/irqs.h> #include <mach/mmc.h> +#include <mach/audmux.h> +#include <mach/ssi.h>
#include "devices.h"
@@ -182,15 +185,64 @@ static struct imxmmc_platform_data sdhc_pdata = { .exit = pca100_sdhc2_exit, };
+static void pca100_ac97_warm_reset(struct snd_ac97 *ac97) +{ + printk("%s\n", __func__); + + mxc_gpio_mode(GPIO_PORTC | 20 | GPIO_GPIO | GPIO_OUT); + gpio_set_value(GPIO_PORTC + 20, 1); + udelay(2); + gpio_set_value(GPIO_PORTC + 20, 0); + mxc_gpio_mode(PC20_PF_SSI1_FS); + msleep(2); +} + +static void pca100_ac97_cold_reset(struct snd_ac97 *ac97) +{ + printk("%s\n", __func__); + + mxc_gpio_mode(GPIO_PORTC | 20 | GPIO_GPIO | GPIO_OUT); /* FS */ + gpio_set_value(GPIO_PORTC + 20, 0); + mxc_gpio_mode(GPIO_PORTC | 22 | GPIO_GPIO | GPIO_OUT); /* TX */ + gpio_set_value(GPIO_PORTC + 22, 0); + mxc_gpio_mode(GPIO_PORTC | 28 | GPIO_GPIO | GPIO_OUT); /* reset */ + gpio_set_value(GPIO_PORTC + 28, 0); + udelay(10); + gpio_set_value(GPIO_PORTC + 28, 1); + mxc_gpio_mode(PC20_PF_SSI1_FS); + mxc_gpio_mode(PC22_PF_SSI1_TXD); + msleep(2); +} + +static struct imx_ssi_platform_data pca100_ssi_pdata = { + .ac97_reset = pca100_ac97_cold_reset, + .ac97_warm_reset = pca100_ac97_warm_reset, + .flags = IMX_SSI_USE_AC97, +}; + static void __init pca100_init(void) { int ret;
+ /* SSI unit */ + mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + MXC_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V1_PCR_TFCSEL(3) | + MXC_AUDMUX_V1_PCR_TCLKDIR | /* clock is output */ + MXC_AUDMUX_V1_PCR_RXDSEL(3)); + mxc_audmux_v1_configure_port(4, + MXC_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + MXC_AUDMUX_V1_PCR_TFCSEL(0) | + MXC_AUDMUX_V1_PCR_TFSDIR | + MXC_AUDMUX_V1_PCR_RXDSEL(0)); + ret = mxc_gpio_setup_multiple_pins(pca100_pins, ARRAY_SIZE(pca100_pins), "PCA100"); if (ret) printk(KERN_ERR "pca100: Failed to setup pins (%d)\n", ret);
+ mxc_register_device(&imx_ssi_device0, &pca100_ssi_pdata); + mxc_register_device(&mxc_uart_device0, &uart_pdata);
mxc_gpio_mode(GPIO_PORTC | 29 | GPIO_GPIO | GPIO_IN);
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/mach-mx3/pcm037.c | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 72 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-mx3/pcm037.c b/arch/arm/mach-mx3/pcm037.c index 6cbaabe..15584b5 100644 --- a/arch/arm/mach-mx3/pcm037.c +++ b/arch/arm/mach-mx3/pcm037.c @@ -33,6 +33,7 @@ #include <linux/irq.h> #include <linux/fsl_devices.h> #include <linux/can/platform/sja1000.h> +#include <linux/mfd/mc13783.h>
#include <media/soc_camera.h>
@@ -51,6 +52,9 @@ #include <mach/mx3_camera.h> #include <mach/mx3fb.h> #include <mach/mxc_nand.h> +#include <mach/spi.h> +#include <mach/audmux.h> +#include <mach/ssi.h>
#include "devices.h" #include "pcm037.h" @@ -124,6 +128,8 @@ static unsigned int pcm037_pins[] = { MX31_PIN_CSPI3_SPI_RDY__CTS3, /* LAN9217 irq pin */ IOMUX_MODE(MX31_PIN_GPIO3_1, IOMUX_CONFIG_GPIO), + /* MC13783 IRQ pim */ + IOMUX_MODE(MX31_PIN_GPIO3_0, IOMUX_CONFIG_GPIO), /* Onewire */ MX31_PIN_BATT_LINE__OWIRE, /* Framebuffer */ @@ -172,6 +178,15 @@ static unsigned int pcm037_pins[] = { MX31_PIN_CSI_VSYNC__CSI_VSYNC, /* GPIO */ IOMUX_MODE(MX31_PIN_ATA_DMACK, IOMUX_CONFIG_GPIO), + /* SSI */ + MX31_PIN_STXD4__STXD4, + MX31_PIN_SRXD4__SRXD4, + MX31_PIN_SCK4__SCK4, + MX31_PIN_SFS4__SFS4, + MX31_PIN_STXD5__STXD5, + MX31_PIN_SRXD5__SRXD5, + MX31_PIN_SCK5__SCK5, + MX31_PIN_SFS5__SFS5, };
static struct physmap_flash_data pcm037_flash_data = { @@ -407,6 +422,31 @@ static void pcm970_sdhc1_exit(struct device *dev, void *data) gpio_free(SDHC1_GPIO_WP); }
+#ifdef CONFIG_SPI +static struct mc13783_platform_data pcm037_pmic = { + .flags = MC13783_USE_ADC | MC13783_USE_TOUCHSCREEN | + MC13783_USE_CODEC, +}; + +static unsigned int pcm037_spi_cs[] = {MXC_SPI_CS(0), }; + +static struct spi_imx_master pcm037_spi_0_data = { + .chipselect = pcm037_spi_cs, + .num_chipselect = ARRAY_SIZE(pcm037_spi_cs), +}; + +static struct spi_board_info pcm037_spi_board_info[] __initdata = { + { + .modalias = "mc13783", + .irq = IOMUX_TO_IRQ(MX31_PIN_GPIO3_0), + .max_speed_hz = 3000000, + .bus_num = 0, + .platform_data = &pcm037_pmic, + .chip_select = 0, + } +}; +#endif /* CONFIG_SPI */ + static struct imxmmc_platform_data sdhc_pdata = { #ifdef PCM970_SDHC_RW_SWITCH .get_ro = pcm970_sdhc1_get_ro, @@ -543,6 +583,10 @@ static struct platform_device pcm970_sja1000 = { .num_resources = ARRAY_SIZE(pcm970_sja1000_resources), };
+struct imx_ssi_platform_data pcm037_ssi_pdata = { +// .flags = IMX_SSI_DMA, +}; + /* * Board specific initialization. */ @@ -550,6 +594,26 @@ static void __init mxc_board_init(void) { int ret;
+ mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_RFSDIR | + MXC_AUDMUX_V2_PTCR_RCLKDIR | + MXC_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | + MXC_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5), + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5)); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, + 0, + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0)); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT1_SSI0) | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT1_SSI0) | + MXC_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT1_SSI0) | + MXC_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT1_SSI0), + 0); + mxc_iomux_setup_multiple_pins(pcm037_pins, ARRAY_SIZE(pcm037_pins), "pcm037");
@@ -605,6 +669,14 @@ static void __init mxc_board_init(void) mxc_register_device(&mx3_camera, &camera_pdata);
platform_device_register(&pcm970_sja1000); + +#ifdef CONFIG_SPI + mxc_register_device(&mxc_spi_device0, &pcm037_spi_0_data); + + spi_register_board_info(pcm037_spi_board_info, + ARRAY_SIZE(pcm037_spi_board_info)); +#endif + mxc_register_device(&imx_ssi_device0, &pcm037_ssi_pdata); }
static void __init pcm037_timer_init(void)
On Thu, Nov 19, 2009 at 04:48:21PM +0100, Sascha Hauer wrote:
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de
- if (machine_is_pcm038() || machine_is_pcm037()) {
Just check when probing the driver.
snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
snd_soc_dai_set_fmt(codec_dai,
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
} else {
snd_soc_dai_set_fmt(codec_dai,
SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, 0);
}
This looks very wrong. The CPU is unconditionally configured to use I2S mode but the CODEC may be configured to use either I2S or DSP mode depending on the direction of audio. The CPU and CODEC should at least be in the same mode...
/* Put DC field of STCCR to 1 (not zero) */
ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0x0, 0xfffffffc, 4, 0);
That comment doesn't really clarify anything for me...
+static int imx_phycore_hifi_hw_free(struct snd_pcm_substream *substream) +{
- return 0;
+}
+static int imx_phycore_probe(struct platform_device *pdev) +{
- return 0;
+}
+static int imx_phycore_remove(struct platform_device *pdev) +{
- return 0;
+}
Remove these if they have no content.
+#ifdef CONFIG_SND_SOC_WM9712 +static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
- {
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI],
.ops = &imx_phycore_hifi_ops,
- },
+}; +#endif
I'd make this a second machine driver, it doesn't appear to have anything in common with the Atlas based system and the conditionals make everything more complicated.
On Thu, Nov 19, 2009 at 04:48:20PM +0100, Sascha Hauer wrote:
This looks pretty good overall, but it'd be nice to split the DMA bits out into a separate file simply because this is how the code is normally structured. Given all the conditionals for the FIQ/DMA selection in the DMA code I did wonder if it might not be easiest to just have separate DMA drivers for the two (probably sharing some routines that are identical).
Right now the conditionals are spread out through the driver which feels a bit intrusive given that the selection between the two models will be done once. A split like that should also make it easier for someone to swap in SDMA support at some point in the future, I'd hope.
Otherwise my comments are all very minor, though I've not tried running it up on an actual platform and verifying the functionality yet:
--- /dev/null +++ b/sound/soc/imx/imx-ssi.c @@ -0,0 +1,1349 @@
+#include "../codecs/ac97.h"
Why do you need to include the header for a particular CODEC driver in a CPU DAI driver? I didn't actually notice any references to it in the code so perhaps it could just be deleted.
+/*
- SSI DAI format configuration.
- Should only be called when port is inactive (i.e. SSIEN = 0).
- Note: We don't use the I2S modes but instead manually configure the
- SSI for I2S.
Might be nice to carry on the comment and explain why not :)
+#define IMX_SSI_RATES \
- (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
- SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
- SNDRV_PCM_RATE_96000)
SNDRV_PCM_RATE_8000_96000
+static int snd_imx_pcm_close(struct snd_pcm_substream *substream) +{
- return 0;
+}
Remove this if it's not needed.
+device_initcall(imx_ssi_init);
It take it this is required for the FIQ?
On Thu, Nov 19, 2009 at 9:48 AM, Sascha Hauer s.hauer@pengutronix.de wrote:
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de
arch/arm/plat-mxc/Makefile | 1 + arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/ssi-fiq.S | 134 ++++ sound/soc/imx/devdma.c | 80 ++ sound/soc/imx/devdma.h | 3 + sound/soc/imx/imx-ssi.c | 1349 ++++++++++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 24 +
Why did you write a new SSI driver when there already is one in sound/soc/fsl?
On Sat, Nov 21, 2009 at 08:12:34PM -0600, Timur Tabi wrote:
Why did you write a new SSI driver when there already is one in sound/soc/fsl?
I can't speak for Sascha here but the i.MX BSPs provided by your colleagues on the i.MX team also have an independant driver for the SSI port. The latest public version of the Freescale i.MX code I'm aware of is in the recent Ubuntu kernels - I've not yet checked but I'd not be surprised to see substantial code sharing between Sascha's code and the BSP for the SSI-specific bits.
It would be very much desirable to unify the various drivers, though I would prefer to avoid making this a blocker for getting i.MX3x "DMA" support in mainline. Taking a brief glance through the code it looks like there's some platform-specifics in the drivers - the i.MX one has optional DMA support due to the whole SDMA issue, while the PowerPC one is including asm/immap_86xx.h for something. Perhaps pulling the common code into a library would be a good approach here?
On Mon, Nov 23, 2009 at 12:00:01PM +0000, Mark Brown wrote:
On Sat, Nov 21, 2009 at 08:12:34PM -0600, Timur Tabi wrote:
Why did you write a new SSI driver when there already is one in sound/soc/fsl?
I can't speak for Sascha here but the i.MX BSPs provided by your colleagues on the i.MX team also have an independant driver for the SSI port. The latest public version of the Freescale i.MX code I'm aware of is in the recent Ubuntu kernels - I've not yet checked but I'd not be surprised to see substantial code sharing between Sascha's code and the BSP for the SSI-specific bits.
It would be very much desirable to unify the various drivers, though I would prefer to avoid making this a blocker for getting i.MX3x "DMA" support in mainline. Taking a brief glance through the code it looks like there's some platform-specifics in the drivers - the i.MX one has optional DMA support due to the whole SDMA issue, while the PowerPC one is including asm/immap_86xx.h for something.
This include seems unused. Timur, can you confirm this?
Sascha
Sascha Hauer wrote:
while the PowerPC one
is including asm/immap_86xx.h for something.
This include seems unused. Timur, can you confirm this?
I can confirm that immap_86xx.h is *not* needed for fsl_ssi.c. When I update the driver for the P1022, I'll remove it.
On Sat, Nov 21, 2009 at 08:12:34PM -0600, Timur Tabi wrote:
On Thu, Nov 19, 2009 at 9:48 AM, Sascha Hauer s.hauer@pengutronix.de wrote:
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de
arch/arm/plat-mxc/Makefile | 1 + arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/ssi-fiq.S | 134 ++++ sound/soc/imx/devdma.c | 80 ++ sound/soc/imx/devdma.h | 3 + sound/soc/imx/imx-ssi.c | 1349 ++++++++++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 24 +
Why did you write a new SSI driver when there already is one in sound/soc/fsl?
Mainly because I wasn't aware of the fact that the fsl driver is the same :(
I'm currently looking if and how the code can be merged. I don't know if it's worth it though since the common code doesn't seem to be very big.
There will have to be some changes in the fsl-ssi driver. For example I need the irq handler for my fiq handler which means that the fsl-ssi driver is not allowed to request the irq for it's statistic. The other thing is the hardware accesses as we do not have in_be*, out_be* and setbits/clrbits. Of course all these things are doable and shouldn't be a showstopper.
Sascha
Sascha Hauer wrote:
Mainly because I wasn't aware of the fact that the fsl driver is the same:(
So "fsl-ssi.c" was not a clue? :-)
I'm currently looking if and how the code can be merged. I don't know if it's worth it though since the common code doesn't seem to be very big.
That's probably because my driver only supports I2S and yours supports AC97. Please, DMA is different. I wasn't really expecting you to be able to re-use my driver, but I figured you could at least re-use the macros I've defined in fsl-ssi.h
There will have to be some changes in the fsl-ssi driver. For example I need the irq handler for my fiq handler which means that the fsl-ssi driver is not allowed to request the irq for it's statistic.
That shouldn't be a problem, since the statistics never show anything useful anyway.
The other thing is the hardware accesses as we do not have in_be*, out_be* and setbits/clrbits. Of course all these things are doable and shouldn't be a showstopper.
These should be defined in the arch/arm header files anyway.
On Thu, Nov 19, 2009 at 04:48:17PM +0100, Sascha Hauer wrote:
This looks pretty good - the main non-nitpick issue here is that you've got a bunch of controls in here which look like they're routing and power control for the CODEC but which are just regular controls and are implemented as hand crafted code.
--- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -114,6 +114,9 @@ config SND_SOC_CX20442 config SND_SOC_L3 tristate
+config SND_SOC_MC13783
- tristate
config SND_SOC_PCM3008 tristate
Add this to SND_SOC_ALL_CODECS too please.
--- /dev/null +++ b/sound/soc/codecs/mc13783.c @@ -0,0 +1,739 @@ +/*
- Copyright 2008 Juergen Beisert, kernel@pengutronix.de
- Copyright 2009 Sascha Hauer, s.hauer@pengutronix.de
- Initial development of this code was funded by
- Phytec Messtechnik GmbH, http://www.phytec.de
Is this based on the FSL code at all - some of the code (eg, the name of the define used in the header) makes it look like it's based off something else? If so it should probably credit them too.
+static int mc13783_write(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int value)
+{
- struct mc13783_priv *priv = codec->private_data;
- priv->reg_cache[reg] = value;
- mc13783_reg_write(mc13783, reg, value);
- return 0;
+}
Pass back the return code of the core function?
+/* sample rates supported by PMIC for stereo playback operations on StDac. */ +static unsigned int mc13783_rates[] = {
- 8000, 11025, 12000, 16000,
- 22050, 24000, 32000,44100,
- 48000, 64000, 96000
+};
It'd be good to add a comment explaining that this is used to look up configurations.
if (rate == mc13783_rates[i]) {
reg = mc13783_read(codec, PMIC_AUDIO_DAC);
/* setup the clock speed */
reg &= ~(0xf << 17);
reg |= i << 17;
mc13783_write(codec, PMIC_AUDIO_DAC, reg);
snd_soc_update_bits() (older drivers don't use this but it really helps with legibility and will also supress no-change writes for you).
+static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- struct snd_soc_codec *codec = dai->codec;
- unsigned int reg;
- if (dai->id == 1)
Some defines in the header for the DAIs might help legibility, both here and in the machine drivers.
+static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- reg |= STEREO_DAC_STD_C_EN; /* DAC power up */
The DAC power shouldn't need to be controlled here?
+static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai,
- unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
- struct snd_soc_codec *codec = dai->codec;
- unsigned int reg;
- if (slots != 4)
return -EINVAL;
- reg = mc13783_read(codec, PMIC_SSI_NETWORK);
- reg &= ~(0xfff << 0);
- reg |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */
- reg |= (0x01 << 4); /* secondary timeslot TX is 1 */
- reg |= (0x01 << 6); /* secondary timeslot RX is 1 */
This appears to be pretty much ignoring the supplied arguments and using a fixed configuration?
+static int mc13783_asp_val; +static int mc13783_alsp_val;
Put these in the CODEC private data.
+static int mc13783_pcm_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- int val;
- val = mc13783_read(codec, PMIC_AUDIO_RX_0);
- ucontrol->value.enumerated.item[0] = (val >> 22) & 1;
return 0;
Look to be some tab/space problem here and elsewhere in the custom control code.
+static struct snd_kcontrol_new mc13783_control_list[] = {
- /* Output Routing */
- SOC_ENUM_EXT("Asp Source", mc13783_enum[0], mc13783_get_asp, mc13783_put_asp),
- SOC_ENUM_EXT("Alsp Source", mc13783_enum[1], mc13783_get_alsp, mc13783_put_alsp),
- SOC_ENUM("Ahs Source", mc13783_enum[2]),
Don't use an array of enums - declare individual variables. For historical reasons some of the older drivers do do this but it is a bit hard to read and prone to off by one errors.
- SOC_SINGLE("Ahsr enable", PMIC_AUDIO_RX_0, 9, 1, 0),
- SOC_SINGLE("Ahsl enable", PMIC_AUDIO_RX_0, 10, 1, 0),
- SOC_ENUM("Arxout Source", mc13783_enum[3]),
- SOC_SINGLE("ArxoutR enable", PMIC_AUDIO_RX_0, 16, 1, 0),
- SOC_SINGLE("ArxoutL enable", PMIC_AUDIO_RX_0, 15, 1, 0),
These controls all look like they should be part of some DAPM routing configuration for the device and either DAPM controls exposed to users to set the routing or DAPM widgets automatically managed by the core. The enums probably want to be muxes, the enables either switches on mixers or widgets of some kind.
I'm also not sure why these are implemented as hand crafted controls rather than using the standard register manipulation widgets but I have to confess I skipped over a lot of them since I couldn't tell what they were doing from the names alone as I read through the file.
- SOC_ENUM("3D Control - Switch", mc13783_enum[5]),
No Switch - Switch means on/off to ALSA. There's some brief naming documentation in Documentation/sound/alsa/ControlNames.txt.
- /* VAUDIOON -> supply audio part, BIAS enable */
- priv->reg_cache[PMIC_AUDIO_RX_0] |= 0x3;
Bias would normally be managed in a set_bias_level() function.
+#define MC13783_RATES_PLAYBACK (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
SNDRV_PCM_RATE_8000_96000 is probably what you want here.
+/*
- OK, this stinks. We currently only can support one MC13783.
- Lets take it as an intermediate to turn this stuff into SoC
- Audio.
- */
+static int mc13783_codec_probe(struct platform_device *pdev) +{
- struct mc13783_priv *priv;
- struct snd_soc_codec *codec;
- int ret;
- printk(KERN_INFO "MC13783 Audio Codec\n");
Please remove this, it doesn't really add anything.
- ret = snd_soc_register_dais(mc13783_dai, ARRAY_SIZE(mc13783_dai));
- if (ret)
goto out;
- ret = snd_soc_register_codec(codec);
- if (ret)
goto out;
You should register the CODEC first then the DAIs since that's what everything else does (so it'll be less fragile in the future) and logically the DAIs hang off the CODEC.
+static int mc13783_codec_remove(struct platform_device *pdev) +{
- mc13783 = NULL;
Should unregister the things you registered here.
Hi Mark,
+static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai,
- unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
- struct snd_soc_codec *codec = dai->codec;
- unsigned int reg;
- if (slots != 4)
return -EINVAL;
- reg = mc13783_read(codec, PMIC_SSI_NETWORK);
- reg &= ~(0xfff << 0);
- reg |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */
- reg |= (0x01 << 4); /* secondary timeslot TX is 1 */
- reg |= (0x01 << 6); /* secondary timeslot RX is 1 */
This appears to be pretty much ignoring the supplied arguments and using a fixed configuration?
The problem here is that I don't really understand what arguments I should supply to this function and how to parse them to archieve what I want.
The MC13783 Voice Codec always uses a network mode to send data. It has a fixed width of four slots.
PMIC_SSI_NETWORK[2:3] defines the slot for primary transmit/receive PMIC_SSI_NETWORK[4:5] defines the secondary transmit timeslot PMIC_SSI_NETWORK[6:7] defines the secondary receive timeslot
The Codec has two channels and it's not primarily desgined to do stereo Audio. I want to put the primary channel into the first timeslot and the secondary channel into the second timeslot.
Any idea how to do this correctly?
Sascha
On Wed, Nov 25, 2009 at 08:46:31AM +0100, Sascha Hauer wrote:
The problem here is that I don't really understand what arguments I should supply to this function and how to parse them to archieve what I want.
The MC13783 Voice Codec always uses a network mode to send data. It has a fixed width of four slots.
PMIC_SSI_NETWORK[2:3] defines the slot for primary transmit/receive PMIC_SSI_NETWORK[4:5] defines the secondary transmit timeslot PMIC_SSI_NETWORK[6:7] defines the secondary receive timeslot
The Codec has two channels and it's not primarily desgined to do stereo Audio. I want to put the primary channel into the first timeslot and the secondary channel into the second timeslot.
What are "primary" and "secondary" here? If they are broadly unrelated except for the clocking (which is what this sounds like) then they probably should be handled as separate DAIs. For the TDM configuration you should be forcing a slot count of four slots. The RX and TX masks will then allow one bit to be set, specifying which of the slots the data goes in for each direction.
On Wed, Nov 25, 2009 at 10:39:59AM +0000, Mark Brown wrote:
On Wed, Nov 25, 2009 at 08:46:31AM +0100, Sascha Hauer wrote:
The problem here is that I don't really understand what arguments I should supply to this function and how to parse them to archieve what I want.
The MC13783 Voice Codec always uses a network mode to send data. It has a fixed width of four slots.
PMIC_SSI_NETWORK[2:3] defines the slot for primary transmit/receive PMIC_SSI_NETWORK[4:5] defines the secondary transmit timeslot PMIC_SSI_NETWORK[6:7] defines the secondary receive timeslot
The Codec has two channels and it's not primarily desgined to do stereo Audio. I want to put the primary channel into the first timeslot and the secondary channel into the second timeslot.
What are "primary" and "secondary" here? If they are broadly unrelated except for the clocking (which is what this sounds like) then they probably should be handled as separate DAIs.
I thought about this, but can I do stereo with two seperate DAIs?
Sascha
On Wed, Nov 25, 2009 at 12:08:54PM +0100, Sascha Hauer wrote:
On Wed, Nov 25, 2009 at 10:39:59AM +0000, Mark Brown wrote:
What are "primary" and "secondary" here? If they are broadly unrelated except for the clocking (which is what this sounds like) then they probably should be handled as separate DAIs.
I thought about this, but can I do stereo with two seperate DAIs?
Again, what are "primary" and "secondary"?
On Wed, Nov 25, 2009 at 11:10:37AM +0000, Mark Brown wrote:
On Wed, Nov 25, 2009 at 12:08:54PM +0100, Sascha Hauer wrote:
On Wed, Nov 25, 2009 at 10:39:59AM +0000, Mark Brown wrote:
What are "primary" and "secondary" here? If they are broadly unrelated except for the clocking (which is what this sounds like) then they probably should be handled as separate DAIs.
I thought about this, but can I do stereo with two seperate DAIs?
Again, what are "primary" and "secondary"?
Please have a look at page 3 of:
http://www.freescale.com/files/rf_if/doc/data_sheet/MC13783.pdf
Maybe this explains best the situation we have here. Right in the middle of the diagram you see the A/D converters in the Voice Codec. As you see it has two monaural A/D converters. One of them is the primary, the other is the secondary converter (don't ask me which of them is which, I'd had to lookup myself) The input sources of these ADCs can't be controlled completely independent and they are designed to be used for example with two microphones in a mobile phone. (I guess that's why they call them 'primary' and 'secondary', not 'left' and 'right'). But of course you can also do stereo with them.
Sascha
On Wed, Nov 25, 2009 at 12:30:34PM +0100, Sascha Hauer wrote:
On Wed, Nov 25, 2009 at 11:10:37AM +0000, Mark Brown wrote:
Again, what are "primary" and "secondary"?
Maybe this explains best the situation we have here. Right in the middle of the diagram you see the A/D converters in the Voice Codec. As you see it has two monaural A/D converters. One of them is the primary, the other is the secondary converter (don't ask me which of them is which, I'd had to lookup myself)
So all you've really got here is stereo in one direction and mono in the other with flexible placement of the slots.
The input sources of these ADCs can't be controlled completely independent and they are designed to be used for example with two microphones in a mobile phone. (I guess that's why they call them 'primary' and 'secondary', not 'left' and 'right'). But of course you can also do stereo with them.
You could either treat them independently or just assume they're going to be used as a stereo pair and put them in one DAI. A third option is to define DAIs for both cases and then trust machine drivers not to mix the stereo and mono options inappropriately. It's really up to you.
For the random placement of the two channels look at the set_channel_map API.
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
I've CCed in Javier who wrote the currently merged code.
I'll review these patches tomorrow or over the weekend, however this jumps out at me as being something that we'd want to fix before merging since having two drivers for the same bit of hardware always leads to problems going forwards. I don't mind how this is addressed, replacing the existing driver or incrementally fixing it would both work for me (the latter is obviously preferrable but equally well we don't want to get bogged down).
The approach of using FIQ for i.MX3x until SDMA gets merged seems like a reasonable one. It's not ideal but it will work and it shouldn't create any problems transitioning to SDMA when that is submitted.
BTW, might be nice to use --no-chain-reply-to for git send-email - it makes long patch sets look a lot nicer in a threaded mail reader. Not a big deal, though.
Hi Mark,
On Thu, Nov 19, 2009 at 04:28:30PM +0000, Mark Brown wrote:
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
I've CCed in Javier who wrote the currently merged code.
I'll review these patches tomorrow or over the weekend, however this jumps out at me as being something that we'd want to fix before merging since having two drivers for the same bit of hardware always leads to problems going forwards. I don't mind how this is addressed, replacing the existing driver or incrementally fixing it would both work for me (the latter is obviously preferrable but equally well we don't want to get bogged down).
My work isn't based on the mainline driver but some older (also Freescale based) code. Putting this into an incremental series would mean quite some work. Lets wait for your review before making further decisions.
The approach of using FIQ for i.MX3x until SDMA gets merged seems like a reasonable one. It's not ideal but it will work and it shouldn't create any problems transitioning to SDMA when that is submitted.
Which is in unforseeable future. So far I haven't seen anyone getting far on this task.
BTW, might be nice to use --no-chain-reply-to for git send-email - it makes long patch sets look a lot nicer in a threaded mail reader. Not a big deal, though.
I usually use the chain reply option because otherwise the patches sometimes get the wrong order when doing git am on a mailbox.
Sascha
On Thu, Nov 19, 2009 at 06:53:27PM +0100, Sascha Hauer wrote:
My work isn't based on the mainline driver but some older (also Freescale based) code. Putting this into an incremental series would mean quite some work.
That's not really my point - my point is that one way or another the new code needs to update the existing code. I don't want to end up in a situation where we have two different drivers in mainline since that will create user confusion and a greater maintinence burden. I'm not so fussed about how exactly that is achieved.
Theoretically, current merged code only supports i.MX21/27 in the DMA part. So it would be correct adding a DMA separate driver for i.MX31.
However I disagree with completely new SSI code. It should be possible to add an arch define to export proper DMA structures whether we are using i.MX21/27 or i.MX31 in the current SSI driver.
Hi Javier,
On Fri, Nov 20, 2009 at 08:51:31AM +0100, javier Martin wrote:
Theoretically, current merged code only supports i.MX21/27 in the DMA part. So it would be correct adding a DMA separate driver for i.MX31.
However I disagree with completely new SSI code. It should be possible to add an arch define to export proper DMA structures whether we are using i.MX21/27 or i.MX31 in the current SSI driver.
I would agree if the current code worked and would be good shape. Unfortunately it's not. It hardcodes the fact that there are 2 SSI units (i.MX51 has three of them). Instead of writel/readl it uses direct pointer derefs. The driver skips audio while it reinitialises the DMA engine. In mx1_mx2_pcm_open() there are several memory leaks. The driver does not call clk_get itself but depends on the board doing it. The only available board support depends on a board not present in the kernel, so we are not able to compile it (and it won't because the audmux definitions are missing)
This all leads to a huge series of patches and much work and in the end we would have a nearly rewritten driver.
Sascha
I would agree if the current code worked and would be good shape. Unfortunately it's not. It hardcodes the fact that there are 2 SSI units (i.MX51 has three of them). Instead of writel/readl it uses direct pointer derefs. The driver skips audio while it reinitialises the DMA engine. In mx1_mx2_pcm_open() there are several memory leaks. The driver does not call clk_get itself but depends on the board doing it. The only available board support depends on a board not present in the kernel, so we are not able to compile it (and it won't because the audmux definitions are missing)
We have still some pending HW changes in our board so we are not ready to submit it now. Anyway it would have been nice to have such this comments when I submitted my patches to avoid unnecessary rework.
This all leads to a huge series of patches and much work and in the end we would have a nearly rewritten driver.
All right, just let me test your patch in our board and comment.
Thank you.
On Fri, Nov 20, 2009 at 11:32:10AM +0100, javier Martin wrote:
I would agree if the current code worked and would be good shape. Unfortunately it's not. It hardcodes the fact that there are 2 SSI units (i.MX51 has three of them). Instead of writel/readl it uses direct pointer derefs. The driver skips audio while it reinitialises the DMA engine. In mx1_mx2_pcm_open() there are several memory leaks. The driver does not call clk_get itself but depends on the board doing it. The only available board support depends on a board not present in the kernel, so we are not able to compile it (and it won't because the audmux definitions are missing)
We have still some pending HW changes in our board so we are not ready to submit it now. Anyway it would have been nice to have such this comments when I submitted my patches to avoid unnecessary rework.
Sorry, I missed them. I'm subscribed to alsa-devel for one day now and AFAIK the patches haven't been posted to any other list.
Sascha
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
BTW, unless I notice anything particularly bad in there I'm inclined to just merge all the arch/arm patches by themselves since obviously you're the maintainer they'd normally go via - I'm assuming the other patches depend on them?
Thanks a lot for doing this work - I've got some boards that I could make a lot of use of if we had i.MX3x audio support in mainline!
On Thu, Nov 19, 2009 at 04:32:41PM +0000, Mark Brown wrote:
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
BTW, unless I notice anything particularly bad in there I'm inclined to just merge all the arch/arm patches by themselves since obviously you're the maintainer they'd normally go via - I'm assuming the other patches depend on them?
I'd prefer merging the arm patches myself as I'm afraid of bothering upstream upstream with merge conflicts. I think I can rebase this series in a way that there won't be compile breakages during merge.
Sascha
On 19 Nov 2009, at 17:47, Sascha Hauer s.hauer@pengutronix.de wrote:
On Thu, Nov 19, 2009 at 04:32:41PM +0000, Mark Brown wrote:
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
There already is a driver for i.MX in the tree, this one is a nearly complete rewrite. I haven't touched the in Kernel driver with these patches. The in Kernel driver has several problems, it doesn't use ioremap, uses direct pointer derefs instead of proper access functions and the only board supported so far is itself not supported in mainline.
BTW, unless I notice anything particularly bad in there I'm inclined to just merge all the arch/arm patches by themselves since obviously you're the maintainer they'd normally go via - I'm assuming the other patches depend on them?
I'd prefer merging the arm patches myself as I'm afraid of bothering upstream upstream with merge conflicts. I think I can rebase this series in a way that there won't be compile breakages during merge.
Please do say when you're submitting if this is you're intention - if you just send me patches without any other comment I'll tend to assume that your intention is that I should apply them and go ahead and do so if they review OK. Merging via ARM is OK providing the build doesn't get broken. If that happens we can do the same thing as has been done with TWL4030 and merge a branch into both trees.
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
one single FIFO. The WM9712 I worked with sends data in slot 12 which we have to sort out manually from the FIFO, so I think there is no way to work around the fiq handler using the DMA engine with AC97.
Slot 12 is used for GPIOs to allow them to be read/written without register accesses - I'd expect that data there will not be uncommon.
The devdma code is actually a copy of sound/arm/devdma.[ch]. It should probably be selected somehow instead of duplicated.
Yes, ideally. A simple Makefile change in sound/arm should do the trick. Other than this I'm happy with all the patches I've not specifically commented on.
On Fri, Nov 20, 2009 at 11:17:03AM +0000, Mark Brown wrote:
On Thu, Nov 19, 2009 at 04:48:14PM +0100, Sascha Hauer wrote:
one single FIFO. The WM9712 I worked with sends data in slot 12 which we have to sort out manually from the FIFO, so I think there is no way to work around the fiq handler using the DMA engine with AC97.
Slot 12 is used for GPIOs to allow them to be read/written without register accesses - I'd expect that data there will not be uncommon.
The devdma code is actually a copy of sound/arm/devdma.[ch]. It should probably be selected somehow instead of duplicated.
Yes, ideally. A simple Makefile change in sound/arm should do the trick. Other than this I'm happy with all the patches I've not specifically commented on.
Ok, great. I'm working on the comments you made. I expect this will take some days. Thanks for review.
Sascha
participants (4)
-
javier Martin
-
Mark Brown
-
Sascha Hauer
-
Timur Tabi